From 067e5f68b9216b233df1c6529db182ff9b2887ab Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Sun, 7 Sep 2014 23:14:30 -0700 Subject: [PATCH] Add new wallpaper features for insets and offsets. Issue #17394151: WallpaperService / Engines need to get notified of WindowInsets Issue #17394203 Wallpapers need a system API to be shifted in order to support burn in protection Adds a new API on WallpaperManager to set additional offsets to make wallpapers extend beyond the display size. Insets are now reported to wallpapers, to use as they may. This includes information about the above offsets, so they can place their content within the visible area. And to help with this, also expose the stable offsets APIs in WindowInsets which is also very useful information for the wallpaper. Another new API on WallpaperManager to set a raw offset to apply to the wallpaper window, forcing it to move on the screen regardless of what the wallpaper is drawing. Fix wallpapers when used with overscan enabled, so they still extend out across the entire screen. Conveniently, the above new window insets information is very useful for this case as well! And a new wallpaper test app for all this stuff. Change-Id: I287ee36581283dd34607609fcd3170d99d120d8e --- api/current.txt | 7 + core/java/android/app/IWallpaperManager.aidl | 6 + core/java/android/app/WallpaperManager.java | 43 +++ .../service/wallpaper/IWallpaperEngine.aidl | 2 + .../service/wallpaper/IWallpaperService.aidl | 3 +- .../service/wallpaper/WallpaperService.java | 172 ++++++++++-- core/java/android/view/IWindowSession.aidl | 5 + core/java/android/view/ViewRootImpl.java | 2 +- core/java/android/view/WindowInsets.java | 54 +++- .../policy/impl/PhoneWindowManager.java | 26 +- .../wallpaper/WallpaperManagerService.java | 86 +++++- .../java/com/android/server/wm/Session.java | 12 + .../server/wm/WindowManagerService.java | 38 +++ .../com/android/server/wm/WindowState.java | 12 + .../server/wm/WindowStateAnimator.java | 5 +- tests/WallpaperTest/Android.mk | 15 ++ tests/WallpaperTest/AndroidManifest.xml | 31 +++ .../drawable-hdpi/test_wallpaper_thumb.png | Bin 0 -> 34700 bytes .../res/layout/activity_main.xml | 177 ++++++++++++ tests/WallpaperTest/res/values-v11/styles.xml | 28 ++ tests/WallpaperTest/res/values-v21/styles.xml | 29 ++ tests/WallpaperTest/res/values/colors.xml | 19 ++ tests/WallpaperTest/res/values/strings.xml | 42 +++ tests/WallpaperTest/res/values/styles.xml | 37 +++ .../WallpaperTest/res/xml/test_wallpaper.xml | 26 ++ .../example/wallpapertest/MainActivity.java | 176 ++++++++++++ .../example/wallpapertest/TestWallpaper.java | 251 ++++++++++++++++++ 27 files changed, 1254 insertions(+), 50 deletions(-) create mode 100644 tests/WallpaperTest/Android.mk create mode 100644 tests/WallpaperTest/AndroidManifest.xml create mode 100644 tests/WallpaperTest/res/drawable-hdpi/test_wallpaper_thumb.png create mode 100644 tests/WallpaperTest/res/layout/activity_main.xml create mode 100644 tests/WallpaperTest/res/values-v11/styles.xml create mode 100644 tests/WallpaperTest/res/values-v21/styles.xml create mode 100644 tests/WallpaperTest/res/values/colors.xml create mode 100644 tests/WallpaperTest/res/values/strings.xml create mode 100644 tests/WallpaperTest/res/values/styles.xml create mode 100644 tests/WallpaperTest/res/xml/test_wallpaper.xml create mode 100644 tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java create mode 100644 tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java diff --git a/api/current.txt b/api/current.txt index fd72d340da475..ad36d73a6f5e6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27275,6 +27275,7 @@ package android.service.wallpaper { method public android.view.SurfaceHolder getSurfaceHolder(); method public boolean isPreview(); method public boolean isVisible(); + method public void onApplyWindowInsets(android.view.WindowInsets); method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean); method public void onCreate(android.view.SurfaceHolder); method public void onDesiredSizeChanged(int, int); @@ -34989,12 +34990,18 @@ package android.view { public final class WindowInsets { ctor public WindowInsets(android.view.WindowInsets); + method public android.view.WindowInsets consumeStableInsets(); method public android.view.WindowInsets consumeSystemWindowInsets(); + method public int getStableInsetBottom(); + method public int getStableInsetLeft(); + method public int getStableInsetRight(); + method public int getStableInsetTop(); method public int getSystemWindowInsetBottom(); method public int getSystemWindowInsetLeft(); method public int getSystemWindowInsetRight(); method public int getSystemWindowInsetTop(); method public boolean hasInsets(); + method public boolean hasStableInsets(); method public boolean hasSystemWindowInsets(); method public boolean isConsumed(); method public boolean isRound(); diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 181eb63bce694..3b5900b4a5d74 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -16,6 +16,7 @@ package android.app; +import android.graphics.Rect; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.app.IWallpaperManagerCallback; @@ -72,6 +73,11 @@ interface IWallpaperManager { */ int getHeightHint(); + /** + * Sets extra padding that we would like the wallpaper to have outside of the display. + */ + void setDisplayPadding(in Rect padding); + /** * Returns the name of the wallpaper. Private API. */ diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 48ff5b65dec5b..8bfe6d3859ae0 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -950,6 +951,48 @@ public class WallpaperManager { } } + /** + * Specify extra padding that the wallpaper should have outside of the display. + * That is, the given padding supplies additional pixels the wallpaper should extend + * outside of the display itself. + * @param padding The number of pixels the wallpaper should extend beyond the display, + * on its left, top, right, and bottom sides. + * @hide + */ + @SystemApi + public void setDisplayPadding(Rect padding) { + try { + if (sGlobals.mService == null) { + Log.w(TAG, "WallpaperService not running"); + } else { + sGlobals.mService.setDisplayPadding(padding); + } + } catch (RemoteException e) { + // Ignore + } + } + + /** + * Apply a raw offset to the wallpaper window. Should only be used in + * combination with {@link #setDisplayPadding(android.graphics.Rect)} when you + * have ensured that the wallpaper will extend outside of the display area so that + * it can be moved without leaving part of the display uncovered. + * @param x The offset, in pixels, to apply to the left edge. + * @param y The offset, in pixels, to apply to the top edge. + * @hide + */ + @SystemApi + public void setDisplayOffset(IBinder windowToken, int x, int y) { + try { + //Log.v(TAG, "Sending new wallpaper display offsets from app..."); + WindowManagerGlobal.getWindowSession().setWallpaperDisplayOffset( + windowToken, x, y); + //Log.v(TAG, "...app returning after sending display offset!"); + } catch (RemoteException e) { + // Ignore. + } + } + /** * Set the position of the current wallpaper within any larger space, when * that wallpaper is visible behind the given window. The X and Y offsets diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index faccde233c5aa..de527e958c6b2 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -16,6 +16,7 @@ package android.service.wallpaper; +import android.graphics.Rect; import android.view.MotionEvent; import android.os.Bundle; @@ -24,6 +25,7 @@ import android.os.Bundle; */ oneway interface IWallpaperEngine { void setDesiredSize(int width, int height); + void setDisplayPadding(in Rect padding); void setVisibility(boolean visible); void dispatchPointer(in MotionEvent event); void dispatchWallpaperCommand(String action, int x, int y, diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl index bc7a1d7aff9ce..5fd015781981f 100644 --- a/core/java/android/service/wallpaper/IWallpaperService.aidl +++ b/core/java/android/service/wallpaper/IWallpaperService.aidl @@ -16,6 +16,7 @@ package android.service.wallpaper; +import android.graphics.Rect; import android.service.wallpaper.IWallpaperConnection; /** @@ -24,5 +25,5 @@ import android.service.wallpaper.IWallpaperConnection; oneway interface IWallpaperService { void attach(IWallpaperConnection connection, IBinder windowToken, int windowType, boolean isPreview, - int reqWidth, int reqHeight); + int reqWidth, int reqHeight, in Rect padding); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f3c26c87c7b28..26e9a3031b3b4 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -16,6 +16,14 @@ package android.service.wallpaper; +import android.content.res.TypedArray; +import android.os.Build; +import android.os.SystemProperties; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.ViewRootImpl; +import android.view.WindowInsets; +import com.android.internal.R; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; import com.android.internal.view.BaseSurfaceHolder; @@ -56,6 +64,8 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; + /** * A wallpaper service is responsible for showing a live wallpaper behind * applications that would like to sit on top of it. This service object @@ -90,7 +100,8 @@ public abstract class WallpaperService extends Service { private static final int DO_ATTACH = 10; private static final int DO_DETACH = 20; private static final int DO_SET_DESIRED_SIZE = 30; - + private static final int DO_SET_DISPLAY_PADDING = 40; + private static final int MSG_UPDATE_SURFACE = 10000; private static final int MSG_VISIBILITY_CHANGED = 10010; private static final int MSG_WALLPAPER_OFFSETS = 10020; @@ -150,13 +161,23 @@ public abstract class WallpaperService extends Service { WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; + TypedValue mOutsetBottom; final Rect mVisibleInsets = new Rect(); final Rect mWinFrame = new Rect(); final Rect mOverscanInsets = new Rect(); final Rect mContentInsets = new Rect(); final Rect mStableInsets = new Rect(); + final Rect mDispatchedOverscanInsets = new Rect(); + final Rect mDispatchedContentInsets = new Rect(); + final Rect mDispatchedStableInsets = new Rect(); + final Rect mFinalSystemInsets = new Rect(); + final Rect mFinalStableInsets = new Rect(); final Configuration mConfiguration = new Configuration(); - + + private boolean mIsEmulator; + private boolean mIsCircularEmulator; + private boolean mWindowIsRound; + final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); IWindowSession mSession; @@ -406,7 +427,7 @@ public abstract class WallpaperService extends Service { */ public void onCreate(SurfaceHolder surfaceHolder) { } - + /** * Called right before the engine is going away. After this the * surface will be destroyed and this Engine object is no longer @@ -414,7 +435,7 @@ public abstract class WallpaperService extends Service { */ public void onDestroy() { } - + /** * Called to inform you of the wallpaper becoming visible or * hidden. It is very important that a wallpaper only use @@ -422,7 +443,17 @@ public abstract class WallpaperService extends Service { */ public void onVisibilityChanged(boolean visible) { } - + + /** + * Called with the current insets that are in effect for the wallpaper. + * This gives you the part of the overall wallpaper surface that will + * generally be visible to the user (ignoring position offsets applied to it). + * + * @param insets Insets to apply. + */ + public void onApplyWindowInsets(WindowInsets insets) { + } + /** * Called as the user performs touch-screen interaction with the * window that is currently showing this wallpaper. Note that the @@ -432,7 +463,7 @@ public abstract class WallpaperService extends Service { */ public void onTouchEvent(MotionEvent event) { } - + /** * Called to inform you of the wallpaper's offsets changing * within its contain, corresponding to the container's @@ -443,7 +474,7 @@ public abstract class WallpaperService extends Service { float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) { } - + /** * Process a command that was sent to the wallpaper with * {@link WallpaperManager#sendWallpaperCommand}. @@ -465,14 +496,14 @@ public abstract class WallpaperService extends Service { Bundle extras, boolean resultRequested) { return null; } - + /** * Called when an application has changed the desired virtual size of * the wallpaper. */ public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) { } - + /** * Convenience for {@link SurfaceHolder.Callback#surfaceChanged * SurfaceHolder.Callback.surfaceChanged()}. @@ -561,16 +592,20 @@ public abstract class WallpaperService extends Service { if (mDestroyed) { Log.w(TAG, "Ignoring updateSurface: destroyed"); } - + + boolean fixedSize = false; int myWidth = mSurfaceHolder.getRequestedWidth(); if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT; + else fixedSize = true; int myHeight = mSurfaceHolder.getRequestedHeight(); if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT; - + else fixedSize = true; + final boolean creating = !mCreated; final boolean surfaceCreating = !mSurfaceCreated; final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat(); boolean sizeChanged = mWidth != myWidth || mHeight != myHeight; + boolean insetsChanged = !mCreated; final boolean typeChanged = mType != mSurfaceHolder.getRequestedType(); final boolean flagsChanged = mCurWindowFlags != mWindowFlags || mCurWindowPrivateFlags != mWindowPrivateFlags; @@ -607,6 +642,32 @@ public abstract class WallpaperService extends Service { mLayout.token = mWindowToken; if (!mCreated) { + // Retrieve watch round and outset info + final WindowManager windowService = (WindowManager)getSystemService( + Context.WINDOW_SERVICE); + TypedArray windowStyle = obtainStyledAttributes( + com.android.internal.R.styleable.Window); + final Display display = windowService.getDefaultDisplay(); + final boolean shouldUseBottomOutset = + display.getDisplayId() == Display.DEFAULT_DISPLAY; + if (shouldUseBottomOutset && windowStyle.hasValue( + R.styleable.Window_windowOutsetBottom)) { + if (mOutsetBottom == null) mOutsetBottom = new TypedValue(); + windowStyle.getValue(R.styleable.Window_windowOutsetBottom, + mOutsetBottom); + } else { + mOutsetBottom = null; + } + mWindowIsRound = getResources().getBoolean( + com.android.internal.R.bool.config_windowIsRound); + windowStyle.recycle(); + + // detect emulator + mIsEmulator = Build.HARDWARE.contains("goldfish"); + mIsCircularEmulator = SystemProperties.getBoolean( + ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false); + + // Add window mLayout.type = mIWallpaperEngine.mWindowType; mLayout.gravity = Gravity.START|Gravity.TOP; mLayout.setTitle(WallpaperService.this.getClass().getName()); @@ -627,6 +688,11 @@ public abstract class WallpaperService extends Service { mSurfaceHolder.mSurfaceLock.lock(); mDrawingAllowed = true; + if (!fixedSize) { + mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding); + } else { + mLayout.surfaceInsets.set(0, 0, 0, 0); + } final int relayoutResult = mSession.relayout( mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets, @@ -636,16 +702,39 @@ public abstract class WallpaperService extends Service { + ", frame=" + mWinFrame); int w = mWinFrame.width(); + int h = mWinFrame.height(); + + if (!fixedSize) { + final Rect padding = mIWallpaperEngine.mDisplayPadding; + w += padding.left + padding.right; + h += padding.top + padding.bottom; + mOverscanInsets.left += padding.left; + mOverscanInsets.top += padding.top; + mOverscanInsets.right += padding.right; + mOverscanInsets.bottom += padding.bottom; + mContentInsets.left += padding.left; + mContentInsets.top += padding.top; + mContentInsets.right += padding.right; + mContentInsets.bottom += padding.bottom; + mStableInsets.left += padding.left; + mStableInsets.top += padding.top; + mStableInsets.right += padding.right; + mStableInsets.bottom += padding.bottom; + } + if (mCurWidth != w) { sizeChanged = true; mCurWidth = w; } - int h = mWinFrame.height(); if (mCurHeight != h) { sizeChanged = true; mCurHeight = h; } + insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets); + insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets); + insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets); + mSurfaceHolder.setSurfaceFrameSize(w, h); mSurfaceHolder.mSurfaceLock.unlock(); @@ -702,6 +791,25 @@ public abstract class WallpaperService extends Service { } } + if (insetsChanged) { + mDispatchedOverscanInsets.set(mOverscanInsets); + mDispatchedContentInsets.set(mContentInsets); + mDispatchedStableInsets.set(mStableInsets); + final boolean isRound = (mIsEmulator && mIsCircularEmulator) + || mWindowIsRound; + mFinalSystemInsets.set(mDispatchedOverscanInsets); + mFinalStableInsets.set(mDispatchedStableInsets); + if (mOutsetBottom != null) { + final DisplayMetrics metrics = getResources().getDisplayMetrics(); + mFinalSystemInsets.bottom = + ( (int) mOutsetBottom.getDimension(metrics) ) + + mIWallpaperEngine.mDisplayPadding.bottom; + } + WindowInsets insets = new WindowInsets(mFinalSystemInsets, + null, mFinalStableInsets, isRound); + onApplyWindowInsets(insets); + } + if (redrawNeeded) { onSurfaceRedrawNeeded(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -781,7 +889,7 @@ public abstract class WallpaperService extends Service { mReportedVisible = false; updateSurface(false, false, false); } - + void doDesiredSizeChanged(int desiredWidth, int desiredHeight) { if (!mDestroyed) { if (DEBUG) Log.v(TAG, "onDesiredSizeChanged(" @@ -792,14 +900,24 @@ public abstract class WallpaperService extends Service { doOffsetsChanged(true); } } - + + void doDisplayPaddingChanged(Rect padding) { + if (!mDestroyed) { + if (DEBUG) Log.v(TAG, "onDisplayPaddingChanged(" + padding + "): " + this); + if (!mIWallpaperEngine.mDisplayPadding.equals(padding)) { + mIWallpaperEngine.mDisplayPadding.set(padding); + updateSurface(true, false, false); + } + } + } + void doVisibilityChanged(boolean visible) { if (!mDestroyed) { mVisible = visible; reportVisibility(); } } - + void reportVisibility() { if (!mDestroyed) { boolean visible = mVisible && mScreenOn; @@ -956,12 +1074,13 @@ public abstract class WallpaperService extends Service { boolean mShownReported; int mReqWidth; int mReqHeight; - + final Rect mDisplayPadding = new Rect(); + Engine mEngine; - + IWallpaperEngineWrapper(WallpaperService context, IWallpaperConnection conn, IBinder windowToken, - int windowType, boolean isPreview, int reqWidth, int reqHeight) { + int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) { mCaller = new HandlerCaller(context, context.getMainLooper(), this, true); mConnection = conn; mWindowToken = windowToken; @@ -969,16 +1088,22 @@ public abstract class WallpaperService extends Service { mIsPreview = isPreview; mReqWidth = reqWidth; mReqHeight = reqHeight; + mDisplayPadding.set(padding); Message msg = mCaller.obtainMessage(DO_ATTACH); mCaller.sendMessage(msg); } - + public void setDesiredSize(int width, int height) { Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height); mCaller.sendMessage(msg); } - + + public void setDisplayPadding(Rect padding) { + Message msg = mCaller.obtainMessageO(DO_SET_DISPLAY_PADDING, padding); + mCaller.sendMessage(msg); + } + public void setVisibility(boolean visible) { Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED, visible ? 1 : 0); @@ -1041,6 +1166,9 @@ public abstract class WallpaperService extends Service { mEngine.doDesiredSizeChanged(message.arg1, message.arg2); return; } + case DO_SET_DISPLAY_PADDING: { + mEngine.doDisplayPaddingChanged((Rect) message.obj); + } case MSG_UPDATE_SURFACE: mEngine.updateSurface(true, false, false); break; @@ -1102,9 +1230,9 @@ public abstract class WallpaperService extends Service { @Override public void attach(IWallpaperConnection conn, IBinder windowToken, - int windowType, boolean isPreview, int reqWidth, int reqHeight) { + int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) { new IWallpaperEngineWrapper(mTarget, conn, windowToken, - windowType, isPreview, reqWidth, reqHeight); + windowType, isPreview, reqWidth, reqHeight, padding); } } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 0f3f182a627ca..037ed281be189 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -177,6 +177,11 @@ interface IWindowSession { void wallpaperOffsetsComplete(IBinder window); + /** + * Apply a raw offset to the wallpaper service when shown behind this window. + */ + void setWallpaperDisplayOffset(IBinder windowToken, int x, int y); + Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, in Bundle extras, boolean sync); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4299e2e73e1d5..7dd11f1f23a9e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -121,7 +121,7 @@ public final class ViewRootImpl implements ViewParent, private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media"; // property used by emulator to determine display shape - private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; + public static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular"; /** * Maximum time we allow the user to roll the trackball enough to generate diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 571a8f0d5a8d0..24c3c1a72ab06 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -378,35 +378,75 @@ public final class WindowInsets { } /** - * @hide + * Returns the top stable inset in pixels. + * + *

The stable inset represents the area of a full-screen window that may be + * partially or fully obscured by the system UI elements. This value does not change + * based on the visibility state of those elements; for example, if the status bar is + * normally shown, but temporarily hidden, the stable inset will still provide the inset + * associated with the status bar being shown.

+ * + * @return The top stable inset */ public int getStableInsetTop() { return mStableInsets.top; } /** - * @hide + * Returns the left stable inset in pixels. + * + *

The stable inset represents the area of a full-screen window that may be + * partially or fully obscured by the system UI elements. This value does not change + * based on the visibility state of those elements; for example, if the status bar is + * normally shown, but temporarily hidden, the stable inset will still provide the inset + * associated with the status bar being shown.

+ * + * @return The left stable inset */ public int getStableInsetLeft() { return mStableInsets.left; } /** - * @hide + * Returns the right stable inset in pixels. + * + *

The stable inset represents the area of a full-screen window that may be + * partially or fully obscured by the system UI elements. This value does not change + * based on the visibility state of those elements; for example, if the status bar is + * normally shown, but temporarily hidden, the stable inset will still provide the inset + * associated with the status bar being shown.

+ * + * @return The right stable inset */ public int getStableInsetRight() { return mStableInsets.right; } /** - * @hide + * Returns the bottom stable inset in pixels. + * + *

The stable inset represents the area of a full-screen window that may be + * partially or fully obscured by the system UI elements. This value does not change + * based on the visibility state of those elements; for example, if the status bar is + * normally shown, but temporarily hidden, the stable inset will still provide the inset + * associated with the status bar being shown.

+ * + * @return The bottom stable inset */ public int getStableInsetBottom() { return mStableInsets.bottom; } /** - * @hide + * Returns true if this WindowInsets has nonzero stable insets. + * + *

The stable inset represents the area of a full-screen window that may be + * partially or fully obscured by the system UI elements. This value does not change + * based on the visibility state of those elements; for example, if the status bar is + * normally shown, but temporarily hidden, the stable inset will still provide the inset + * associated with the status bar being shown.

+ * + * @return true if any of the stable inset values are nonzero */ public boolean hasStableInsets() { return mStableInsets.top != 0 || mStableInsets.left != 0 || mStableInsets.right != 0 @@ -414,7 +454,9 @@ public final class WindowInsets { } /** - * @hide + * Returns a copy of this WindowInsets with the stable insets fully consumed. + * + * @return A modified copy of this WindowInsets */ public WindowInsets consumeStableInsets() { final WindowInsets result = new WindowInsets(this); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index cd3eab5948916..c23468d9627a5 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -3533,13 +3533,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop + mOverscanScreenHeight; } else if (attrs.type == TYPE_WALLPAPER) { - // The wallpaper also has Real Ultimate Power. - pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft; - pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop; - pf.right = df.right = of.right = cf.right - = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; - pf.bottom = df.bottom = of.bottom = cf.bottom - = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + // The wallpaper also has Real Ultimate Power, but we want to tell + // it about the overscan area. + pf.left = df.left = mOverscanScreenLeft; + pf.top = df.top = mOverscanScreenTop; + pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth; + pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight; + of.left = cf.left = mUnrestrictedScreenLeft; + of.top = cf.top = mUnrestrictedScreenTop; + of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0 && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { @@ -3653,9 +3656,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it. if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR) { - df.left = df.top = of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000; - df.right = df.bottom = of.right = of.bottom = cf.right = cf.bottom - = vf.right = vf.bottom = 10000; + df.left = df.top = -10000; + df.right = df.bottom = 10000; + if (attrs.type != TYPE_WALLPAPER) { + of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000; + of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000; + } } if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle() diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 802df9522439f..e1ade63a2cb8c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -42,6 +42,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -206,6 +207,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { int width = -1; int height = -1; + final Rect padding = new Rect(0, 0, 0, 0); + WallpaperData(int userId) { this.userId = userId; wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER); @@ -222,6 +225,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { IRemoteCallback mReply; boolean mDimensionsChanged = false; + boolean mPaddingChanged = false; public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) { mInfo = info; @@ -283,6 +287,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } mDimensionsChanged = false; } + if (mPaddingChanged) { + try { + mEngine.setDisplayPadding(mWallpaper.padding); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to set wallpaper padding", e); + } + mPaddingChanged = false; + } } } @@ -719,6 +731,40 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + public void setDisplayPadding(Rect padding) { + checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); + synchronized (mLock) { + int userId = UserHandle.getCallingUserId(); + WallpaperData wallpaper = mWallpaperMap.get(userId); + if (wallpaper == null) { + throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); + } + if (padding.left < 0 || padding.top < 0 || padding.right < 0 || padding.bottom < 0) { + throw new IllegalArgumentException("padding must be positive: " + padding); + } + + if (!padding.equals(wallpaper.padding)) { + wallpaper.padding.set(padding); + saveSettingsLocked(wallpaper); + if (mCurrentUserId != userId) return; // Don't change the properties now + if (wallpaper.connection != null) { + if (wallpaper.connection.mEngine != null) { + try { + wallpaper.connection.mEngine.setDisplayPadding(padding); + } catch (RemoteException e) { + } + notifyCallbacksLocked(wallpaper); + } else if (wallpaper.connection.mService != null) { + // We've attached to the service but the engine hasn't attached back to us + // yet. This means it will be created with the previous dimensions, so we + // need to update it to the new dimensions once it attaches. + wallpaper.connection.mPaddingChanged = true; + } + } + } + } + } + public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, Bundle outParams) { synchronized (mLock) { @@ -1006,7 +1052,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { try { conn.mService.attach(conn, conn.mToken, WindowManager.LayoutParams.TYPE_WALLPAPER, false, - wallpaper.width, wallpaper.height); + wallpaper.width, wallpaper.height, wallpaper.padding); } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper; clearing", e); if (!wallpaper.wallpaperUpdating) { @@ -1055,6 +1101,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { out.startTag(null, "wp"); out.attribute(null, "width", Integer.toString(wallpaper.width)); out.attribute(null, "height", Integer.toString(wallpaper.height)); + if (wallpaper.padding.left != 0) { + out.attribute(null, "paddingLeft", Integer.toString(wallpaper.padding.left)); + } + if (wallpaper.padding.top != 0) { + out.attribute(null, "paddingTop", Integer.toString(wallpaper.padding.top)); + } + if (wallpaper.padding.right != 0) { + out.attribute(null, "paddingRight", Integer.toString(wallpaper.padding.right)); + } + if (wallpaper.padding.bottom != 0) { + out.attribute(null, "paddingBottom", Integer.toString(wallpaper.padding.bottom)); + } out.attribute(null, "name", wallpaper.name); if (wallpaper.wallpaperComponent != null && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) { @@ -1091,6 +1149,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { } } + private int getAttributeInt(XmlPullParser parser, String name, int defValue) { + String value = parser.getAttributeValue(null, name); + if (value == null) { + return defValue; + } + return Integer.parseInt(value); + } + private void loadSettingsLocked(int userId) { if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); @@ -1121,6 +1187,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width")); wallpaper.height = Integer.parseInt(parser .getAttributeValue(null, "height")); + wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0); + wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0); + wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0); + wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0); wallpaper.name = parser.getAttributeValue(null, "name"); String comp = parser.getAttributeValue(null, "component"); wallpaper.nextWallpaperComponent = comp != null @@ -1167,6 +1237,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { if (!success) { wallpaper.width = -1; wallpaper.height = -1; + wallpaper.padding.set(0, 0, 0, 0); wallpaper.name = ""; } @@ -1330,13 +1401,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { WallpaperData wallpaper = mWallpaperMap.valueAt(i); pw.println(" User " + wallpaper.userId + ":"); pw.print(" mWidth="); - pw.print(wallpaper.width); - pw.print(" mHeight="); - pw.println(wallpaper.height); - pw.print(" mName="); - pw.println(wallpaper.name); - pw.print(" mWallpaperComponent="); - pw.println(wallpaper.wallpaperComponent); + pw.print(wallpaper.width); + pw.print(" mHeight="); + pw.println(wallpaper.height); + pw.print(" mPadding="); pw.println(wallpaper.padding); + pw.print(" mName="); pw.println(wallpaper.name); + pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); if (wallpaper.connection != null) { WallpaperConnection conn = wallpaper.connection; pw.print(" Wallpaper connection "); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index f2703ad28e7a0..d737e7f1563db 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -415,6 +415,18 @@ final class Session extends IWindowSession.Stub mService.wallpaperOffsetsComplete(window); } + public void setWallpaperDisplayOffset(IBinder window, int x, int y) { + synchronized(mService.mWindowMap) { + long ident = Binder.clearCallingIdentity(); + try { + mService.setWindowWallpaperDisplayOffsetLocked( + mService.windowForClientLocked(this, window, true), x, y); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, Bundle extras, boolean sync) { synchronized(mService.mWindowMap) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a1afe29c32ba0..694e7b2202a5d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -572,6 +572,8 @@ public class WindowManagerService extends IWindowManager.Stub float mLastWallpaperY = -1; float mLastWallpaperXStep = -1; float mLastWallpaperYStep = -1; + int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; // This is set when we are waiting for a wallpaper to tell us it is done // changing its scroll position. WindowState mWaitingOnWallpaper; @@ -1892,6 +1894,12 @@ public class WindowManagerService extends IWindowManager.Stub mLastWallpaperY = mWallpaperTarget.mWallpaperY; mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; } + if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; + } + if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; + } } // Start stepping backwards from here, ensuring that our wallpaper windows @@ -2030,6 +2038,9 @@ public class WindowManagerService extends IWindowManager.Stub float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw; int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0; + if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + offset += mLastWallpaperDisplayOffsetX; + } changed = wallpaperWin.mXOffset != offset; if (changed) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " @@ -2046,6 +2057,9 @@ public class WindowManagerService extends IWindowManager.Stub float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; int availh = wallpaperWin.mFrame.bottom-wallpaperWin.mFrame.top-dh; offset = availh > 0 ? -(int)(availh*wpy+.5f) : 0; + if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + offset += mLastWallpaperDisplayOffsetY; + } if (wallpaperWin.mYOffset != offset) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset); @@ -2130,6 +2144,16 @@ public class WindowManagerService extends IWindowManager.Stub } else if (changingTarget.mWallpaperY >= 0) { mLastWallpaperY = changingTarget.mWallpaperY; } + if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; + } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; + } + if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; + } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; + } } int curTokenIndex = mWallpaperTokens.size(); @@ -2826,6 +2850,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void setWindowWallpaperDisplayOffsetLocked(WindowState window, int x, int y) { + if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y) { + window.mWallpaperDisplayOffsetX = x; + window.mWallpaperDisplayOffsetY = y; + updateWallpaperOffsetLocked(window, true); + } + } + public Bundle sendWindowWallpaperCommandLocked(WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) { if (window == mWallpaperTarget || window == mLowerWallpaperTarget @@ -10880,6 +10912,12 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" mLastWallpaperX="); pw.print(mLastWallpaperX); pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); + if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE + || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + pw.print(" mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX); + pw.print(" mLastWallpaperDisplayOffsetY="); + pw.println(mLastWallpaperDisplayOffsetY); + } if (mInputMethodAnimLayerAdjustment != 0 || mWallpaperAnimLayerAdjustment != 0) { pw.print(" mInputMethodAnimLayerAdjustment="); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e74de38ea0c1a..0baa2be0a6fd1 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -247,6 +247,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { float mWallpaperXStep = -1; float mWallpaperYStep = -1; + // If a window showing a wallpaper: a raw pixel offset to forcibly apply + // to its window; if a wallpaper window: not used. + int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Wallpaper windows: pixels offset based on above variables. int mXOffset; int mYOffset; @@ -1584,6 +1589,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep); pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); } + if (mWallpaperDisplayOffsetX != Integer.MIN_VALUE + || mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + pw.print(prefix); pw.print("mWallpaperDisplayOffsetX="); + pw.print(mWallpaperDisplayOffsetX); + pw.print(" mWallpaperDisplayOffsetY="); + pw.println(mWallpaperDisplayOffsetY); + } } String makeInputChannelName() { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 3d4be12469165..1cd2dcdc93d46 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1526,8 +1526,9 @@ class WindowStateAnimator { } void setWallpaperOffset(RectF shownFrame) { - final int left = (int) shownFrame.left; - final int top = (int) shownFrame.top; + final LayoutParams attrs = mWin.getAttrs(); + final int left = ((int) shownFrame.left) - attrs.surfaceInsets.left; + final int top = ((int) shownFrame.top) - attrs.surfaceInsets.top; if (mSurfaceX != left || mSurfaceY != top) { mSurfaceX = left; mSurfaceY = top; diff --git a/tests/WallpaperTest/Android.mk b/tests/WallpaperTest/Android.mk new file mode 100644 index 0000000000000..b4259cd2782b6 --- /dev/null +++ b/tests/WallpaperTest/Android.mk @@ -0,0 +1,15 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_PACKAGE_NAME := WallpaperTest + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + diff --git a/tests/WallpaperTest/AndroidManifest.xml b/tests/WallpaperTest/AndroidManifest.xml new file mode 100644 index 0000000000000..4c914dd5498d0 --- /dev/null +++ b/tests/WallpaperTest/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/WallpaperTest/res/drawable-hdpi/test_wallpaper_thumb.png b/tests/WallpaperTest/res/drawable-hdpi/test_wallpaper_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..df92eb5c1b5572b60934c7d7026f43e8eadb57b4 GIT binary patch literal 34700 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 literal 0 HcmV?d00001 diff --git a/tests/WallpaperTest/res/layout/activity_main.xml b/tests/WallpaperTest/res/layout/activity_main.xml new file mode 100644 index 0000000000000..d968396e71af1 --- /dev/null +++ b/tests/WallpaperTest/res/layout/activity_main.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/WallpaperTest/res/values-v11/styles.xml b/tests/WallpaperTest/res/values-v11/styles.xml new file mode 100644 index 0000000000000..95000b29a9c4a --- /dev/null +++ b/tests/WallpaperTest/res/values-v11/styles.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/tests/WallpaperTest/res/values-v21/styles.xml b/tests/WallpaperTest/res/values-v21/styles.xml new file mode 100644 index 0000000000000..e42d5267a3da9 --- /dev/null +++ b/tests/WallpaperTest/res/values-v21/styles.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/tests/WallpaperTest/res/values/colors.xml b/tests/WallpaperTest/res/values/colors.xml new file mode 100644 index 0000000000000..8c0824984513f --- /dev/null +++ b/tests/WallpaperTest/res/values/colors.xml @@ -0,0 +1,19 @@ + + + + #80000000 + \ No newline at end of file diff --git a/tests/WallpaperTest/res/values/strings.xml b/tests/WallpaperTest/res/values/strings.xml new file mode 100644 index 0000000000000..fd212596f644d --- /dev/null +++ b/tests/WallpaperTest/res/values/strings.xml @@ -0,0 +1,42 @@ + + + + + Wallpaper Test + + Test Wallpaper + Google + + Test wallpaper for use with the wallpaper test app. + + + Dimens: + Width: + Height: + + Wall off: + X: + Y: + + Padding: + Left: + Right: + Top: + Bottom: + + Disp off: + diff --git a/tests/WallpaperTest/res/values/styles.xml b/tests/WallpaperTest/res/values/styles.xml new file mode 100644 index 0000000000000..d2b91d68cb3b7 --- /dev/null +++ b/tests/WallpaperTest/res/values/styles.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/WallpaperTest/res/xml/test_wallpaper.xml b/tests/WallpaperTest/res/xml/test_wallpaper.xml new file mode 100644 index 0000000000000..9f7d714b75cd8 --- /dev/null +++ b/tests/WallpaperTest/res/xml/test_wallpaper.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java b/tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java new file mode 100644 index 0000000000000..7880f67211930 --- /dev/null +++ b/tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java @@ -0,0 +1,176 @@ +/* + * Copyright 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.example.wallpapertest; + +import android.app.Activity; +import android.app.WallpaperManager; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.WindowManager; +import android.widget.TextView; + +public class MainActivity extends Activity { + private static final String TAG = "MainActivity"; + + WallpaperManager mWallpaperManager; + WindowManager mWindowManager; + + TextView mDimenWidthView; + TextView mDimenHeightView; + + TextView mWallOffXView; + TextView mWallOffYView; + + TextView mPaddingLeftView; + TextView mPaddingRightView; + TextView mPaddingTopView; + TextView mPaddingBottomView; + + TextView mDispOffXView; + TextView mDispOffYView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mWallpaperManager = (WallpaperManager)getSystemService(Context.WALLPAPER_SERVICE); + mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE); + + mDimenWidthView = (TextView) findViewById(R.id.dimen_width); + mDimenWidthView.addTextChangedListener(mTextWatcher); + mDimenHeightView = (TextView) findViewById(R.id.dimen_height); + mDimenHeightView.addTextChangedListener(mTextWatcher); + + mWallOffXView = (TextView) findViewById(R.id.walloff_x); + mWallOffXView.addTextChangedListener(mTextWatcher); + mWallOffYView = (TextView) findViewById(R.id.walloff_y); + mWallOffYView.addTextChangedListener(mTextWatcher); + + mPaddingLeftView = (TextView) findViewById(R.id.padding_left); + mPaddingLeftView.addTextChangedListener(mTextWatcher); + mPaddingRightView = (TextView) findViewById(R.id.padding_right); + mPaddingRightView.addTextChangedListener(mTextWatcher); + mPaddingTopView = (TextView) findViewById(R.id.padding_top); + mPaddingTopView.addTextChangedListener(mTextWatcher); + mPaddingBottomView = (TextView) findViewById(R.id.padding_bottom); + mPaddingBottomView.addTextChangedListener(mTextWatcher); + + mDispOffXView = (TextView) findViewById(R.id.dispoff_x); + mDispOffXView.addTextChangedListener(mTextWatcher); + mDispOffYView = (TextView) findViewById(R.id.dispoff_y); + mDispOffYView.addTextChangedListener(mTextWatcher); + + updateDimens(); + updateWallOff(); + updatePadding(); + updateDispOff(); + } + + private int loadPropIntText(TextView view, int baseVal) { + String str = view.getText().toString(); + if (str != null && !TextUtils.isEmpty(str)) { + try { + float fval = Float.parseFloat(str); + return (int)(fval*baseVal); + } catch (NumberFormatException e) { + Log.i(TAG, "Bad number: " + str, e); + } + } + return baseVal; + } + + private float loadFloatText(TextView view) { + String str = view.getText().toString(); + if (str != null && !TextUtils.isEmpty(str)) { + try { + return Float.parseFloat(str); + } catch (NumberFormatException e) { + Log.i(TAG, "Bad number: " + str, e); + } + } + return 0; + } + + private int loadIntText(TextView view) { + String str = view.getText().toString(); + if (str != null && !TextUtils.isEmpty(str)) { + try { + return Integer.parseInt(str); + } catch (NumberFormatException e) { + Log.i(TAG, "Bad number: " + str, e); + } + } + return 0; + } + + public void updateDimens() { + Point minDims = new Point(); + Point maxDims = new Point(); + mWindowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); + mWallpaperManager.suggestDesiredDimensions( + loadPropIntText(mDimenWidthView, maxDims.x), + loadPropIntText(mDimenHeightView, maxDims.y)); + } + + public void updateWallOff() { + IBinder token = getWindow().getDecorView().getWindowToken(); + if (token != null) { + mWallpaperManager.setWallpaperOffsets(token, loadFloatText(mWallOffXView), + loadFloatText(mWallOffYView)); + } + } + + public void updatePadding() { + Rect padding = new Rect(); + padding.left = loadIntText(mPaddingLeftView); + padding.top = loadIntText(mPaddingTopView); + padding.right = loadIntText(mPaddingRightView); + padding.bottom = loadIntText(mPaddingBottomView); + mWallpaperManager.setDisplayPadding(padding); + } + + public void updateDispOff() { + IBinder token = getWindow().getDecorView().getWindowToken(); + if (token != null) { + mWallpaperManager.setDisplayOffset(token, loadIntText(mDispOffXView), + loadIntText(mDispOffYView)); + } + } + + final TextWatcher mTextWatcher = new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + updateDimens(); + updateWallOff(); + updatePadding(); + updateDispOff(); + } + + @Override public void afterTextChanged(Editable s) { + } + }; +} diff --git a/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java b/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java new file mode 100644 index 0000000000000..95db6d100b79e --- /dev/null +++ b/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java @@ -0,0 +1,251 @@ +/* + * 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.example.wallpapertest; + +import android.service.wallpaper.WallpaperService; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.Paint; +import android.graphics.Color; +import android.graphics.RectF; +import android.text.TextPaint; +import android.view.SurfaceHolder; +import android.content.res.XmlResourceParser; + +import android.os.Handler; +import android.util.Log; + +import android.view.WindowInsets; + +public class TestWallpaper extends WallpaperService { + private static final String LOG_TAG = "PolarClock"; + + private final Handler mHandler = new Handler(); + + @Override + public void onCreate() { + super.onCreate(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + public Engine onCreateEngine() { + return new ClockEngine(); + } + + class ClockEngine extends Engine { + private static final int OUTER_COLOR = 0xffff0000; + private static final int INNER_COLOR = 0xff000080; + private static final int STABLE_COLOR = 0xa000ff00; + private static final int TEXT_COLOR = 0xa0ffffff; + + private final Paint.FontMetrics mTextMetrics = new Paint.FontMetrics(); + + private int mPadding; + + private final Rect mMainInsets = new Rect(); + private final Rect mStableInsets = new Rect(); + private boolean mRound = false; + + private int mDesiredWidth; + private int mDesiredHeight; + + private float mOffsetX; + private float mOffsetY; + private float mOffsetXStep; + private float mOffsetYStep; + private int mOffsetXPixels; + private int mOffsetYPixels; + + private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + + private final Runnable mDrawClock = new Runnable() { + public void run() { + drawFrame(); + } + }; + private boolean mVisible; + + ClockEngine() { + } + + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + super.onCreate(surfaceHolder); + + mDesiredWidth = getDesiredMinimumWidth(); + mDesiredHeight = getDesiredMinimumHeight(); + + Paint paint = mFillPaint; + paint.setStyle(Paint.Style.FILL); + + paint = mStrokePaint; + paint.setStrokeWidth(3); + paint.setStrokeCap(Paint.Cap.ROUND); + paint.setStyle(Paint.Style.STROKE); + + TextPaint tpaint = mTextPaint; + tpaint.density = getResources().getDisplayMetrics().density; + tpaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale); + tpaint.setColor(TEXT_COLOR); + tpaint.setTextSize(18 * getResources().getDisplayMetrics().scaledDensity); + tpaint.setShadowLayer(4 * getResources().getDisplayMetrics().density, 0, 0, 0xff000000); + + mTextPaint.getFontMetrics(mTextMetrics); + + mPadding = (int)(16 * getResources().getDisplayMetrics().density); + + if (isPreview()) { + mOffsetX = 0.5f; + mOffsetY = 0.5f; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + mHandler.removeCallbacks(mDrawClock); + } + + @Override + public void onVisibilityChanged(boolean visible) { + mVisible = visible; + if (!visible) { + mHandler.removeCallbacks(mDrawClock); + } + drawFrame(); + } + + @Override + public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { + super.onSurfaceChanged(holder, format, width, height); + drawFrame(); + } + + @Override + public void onSurfaceCreated(SurfaceHolder holder) { + super.onSurfaceCreated(holder); + } + + @Override + public void onSurfaceDestroyed(SurfaceHolder holder) { + super.onSurfaceDestroyed(holder); + mVisible = false; + mHandler.removeCallbacks(mDrawClock); + } + + @Override + public void onApplyWindowInsets(WindowInsets insets) { + super.onApplyWindowInsets(insets); + mMainInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()); + mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(), + insets.getStableInsetRight(), insets.getStableInsetBottom()); + mRound = insets.isRound(); + drawFrame(); + } + + @Override + public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) { + super.onDesiredSizeChanged(desiredWidth, desiredHeight); + mDesiredWidth = desiredWidth; + mDesiredHeight = desiredHeight; + drawFrame(); + } + + @Override + public void onOffsetsChanged(float xOffset, float yOffset, + float xStep, float yStep, int xPixels, int yPixels) { + super.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels); + + if (isPreview()) return; + + mOffsetX = xOffset; + mOffsetY = yOffset; + mOffsetXStep = xStep; + mOffsetYStep = yStep; + mOffsetXPixels = xPixels; + mOffsetYPixels = yPixels; + + drawFrame(); + } + + void drawFrame() { + final SurfaceHolder holder = getSurfaceHolder(); + final Rect frame = holder.getSurfaceFrame(); + final int width = frame.width(); + final int height = frame.height(); + + Canvas c = null; + try { + c = holder.lockCanvas(); + if (c != null) { + final Paint paint = mFillPaint; + + paint.setColor(OUTER_COLOR); + c.drawRect(0, 0, width, height, paint); + + paint.setColor(INNER_COLOR); + c.drawRect(0+mMainInsets.left, 0+mMainInsets.top, + width-mMainInsets.right, height-mMainInsets.bottom, paint); + + mStrokePaint.setColor(STABLE_COLOR); + c.drawRect(0 + mStableInsets.left, 0 + mStableInsets.top, + width - mStableInsets.right, height - mStableInsets.bottom, + mStrokePaint); + + final int ascdesc = (int)(-mTextMetrics.ascent + mTextMetrics.descent); + final int linegap = (int)(-mTextMetrics.ascent + mTextMetrics.descent + + mTextMetrics.leading); + + int x = mStableInsets.left + mPadding; + int y = height - mStableInsets.bottom - mPadding - ascdesc; + c.drawText("Surface Size: " + width + " x " + height, + x, y, mTextPaint); + y -= linegap; + c.drawText("Desired Size: " + mDesiredWidth + " x " + mDesiredHeight, + x, y, mTextPaint); + y -= linegap; + c.drawText("Cur Offset Raw: " + mOffsetX + ", " + mOffsetY, + x, y, mTextPaint); + y -= linegap; + c.drawText("Cur Offset Step: " + mOffsetXStep + ", " + mOffsetYStep, + x, y, mTextPaint); + y -= linegap; + c.drawText("Cur Offset Pixels: " + mOffsetXPixels + ", " + mOffsetYPixels, + x, y, mTextPaint); + y -= linegap; + c.drawText("Stable Insets: (" + mStableInsets.left + ", " + mStableInsets.top + + ") - (" + mStableInsets.right + ", " + mStableInsets.bottom + ")", + x, y, mTextPaint); + y -= linegap; + c.drawText("System Insets: (" + mMainInsets.left + ", " + mMainInsets.top + + ") - (" + mMainInsets.right + ", " + mMainInsets.bottom + ")", + x, y, mTextPaint); + + } + } finally { + if (c != null) holder.unlockCanvasAndPost(c); + } + } + } +}