/* * Copyright (C) 2009 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.service.wallpaper; import static android.app.WallpaperManager.COMMAND_FREEZE; import static android.app.WallpaperManager.COMMAND_UNFREEZE; import static android.graphics.Matrix.MSCALE_X; import static android.graphics.Matrix.MSCALE_Y; import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.Service; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.BLASTBufferQueue; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.util.ArraySet; import android.util.Log; import android.util.MergedConfiguration; import android.view.Display; import android.view.DisplayCutout; import android.view.Gravity; import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.InsetsVisibilities; import android.view.MotionEvent; import android.view.PixelCopy; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.ClientWindowFrames; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; import com.android.internal.view.BaseSurfaceHolder; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; /** * A wallpaper service is responsible for showing a live wallpaper behind * applications that would like to sit on top of it. This service object * itself does very little -- its only purpose is to generate instances of * {@link Engine} as needed. Implementing a wallpaper thus * involves subclassing from this, subclassing an Engine implementation, * and implementing {@link #onCreateEngine()} to return a new instance of * your engine. */ public abstract class WallpaperService extends Service { /** * The {@link Intent} that must be declared as handled by the service. * To be supported, the service must also require the * {@link android.Manifest.permission#BIND_WALLPAPER} permission so * that other applications can not abuse it. */ @SdkConstant(SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService"; /** * Name under which a WallpaperService component publishes information * about itself. This meta-data must reference an XML resource containing * a <{@link android.R.styleable#Wallpaper wallpaper}> * tag. */ public static final String SERVICE_META_DATA = "android.service.wallpaper"; static final String TAG = "WallpaperService"; static final boolean DEBUG = false; static final float MIN_PAGE_ALLOWED_MARGIN = .05f; private static final int MIN_BITMAP_SCREENSHOT_WIDTH = 64; private static final long DEFAULT_UPDATE_SCREENSHOT_DURATION = 60 * 1000; //Once per minute private static final @NonNull RectF LOCAL_COLOR_BOUNDS = new RectF(0, 0, 1, 1); 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 DO_IN_AMBIENT_MODE = 50; private static final int MSG_UPDATE_SURFACE = 10000; private static final int MSG_VISIBILITY_CHANGED = 10010; private static final int MSG_WALLPAPER_OFFSETS = 10020; private static final int MSG_WALLPAPER_COMMAND = 10025; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static final int MSG_WINDOW_RESIZED = 10030; private static final int MSG_WINDOW_MOVED = 10035; private static final int MSG_TOUCH_EVENT = 10040; private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050; private static final int MSG_ZOOM = 10100; private static final int MSG_SCALE_PREVIEW = 10110; private static final int MSG_REPORT_SHOWN = 10150; private static final List PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY); private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000; private static final boolean ENABLE_WALLPAPER_DIMMING = SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true); private final ArrayList mActiveEngines = new ArrayList(); static final class WallpaperCommand { String action; int x; int y; int z; Bundle extras; boolean sync; } /** * The actual implementation of a wallpaper. A wallpaper service may * have multiple instances running (for example as a real wallpaper * and as a preview), each of which is represented by its own Engine * instance. You must implement {@link WallpaperService#onCreateEngine()} * to return your concrete Engine implementation. */ public class Engine { IWallpaperEngineWrapper mIWallpaperEngine; final ArraySet mLocalColorAreas = new ArraySet<>(4); final ArraySet mLocalColorsToAdd = new ArraySet<>(4); // 2D matrix [x][y] to represent a page of a portion of a window EngineWindowPage[] mWindowPages = new EngineWindowPage[1]; Bitmap mLastScreenshot; int mLastWindowPage = -1; private boolean mResetWindowPages; // Copies from mIWallpaperEngine. HandlerCaller mCaller; IWallpaperConnection mConnection; IBinder mWindowToken; boolean mInitializing = true; boolean mVisible; boolean mReportedVisible; boolean mDestroyed; // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once // mScreenshotSurfaceControl isn't null. When this happens, then Engine is notified through // doVisibilityChanged that main wallpaper surface is no longer visible and the wallpaper // host receives onVisibilityChanged(false) callback. private boolean mFrozenRequested = false; // Current window state. boolean mCreated; boolean mSurfaceCreated; boolean mIsCreating; boolean mDrawingAllowed; boolean mOffsetsChanged; boolean mFixedSizeAllowed; boolean mShouldDim; int mWidth; int mHeight; int mFormat; int mType; int mCurWidth; int mCurHeight; float mZoom = 0f; int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; Rect mPreviewSurfacePosition; final ClientWindowFrames mWinFrames = new ClientWindowFrames(); final Rect mDispatchedContentInsets = new Rect(); final Rect mDispatchedStableInsets = new Rect(); DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; final InsetsState mInsetsState = new InsetsState(); final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); private final Point mSurfaceSize = new Point(); private final Point mLastSurfaceSize = new Point(); private final Matrix mTmpMatrix = new Matrix(); private final float[] mTmpValues = new float[9]; final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); IWindowSession mSession; final Object mLock = new Object(); boolean mOffsetMessageEnqueued; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) float mPendingXOffset; float mPendingYOffset; float mPendingXOffsetStep; float mPendingYOffsetStep; boolean mPendingSync; MotionEvent mPendingMove; boolean mIsInAmbientMode; // Needed for throttling onComputeColors. private long mLastColorInvalidation; private final Runnable mNotifyColorsChanged = this::notifyColorsChanged; private final Supplier mClockFunction; private final Handler mHandler; private Display mDisplay; private Context mDisplayContext; private int mDisplayState; private float mWallpaperDimAmount = 0.05f; SurfaceControl mSurfaceControl = new SurfaceControl(); SurfaceControl mBbqSurfaceControl; BLASTBufferQueue mBlastBufferQueue; private SurfaceControl mScreenshotSurfaceControl; private Point mScreenshotSize = new Point(); final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { mRequestedFormat = PixelFormat.RGBX_8888; } @Override public boolean onAllowLockCanvas() { return mDrawingAllowed; } @Override public void onRelayoutContainer() { Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE); mCaller.sendMessage(msg); } @Override public void onUpdateSurface() { Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE); mCaller.sendMessage(msg); } public boolean isCreating() { return mIsCreating; } @Override public void setFixedSize(int width, int height) { if (!mFixedSizeAllowed) { // Regular apps can't do this. It can only work for // certain designs of window animations, so you can't // rely on it. throw new UnsupportedOperationException( "Wallpapers currently only support sizing from layout"); } super.setFixedSize(width, height); } public void setKeepScreenOn(boolean screenOn) { throw new UnsupportedOperationException( "Wallpapers do not support keep screen on"); } private void prepareToDraw() { if (mDisplayState == Display.STATE_DOZE || mDisplayState == Display.STATE_DOZE_SUSPEND) { try { mSession.pokeDrawLock(mWindow); } catch (RemoteException e) { // System server died, can be ignored. } } } @Override public Canvas lockCanvas() { prepareToDraw(); return super.lockCanvas(); } @Override public Canvas lockCanvas(Rect dirty) { prepareToDraw(); return super.lockCanvas(dirty); } @Override public Canvas lockHardwareCanvas() { prepareToDraw(); return super.lockHardwareCanvas(); } }; final class WallpaperInputEventReceiver extends InputEventReceiver { public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } @Override public void onInputEvent(InputEvent event) { boolean handled = false; try { if (event instanceof MotionEvent && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event); dispatchPointer(dup); handled = true; } } finally { finishInputEvent(event, handled); } } } WallpaperInputEventReceiver mInputEventReceiver; final BaseIWindow mWindow = new BaseIWindow() { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId) { Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED, reportDraw ? 1 : 0); mCaller.sendMessage(msg); } @Override public void moved(int newX, int newY) { Message msg = mCaller.obtainMessageII(MSG_WINDOW_MOVED, newX, newY); mCaller.sendMessage(msg); } @Override public void dispatchAppVisibility(boolean visible) { // We don't do this in preview mode; we'll let the preview // activity tell us when to run. if (!mIWallpaperEngine.mIsPreview) { Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED, visible ? 1 : 0); mCaller.sendMessage(msg); } } @Override public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, float zoom, boolean sync) { synchronized (mLock) { if (DEBUG) Log.v(TAG, "Dispatch wallpaper offsets: " + x + ", " + y); mPendingXOffset = x; mPendingYOffset = y; mPendingXOffsetStep = xStep; mPendingYOffsetStep = yStep; if (sync) { mPendingSync = true; } if (!mOffsetMessageEnqueued) { mOffsetMessageEnqueued = true; Message msg = mCaller.obtainMessage(MSG_WALLPAPER_OFFSETS); mCaller.sendMessage(msg); } Message msg = mCaller.obtainMessageI(MSG_ZOOM, Float.floatToIntBits(zoom)); mCaller.sendMessage(msg); } } @Override public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { synchronized (mLock) { if (DEBUG) Log.v(TAG, "Dispatch wallpaper command: " + x + ", " + y); WallpaperCommand cmd = new WallpaperCommand(); cmd.action = action; cmd.x = x; cmd.y = y; cmd.z = z; cmd.extras = extras; cmd.sync = sync; Message msg = mCaller.obtainMessage(MSG_WALLPAPER_COMMAND); msg.obj = cmd; mCaller.sendMessage(msg); } } }; /** * Default constructor */ public Engine() { this(SystemClock::elapsedRealtime, Handler.getMain()); } /** * Constructor used for test purposes. * * @param clockFunction Supplies current times in millis. * @param handler Used for posting/deferring asynchronous calls. * @hide */ @VisibleForTesting public Engine(Supplier clockFunction, Handler handler) { mClockFunction = clockFunction; mHandler = handler; } /** * Provides access to the surface in which this wallpaper is drawn. */ public SurfaceHolder getSurfaceHolder() { return mSurfaceHolder; } /** * Convenience for {@link WallpaperManager#getDesiredMinimumWidth() * WallpaperManager.getDesiredMinimumWidth()}, returning the width * that the system would like this wallpaper to run in. */ public int getDesiredMinimumWidth() { return mIWallpaperEngine.mReqWidth; } /** * Convenience for {@link WallpaperManager#getDesiredMinimumHeight() * WallpaperManager.getDesiredMinimumHeight()}, returning the height * that the system would like this wallpaper to run in. */ public int getDesiredMinimumHeight() { return mIWallpaperEngine.mReqHeight; } /** * Return whether the wallpaper is currently visible to the user, * this is the last value supplied to * {@link #onVisibilityChanged(boolean)}. */ public boolean isVisible() { return mReportedVisible; } /** * Return whether the wallpaper is capable of extracting local colors in a rectangle area, * Must implement without calling super: * {@link #addLocalColorsAreas(List)} * {@link #removeLocalColorsAreas(List)} * When local colors change, call {@link #notifyLocalColorsChanged(List, List)} * See {@link com.android.systemui.ImageWallpaper} for an example * @hide */ public boolean supportsLocalColorExtraction() { return false; } /** * Returns true if this engine is running in preview mode -- that is, * it is being shown to the user before they select it as the actual * wallpaper. */ public boolean isPreview() { return mIWallpaperEngine.mIsPreview; } /** * Returns true if this engine is running in ambient mode -- that is, * it is being shown in low power mode, on always on display. * @hide */ @SystemApi public boolean isInAmbientMode() { return mIsInAmbientMode; } /** * This will be called when the wallpaper is first started. If true is returned, the system * will zoom in the wallpaper by default and zoom it out as the user interacts, * to create depth. Otherwise, zoom will have to be handled manually * in {@link #onZoomChanged(float)}. * * @hide */ public boolean shouldZoomOutWallpaper() { return false; } /** * This will be called in the end of {@link #updateSurface(boolean, boolean, boolean)}. * If true is returned, the engine will not report shown until rendering finished is * reported. Otherwise, the engine will report shown immediately right after redraw phase * in {@link #updateSurface(boolean, boolean, boolean)}. * * @hide */ public boolean shouldWaitForEngineShown() { return false; } /** * Reports the rendering is finished, stops waiting, then invokes * {@link IWallpaperEngineWrapper#reportShown()}. * * @hide */ public void reportEngineShown(boolean waitForEngineShown) { if (mIWallpaperEngine.mShownReported) return; if (!waitForEngineShown) { Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN); mCaller.removeMessages(MSG_REPORT_SHOWN); mCaller.sendMessage(message); } else { // if we are already waiting, no need to reset the timeout. if (!mCaller.hasMessages(MSG_REPORT_SHOWN)) { Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN); mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5)); } } } /** * Control whether this wallpaper will receive raw touch events * from the window manager as the user interacts with the window * that is currently displaying the wallpaper. By default they * are turned off. If enabled, the events will be received in * {@link #onTouchEvent(MotionEvent)}. */ public void setTouchEventsEnabled(boolean enabled) { mWindowFlags = enabled ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); if (mCreated) { updateSurface(false, false, false); } } /** * Control whether this wallpaper will receive notifications when the wallpaper * has been scrolled. By default, wallpapers will receive notifications, although * the default static image wallpapers do not. It is a performance optimization to * set this to false. * * @param enabled whether the wallpaper wants to receive offset notifications */ public void setOffsetNotificationsEnabled(boolean enabled) { mWindowPrivateFlags = enabled ? (mWindowPrivateFlags | WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) : (mWindowPrivateFlags & ~WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS); if (mCreated) { updateSurface(false, false, false); } } /** {@hide} */ @UnsupportedAppUsage public void setFixedSizeAllowed(boolean allowed) { mFixedSizeAllowed = allowed; } /** * Returns the current scale of the surface * @hide */ @VisibleForTesting public float getZoom() { return mZoom; } /** * Called once to initialize the engine. After returning, the * engine's surface will be created by the framework. */ 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 * valid. */ public void onDestroy() { } /** * Called to inform you of the wallpaper becoming visible or * hidden. It is very important that a wallpaper only use * CPU while it is visible.. */ 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 * events you receive here are driven by the actual application the * user is interacting with, so if it is slow you will get fewer * move events. */ public void onTouchEvent(MotionEvent event) { } /** * Called to inform you of the wallpaper's offsets changing * within its contain, corresponding to the container's * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float) * WallpaperManager.setWallpaperOffsets()}. */ public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) { } /** * Process a command that was sent to the wallpaper with * {@link WallpaperManager#sendWallpaperCommand}. * The default implementation does nothing, and always returns null * as the result. * * @param action The name of the command to perform. This tells you * what to do and how to interpret the rest of the arguments. * @param x Generic integer parameter. * @param y Generic integer parameter. * @param z Generic integer parameter. * @param extras Any additional parameters. * @param resultRequested If true, the caller is requesting that * a result, appropriate for the command, be returned back. * @return If returning a result, create a Bundle and place the * result data in to it. Otherwise return null. */ public Bundle onCommand(String action, int x, int y, int z, Bundle extras, boolean resultRequested) { return null; } /** * Called when the device enters or exits ambient mode. * * @param inAmbientMode {@code true} if in ambient mode. * @param animationDuration How long the transition animation to change the ambient state * should run, in milliseconds. If 0 is passed as the argument * here, the state should be switched immediately. * * @see #isInAmbientMode() * @see WallpaperInfo#supportsAmbientMode() * @hide */ @SystemApi public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) { } /** * 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()}. */ public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { } /** * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded * SurfaceHolder.Callback.surfaceRedrawNeeded()}. */ public void onSurfaceRedrawNeeded(SurfaceHolder holder) { } /** * Convenience for {@link SurfaceHolder.Callback#surfaceCreated * SurfaceHolder.Callback.surfaceCreated()}. */ public void onSurfaceCreated(SurfaceHolder holder) { } /** * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed * SurfaceHolder.Callback.surfaceDestroyed()}. */ public void onSurfaceDestroyed(SurfaceHolder holder) { } /** * Called when the zoom level of the wallpaper changed. * This method will be called with the initial zoom level when the surface is created. * * @param zoom the zoom level, between 0 indicating fully zoomed in and 1 indicating fully * zoomed out. */ public void onZoomChanged(@FloatRange(from = 0f, to = 1f) float zoom) { } /** * Notifies the engine that wallpaper colors changed significantly. * This will trigger a {@link #onComputeColors()} call. */ public void notifyColorsChanged() { final long now = mClockFunction.get(); if (now - mLastColorInvalidation < NOTIFY_COLORS_RATE_LIMIT_MS) { Log.w(TAG, "This call has been deferred. You should only call " + "notifyColorsChanged() once every " + (NOTIFY_COLORS_RATE_LIMIT_MS / 1000f) + " seconds."); if (!mHandler.hasCallbacks(mNotifyColorsChanged)) { mHandler.postDelayed(mNotifyColorsChanged, NOTIFY_COLORS_RATE_LIMIT_MS); } return; } mLastColorInvalidation = now; mHandler.removeCallbacks(mNotifyColorsChanged); try { final WallpaperColors newColors = onComputeColors(); if (mConnection != null) { mConnection.onWallpaperColorsChanged(newColors, mDisplay.getDisplayId()); } else { Log.w(TAG, "Can't notify system because wallpaper connection " + "was not established."); } mResetWindowPages = true; processLocalColors(mPendingXOffset, mPendingXOffsetStep); } catch (RemoteException e) { Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e); } } /** * Called by the system when it needs to know what colors the wallpaper is using. * You might return null if no color information is available at the moment. * In that case you might want to call {@link #notifyColorsChanged()} when * color information becomes available. *

* The simplest way of creating a {@link android.app.WallpaperColors} object is by using * {@link android.app.WallpaperColors#fromBitmap(Bitmap)} or * {@link android.app.WallpaperColors#fromDrawable(Drawable)}, but you can also specify * your main colors by constructing a {@link android.app.WallpaperColors} object manually. * * @return Wallpaper colors. */ public @Nullable WallpaperColors onComputeColors() { return null; } /** * Send the changed local color areas for the connection * @param regions * @param colors * @hide */ public void notifyLocalColorsChanged(@NonNull List regions, @NonNull List colors) throws RuntimeException { for (int i = 0; i < regions.size() && i < colors.size(); i++) { WallpaperColors color = colors.get(i); RectF area = regions.get(i); if (color == null || area == null) { if (DEBUG) { Log.e(TAG, "notifyLocalColorsChanged null values. color: " + color + " area " + area); } continue; } try { mConnection.onLocalWallpaperColorsChanged( area, color, mDisplayContext.getDisplayId() ); } catch (RemoteException e) { throw new RuntimeException(e); } } WallpaperColors primaryColors = mIWallpaperEngine.mWallpaperManager .getWallpaperColors(WallpaperManager.FLAG_SYSTEM); setPrimaryWallpaperColors(primaryColors); } private void setPrimaryWallpaperColors(WallpaperColors colors) { if (colors == null) { return; } int colorHints = colors.getColorHints(); boolean shouldDim = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 && (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0); if (shouldDim != mShouldDim) { mShouldDim = shouldDim; updateSurfaceDimming(); updateSurface(false, false, true); } } private void updateSurfaceDimming() { if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) { return; } // TODO: apply the dimming to preview as well once surface transparency works in // preview mode. if (!isPreview() && mShouldDim) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); new SurfaceControl.Transaction() .setAlpha(mBbqSurfaceControl, 1 - mWallpaperDimAmount) .apply(); } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); new SurfaceControl.Transaction() .setAlpha(mBbqSurfaceControl, 1.0f) .apply(); } } /** * Sets internal engine state. Only for testing. * @param created {@code true} or {@code false}. * @hide */ @VisibleForTesting public void setCreated(boolean created) { mCreated = created; } protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { out.print(prefix); out.print("mInitializing="); out.print(mInitializing); out.print(" mDestroyed="); out.println(mDestroyed); out.print(prefix); out.print("mVisible="); out.print(mVisible); out.print(" mReportedVisible="); out.println(mReportedVisible); out.print(prefix); out.print("mDisplay="); out.println(mDisplay); out.print(prefix); out.print("mCreated="); out.print(mCreated); out.print(" mSurfaceCreated="); out.print(mSurfaceCreated); out.print(" mIsCreating="); out.print(mIsCreating); out.print(" mDrawingAllowed="); out.println(mDrawingAllowed); out.print(prefix); out.print("mWidth="); out.print(mWidth); out.print(" mCurWidth="); out.print(mCurWidth); out.print(" mHeight="); out.print(mHeight); out.print(" mCurHeight="); out.println(mCurHeight); out.print(prefix); out.print("mType="); out.print(mType); out.print(" mWindowFlags="); out.print(mWindowFlags); out.print(" mCurWindowFlags="); out.println(mCurWindowFlags); out.print(prefix); out.print("mWindowPrivateFlags="); out.print(mWindowPrivateFlags); out.print(" mCurWindowPrivateFlags="); out.println(mCurWindowPrivateFlags); out.print(prefix); out.println("mWinFrames="); out.println(mWinFrames); out.print(prefix); out.print("mConfiguration="); out.println(mMergedConfiguration.getMergedConfiguration()); out.print(prefix); out.print("mLayout="); out.println(mLayout); out.print(prefix); out.print("mZoom="); out.println(mZoom); out.print(prefix); out.print("mPreviewSurfacePosition="); out.println(mPreviewSurfacePosition); synchronized (mLock) { out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset); out.print(" mPendingXOffset="); out.println(mPendingXOffset); out.print(prefix); out.print("mPendingXOffsetStep="); out.print(mPendingXOffsetStep); out.print(" mPendingXOffsetStep="); out.println(mPendingXOffsetStep); out.print(prefix); out.print("mOffsetMessageEnqueued="); out.print(mOffsetMessageEnqueued); out.print(" mPendingSync="); out.println(mPendingSync); if (mPendingMove != null) { out.print(prefix); out.print("mPendingMove="); out.println(mPendingMove); } } } /** * Set the wallpaper zoom to the given value. This value will be ignored when in ambient * mode (and zoom will be reset to 0). * @hide * @param zoom between 0 and 1 (inclusive) indicating fully zoomed in to fully zoomed out * respectively. */ @VisibleForTesting public void setZoom(float zoom) { if (DEBUG) { Log.v(TAG, "set zoom received: " + zoom); } boolean updated = false; synchronized (mLock) { if (DEBUG) { Log.v(TAG, "mZoom: " + mZoom + " updated: " + zoom); } if (mIsInAmbientMode) { mZoom = 0; } if (Float.compare(zoom, mZoom) != 0) { mZoom = zoom; updated = true; } } if (DEBUG) Log.v(TAG, "setZoom updated? " + updated); if (updated && !mDestroyed) { onZoomChanged(mZoom); } } private void dispatchPointer(MotionEvent event) { if (event.isTouchEvent()) { synchronized (mLock) { if (event.getAction() == MotionEvent.ACTION_MOVE) { mPendingMove = event; } else { mPendingMove = null; } } Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event); mCaller.sendMessage(msg); } else { event.recycle(); } } void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) { if (mDestroyed) { Log.w(TAG, "Ignoring updateSurface due to destroyed"); return; } 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; if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged || typeChanged || flagsChanged || redrawNeeded || !mIWallpaperEngine.mShownReported) { if (DEBUG) Log.v(TAG, "Changes: creating=" + creating + " format=" + formatChanged + " size=" + sizeChanged); try { mWidth = myWidth; mHeight = myHeight; mFormat = mSurfaceHolder.getRequestedFormat(); mType = mSurfaceHolder.getRequestedType(); mLayout.x = 0; mLayout.y = 0; mLayout.width = myWidth; mLayout.height = myHeight; mLayout.format = mFormat; mCurWindowFlags = mWindowFlags; mLayout.flags = mWindowFlags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mCurWindowPrivateFlags = mWindowPrivateFlags; mLayout.privateFlags = mWindowPrivateFlags; mLayout.memoryType = mType; mLayout.token = mWindowToken; if (!mCreated) { // Retrieve watch round info TypedArray windowStyle = obtainStyledAttributes( com.android.internal.R.styleable.Window); windowStyle.recycle(); // Add window mLayout.type = mIWallpaperEngine.mWindowType; mLayout.gravity = Gravity.START|Gravity.TOP; mLayout.setFitInsetsTypes(0 /* types */); mLayout.setTitle(WallpaperService.this.getClass().getName()); mLayout.windowAnimations = com.android.internal.R.style.Animation_Wallpaper; InputChannel inputChannel = new InputChannel(); if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE, mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel, mInsetsState, mTempControls) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } mSession.setShouldZoomOutWallpaper(mWindow, shouldZoomOutWallpaper()); mCreated = true; mInputEventReceiver = new WallpaperInputEventReceiver( inputChannel, Looper.myLooper()); } 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, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSurfaceSize); if (mSurfaceControl.isValid()) { if (mBbqSurfaceControl == null) { mBbqSurfaceControl = new SurfaceControl.Builder() .setName("Wallpaper BBQ wrapper") .setHidden(false) // TODO(b/192291754) .setMetadata(METADATA_WINDOW_TYPE, TYPE_WALLPAPER) .setBLASTLayer() .setParent(mSurfaceControl) .setCallsite("Wallpaper#relayout") .build(); updateSurfaceDimming(); } // Propagate transform hint from WM so we can use the right hint for the // first frame. mBbqSurfaceControl.setTransformHint(mSurfaceControl.getTransformHint()); Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, mSurfaceSize.y, mFormat); // If blastSurface == null that means it hasn't changed since the last // time we called. In this situation, avoid calling transferFrom as we // would then inc the generation ID and cause EGL resources to be recreated. if (blastSurface != null) { mSurfaceHolder.mSurface.transferFrom(blastSurface); } } if (!mLastSurfaceSize.equals(mSurfaceSize)) { mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y); } if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface + ", frame=" + mWinFrames); int w = mWinFrames.frame.width(); int h = mWinFrames.frame.height(); final DisplayCutout rawCutout = mInsetsState.getDisplayCutout(); final Configuration config = getResources().getConfiguration(); final Rect visibleFrame = new Rect(mWinFrames.frame); visibleFrame.intersect(mInsetsState.getDisplayFrame()); WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame, null /* ignoringVisibilityState */, config.isScreenRound(), false /* alwaysConsumeSystemBars */, mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type, config.windowConfiguration.getWindowingMode(), null /* typeSideMap */); if (!fixedSize) { final Rect padding = mIWallpaperEngine.mDisplayPadding; w += padding.left + padding.right; h += padding.top + padding.bottom; windowInsets = windowInsets.insetUnchecked( -padding.left, -padding.top, -padding.right, -padding.bottom); } else { w = myWidth; h = myHeight; } if (mCurWidth != w) { sizeChanged = true; mCurWidth = w; } if (mCurHeight != h) { sizeChanged = true; mCurHeight = h; } if (DEBUG) { Log.v(TAG, "Wallpaper size has changed: (" + mCurWidth + ", " + mCurHeight); } final Rect contentInsets = windowInsets.getSystemWindowInsets().toRect(); final Rect stableInsets = windowInsets.getStableInsets().toRect(); final DisplayCutout displayCutout = windowInsets.getDisplayCutout() != null ? windowInsets.getDisplayCutout() : rawCutout; insetsChanged |= !mDispatchedContentInsets.equals(contentInsets); insetsChanged |= !mDispatchedStableInsets.equals(stableInsets); insetsChanged |= !mDispatchedDisplayCutout.equals(displayCutout); mSurfaceHolder.setSurfaceFrameSize(w, h); mSurfaceHolder.mSurfaceLock.unlock(); if (!mSurfaceHolder.mSurface.isValid()) { reportSurfaceDestroyed(); if (DEBUG) Log.v(TAG, "Layout: Surface destroyed"); return; } boolean didSurface = false; try { mSurfaceHolder.ungetCallbacks(); if (surfaceCreating) { mIsCreating = true; didSurface = true; if (DEBUG) Log.v(TAG, "onSurfaceCreated(" + mSurfaceHolder + "): " + this); onSurfaceCreated(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceCreated(mSurfaceHolder); } } } redrawNeeded |= creating || (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0; if (forceReport || creating || surfaceCreating || formatChanged || sizeChanged) { if (DEBUG) { RuntimeException e = new RuntimeException(); e.fillInStackTrace(); Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating + " formatChanged=" + formatChanged + " sizeChanged=" + sizeChanged, e); } if (DEBUG) Log.v(TAG, "onSurfaceChanged(" + mSurfaceHolder + ", " + mFormat + ", " + mCurWidth + ", " + mCurHeight + "): " + this); didSurface = true; onSurfaceChanged(mSurfaceHolder, mFormat, mCurWidth, mCurHeight); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceChanged(mSurfaceHolder, mFormat, mCurWidth, mCurHeight); } } } if (insetsChanged) { mDispatchedContentInsets.set(contentInsets); mDispatchedStableInsets.set(stableInsets); mDispatchedDisplayCutout = displayCutout; if (DEBUG) { Log.v(TAG, "dispatching insets=" + windowInsets); } onApplyWindowInsets(windowInsets); } if (redrawNeeded) { onSurfaceRedrawNeeded(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder); } } } } if (didSurface && !mReportedVisible) { // This wallpaper is currently invisible, but its // surface has changed. At this point let's tell it // again that it is invisible in case the report about // the surface caused it to start running. We really // don't want wallpapers running when not visible. if (mIsCreating) { // Some wallpapers will ignore this call if they // had previously been told they were invisble, // so if we are creating a new surface then toggle // the state to get them to notice. if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: " + this); onVisibilityChanged(true); } if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: " + this); onVisibilityChanged(false); } } finally { mIsCreating = false; mSurfaceCreated = true; if (redrawNeeded) { resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */); processLocalColors(mPendingXOffset, mPendingXOffsetStep); } reposition(); reportEngineShown(shouldWaitForEngineShown()); } } catch (RemoteException ex) { } if (DEBUG) Log.v( TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y + " w=" + mLayout.width + " h=" + mLayout.height); } } private void scalePreview(Rect position) { if (isPreview() && mPreviewSurfacePosition == null && position != null || mPreviewSurfacePosition != null && !mPreviewSurfacePosition.equals(position)) { mPreviewSurfacePosition = position; if (mSurfaceControl.isValid()) { reposition(); } else { updateSurface(false, false, false); } } } private void reposition() { if (mPreviewSurfacePosition == null) { return; } if (DEBUG) { Log.i(TAG, "reposition: rect: " + mPreviewSurfacePosition); } mTmpMatrix.setTranslate(mPreviewSurfacePosition.left, mPreviewSurfacePosition.top); mTmpMatrix.postScale(((float) mPreviewSurfacePosition.width()) / mCurWidth, ((float) mPreviewSurfacePosition.height()) / mCurHeight); mTmpMatrix.getValues(mTmpValues); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setPosition(mSurfaceControl, mPreviewSurfacePosition.left, mPreviewSurfacePosition.top); t.setMatrix(mSurfaceControl, mTmpValues[MSCALE_X], mTmpValues[MSKEW_Y], mTmpValues[MSKEW_X], mTmpValues[MSCALE_Y]); t.apply(); } void attach(IWallpaperEngineWrapper wrapper) { if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper); if (mDestroyed) { return; } mIWallpaperEngine = wrapper; mCaller = wrapper.mCaller; mConnection = wrapper.mConnection; mWindowToken = wrapper.mWindowToken; mSurfaceHolder.setSizeFromLayout(); mInitializing = true; mSession = WindowManagerGlobal.getWindowSession(); mWindow.setSession(mSession); mLayout.packageName = getPackageName(); mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler()); mDisplay = mIWallpaperEngine.mDisplay; // Use window context of TYPE_WALLPAPER so client can access UI resources correctly. mDisplayContext = createDisplayContext(mDisplay) .createWindowContext(TYPE_WALLPAPER, null /* options */); mWallpaperDimAmount = mDisplayContext.getResources().getFloat( com.android.internal.R.dimen.config_wallpaperDimAmount); mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); onCreate(mSurfaceHolder); mInitializing = false; mReportedVisible = false; updateSurface(false, false, false); } /** * The {@link Context} with resources that match the current display the wallpaper is on. * For multiple display environment, multiple engines can be created to render on each * display, but these displays may have different densities. Use this context to get the * corresponding resources for currently display, avoiding the context of the service. *

* The display context will never be {@code null} after * {@link Engine#onCreate(SurfaceHolder)} has been called. * * @return A {@link Context} for current display. */ @Nullable public Context getDisplayContext() { return mDisplayContext; } /** * Executes life cycle event and updates internal ambient mode state based on * message sent from handler. * * @param inAmbientMode {@code true} if in ambient mode. * @param animationDuration For how long the transition will last, in ms. * @hide */ @VisibleForTesting public void doAmbientModeChanged(boolean inAmbientMode, long animationDuration) { if (!mDestroyed) { if (DEBUG) { Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + ", " + animationDuration + "): " + this); } mIsInAmbientMode = inAmbientMode; if (mCreated) { onAmbientModeChanged(inAmbientMode, animationDuration); } } } void doDesiredSizeChanged(int desiredWidth, int desiredHeight) { if (!mDestroyed) { if (DEBUG) Log.v(TAG, "onDesiredSizeChanged(" + desiredWidth + "," + desiredHeight + "): " + this); mIWallpaperEngine.mReqWidth = desiredWidth; mIWallpaperEngine.mReqHeight = desiredHeight; onDesiredSizeChanged(desiredWidth, desiredHeight); 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(); if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); } } void reportVisibility() { if (mScreenshotSurfaceControl != null && mVisible) { if (DEBUG) Log.v(TAG, "Frozen so don't report visibility change"); return; } if (!mDestroyed) { mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState(); boolean visible = mVisible && mDisplayState != Display.STATE_OFF; if (mReportedVisible != visible) { mReportedVisible = visible; if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible + "): " + this); if (visible) { // If becoming visible, in preview mode the surface // may have been destroyed so now we need to make // sure it is re-created. doOffsetsChanged(false); // force relayout to get new surface updateSurface(true, false, false); } onVisibilityChanged(visible); if (mReportedVisible && mFrozenRequested) { if (DEBUG) Log.v(TAG, "Freezing wallpaper after visibility update"); freeze(); } } } } void doOffsetsChanged(boolean always) { if (mDestroyed) { return; } if (!always && !mOffsetsChanged) { return; } float xOffset; float yOffset; float xOffsetStep; float yOffsetStep; boolean sync; synchronized (mLock) { xOffset = mPendingXOffset; yOffset = mPendingYOffset; xOffsetStep = mPendingXOffsetStep; yOffsetStep = mPendingYOffsetStep; sync = mPendingSync; mPendingSync = false; mOffsetMessageEnqueued = false; } if (mSurfaceCreated) { if (mReportedVisible) { if (DEBUG) Log.v(TAG, "Offsets change in " + this + ": " + xOffset + "," + yOffset); final int availw = mIWallpaperEngine.mReqWidth-mCurWidth; final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0; final int availh = mIWallpaperEngine.mReqHeight-mCurHeight; final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0; onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels); } else { mOffsetsChanged = true; } } if (sync) { try { if (DEBUG) Log.v(TAG, "Reporting offsets change complete"); mSession.wallpaperOffsetsComplete(mWindow.asBinder()); } catch (RemoteException e) { } } // setup local color extraction data processLocalColors(xOffset, xOffsetStep); } private void processLocalColors(float xOffset, float xOffsetStep) { // implemented by the wallpaper if (supportsLocalColorExtraction()) return; if (DEBUG) { Log.d(TAG, "processLocalColors " + xOffset + " of step " + xOffsetStep); } //below is the default implementation if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN || !mSurfaceHolder.getSurface().isValid()) return; int xCurrentPage; int xPages; if (!validStep(xOffsetStep)) { if (DEBUG) { Log.w(TAG, "invalid offset step " + xOffsetStep); } xOffset = 0; xOffsetStep = 1; xCurrentPage = 0; xPages = 1; } else { xPages = Math.round(1 / xOffsetStep) + 1; xOffsetStep = (float) 1 / (float) xPages; float shrink = (float) (xPages - 1) / (float) xPages; xOffset *= shrink; xCurrentPage = Math.round(xOffset / xOffsetStep); } if (DEBUG) { Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage); Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); } float finalXOffsetStep = xOffsetStep; float finalXOffset = xOffset; mHandler.post(() -> { resetWindowPages(); int xPage = xCurrentPage; EngineWindowPage current; if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) { mWindowPages = new EngineWindowPage[xPages]; initWindowPages(mWindowPages, finalXOffsetStep); } if (mLocalColorsToAdd.size() != 0) { for (RectF colorArea : mLocalColorsToAdd) { if (!isValid(colorArea)) continue; mLocalColorAreas.add(colorArea); int colorPage = getRectFPage(colorArea, finalXOffsetStep); EngineWindowPage currentPage = mWindowPages[colorPage]; if (currentPage == null) { currentPage = new EngineWindowPage(); currentPage.addArea(colorArea); mWindowPages[colorPage] = currentPage; } else { currentPage.setLastUpdateTime(0); currentPage.removeColor(colorArea); } } mLocalColorsToAdd.clear(); } if (xPage >= mWindowPages.length) { if (DEBUG) { Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage); Log.e(TAG, "error on page " + xPage + " out of " + xPages); Log.e(TAG, "error on xOffsetStep " + finalXOffsetStep + " xOffset " + finalXOffset); } xPage = mWindowPages.length - 1; } current = mWindowPages[xPage]; if (current == null) { if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages); if (DEBUG) { Log.d(TAG, "xOffsetStep " + finalXOffsetStep + " xOffset " + finalXOffset); } current = new EngineWindowPage(); mWindowPages[xPage] = current; } updatePage(current, xPage, xPages, finalXOffsetStep); }); } private void initWindowPages(EngineWindowPage[] windowPages, float step) { for (int i = 0; i < windowPages.length; i++) { windowPages[i] = new EngineWindowPage(); } mLocalColorAreas.addAll(mLocalColorsToAdd); mLocalColorsToAdd.clear(); for (RectF area: mLocalColorAreas) { if (!isValid(area)) { mLocalColorAreas.remove(area); continue; } int pageNum = getRectFPage(area, step); windowPages[pageNum].addArea(area); } } void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, float xOffsetStep) { // to save creating a runnable, check twice long current = System.currentTimeMillis(); long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION && currentPage.getLastUpdateTime() > 0) { return; } Surface surface = mSurfaceHolder.getSurface(); boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; int smaller = widthIsLarger ? mSurfaceSize.x : mSurfaceSize.y; float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller; int width = (int) (ratio * mSurfaceSize.x); int height = (int) (ratio * mSurfaceSize.y); if (width <= 0 || height <= 0) { Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height); return; } Bitmap screenShot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Bitmap finalScreenShot = screenShot; Trace.beginSection("WallpaperService#pixelCopy"); PixelCopy.request(surface, screenShot, (res) -> { Trace.endSection(); if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); if (res != PixelCopy.SUCCESS) { Bitmap lastBitmap = currentPage.getBitmap(); // assign the last bitmap taken for now currentPage.setBitmap(mLastScreenshot); Bitmap lastScreenshot = mLastScreenshot; if (lastScreenshot != null && !lastScreenshot.isRecycled() && !Objects.equals(lastBitmap, lastScreenshot)) { updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); } } else { mLastScreenshot = finalScreenShot; // going to hold this lock for a while currentPage.setBitmap(finalScreenShot); currentPage.setLastUpdateTime(current); updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); } }, mHandler); } // locked by the passed page private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages, float xOffsetStep) { if (page.getBitmap() == null) return; if (DEBUG) { Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas " + page.getAreas().size() + " and bitmap size of " + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight()); } for (RectF area: page.getAreas()) { if (area == null) continue; RectF subArea = generateSubRect(area, pageIndx, numPages); Bitmap b = page.getBitmap(); int x = Math.round(b.getWidth() * subArea.left); int y = Math.round(b.getHeight() * subArea.top); int width = Math.round(b.getWidth() * subArea.width()); int height = Math.round(b.getHeight() * subArea.height()); Bitmap target; try { target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height); } catch (Exception e) { Log.e(TAG, "Error creating page local color bitmap", e); continue; } WallpaperColors color = WallpaperColors.fromBitmap(target); target.recycle(); WallpaperColors currentColor = page.getColors(area); if (DEBUG) { Log.d(TAG, "getting local bitmap area x " + x + " y " + y + " width " + width + " height " + height + " for sub area " + subArea + " and with page " + pageIndx + " of " + numPages); } if (currentColor == null || !color.equals(currentColor)) { page.addWallpaperColors(area, color); if (DEBUG) { Log.d(TAG, "onLocalWallpaperColorsChanged" + " local color callback for area" + area + " for page " + pageIndx + " of " + numPages); } try { mConnection.onLocalWallpaperColorsChanged(area, color, mDisplayContext.getDisplayId()); } catch (RemoteException e) { Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); } } } } private RectF generateSubRect(RectF in, int pageInx, int numPages) { float minLeft = (float) (pageInx) / (float) (numPages); float maxRight = (float) (pageInx + 1) / (float) (numPages); float left = in.left; float right = in.right; // bound rect if (left < minLeft) left = minLeft; if (right > maxRight) right = maxRight; // scale up the sub area then trim left = (left * (float) numPages) % 1f; right = (right * (float) numPages) % 1f; if (right == 0f) { right = 1f; } return new RectF(left, in.top, right, in.bottom); } private void resetWindowPages() { if (supportsLocalColorExtraction()) return; if (!mResetWindowPages) return; mResetWindowPages = false; mLastWindowPage = -1; for (int i = 0; i < mWindowPages.length; i++) { EngineWindowPage page = mWindowPages[i]; if (page != null) { page.setLastUpdateTime(0L); } } } private int getRectFPage(RectF area, float step) { if (!isValid(area)) return 0; if (!validStep(step)) return 0; int pages = Math.round(1 / step); int page = Math.round(area.centerX() * pages); if (page == pages) return pages - 1; if (page == mWindowPages.length) page = mWindowPages.length - 1; return page; } /** * Add local colors areas of interest * @param regions list of areas * @hide */ public void addLocalColorsAreas(@NonNull List regions) { if (supportsLocalColorExtraction()) return; if (DEBUG) { Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions); } mHandler.post(() -> { mLocalColorsToAdd.addAll(regions); processLocalColors(mPendingXOffset, mPendingYOffset); }); } /** * Remove local colors areas of interest if they exist * @param regions list of areas * @hide */ public void removeLocalColorsAreas(@NonNull List regions) { if (supportsLocalColorExtraction()) return; mHandler.post(() -> { float step = mPendingXOffsetStep; mLocalColorsToAdd.removeAll(regions); mLocalColorAreas.removeAll(regions); if (!validStep(step)) { return; } for (int i = 0; i < mWindowPages.length; i++) { for (int j = 0; j < regions.size(); j++) { EngineWindowPage page = mWindowPages[i]; if (page != null) page.removeArea(regions.get(j)); } } }); } // fix the rect to be included within the bounds of the bitmap private Rect fixRect(Bitmap b, Rect r) { r.left = r.left >= r.right || r.left >= b.getWidth() || r.left > 0 ? 0 : r.left; r.right = r.left >= r.right || r.right > b.getWidth() ? b.getWidth() : r.right; return r; } private boolean validStep(float step) { return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.; } void doCommand(WallpaperCommand cmd) { Bundle result; if (!mDestroyed) { if (COMMAND_FREEZE.equals(cmd.action) || COMMAND_UNFREEZE.equals(cmd.action)) { updateFrozenState(/* frozenRequested= */ !COMMAND_UNFREEZE.equals(cmd.action)); } result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z, cmd.extras, cmd.sync); } else { result = null; } if (cmd.sync) { try { if (DEBUG) Log.v(TAG, "Reporting command complete"); mSession.wallpaperCommandComplete(mWindow.asBinder(), result); } catch (RemoteException e) { } } } private void updateFrozenState(boolean frozenRequested) { if (mIWallpaperEngine.mWallpaperManager.getWallpaperInfo() == null // Procees the unfreeze command in case the wallaper became static while // being paused. && frozenRequested) { if (DEBUG) Log.v(TAG, "Ignoring the freeze command for static wallpapers"); return; } mFrozenRequested = frozenRequested; boolean isFrozen = mScreenshotSurfaceControl != null; if (mFrozenRequested == isFrozen) { return; } if (mFrozenRequested) { freeze(); } else { unfreeze(); } } private void freeze() { if (!mReportedVisible || mDestroyed) { // Screenshot can't be taken until visibility is reported to the wallpaper host. return; } if (!showScreenshotOfWallpaper()) { return; } // Prevent a wallpaper host from rendering wallpaper behind a screeshot. doVisibilityChanged(false); // Remember that visibility is requested since it's not guaranteed that // mWindow#dispatchAppVisibility will be called when letterboxed application with // wallpaper background transitions to the Home screen. mVisible = true; } private void unfreeze() { cleanUpScreenshotSurfaceControl(); if (mVisible) { doVisibilityChanged(true); } } private void cleanUpScreenshotSurfaceControl() { // TODO(b/194399558): Add crossfade transition. if (mScreenshotSurfaceControl != null) { new SurfaceControl.Transaction() .remove(mScreenshotSurfaceControl) .show(mBbqSurfaceControl) .apply(); mScreenshotSurfaceControl = null; } } void scaleAndCropScreenshot() { if (mScreenshotSurfaceControl == null) { return; } if (mScreenshotSize.x <= 0 || mScreenshotSize.y <= 0) { Log.w(TAG, "Unexpected screenshot size: " + mScreenshotSize); return; } // Don't scale down and using the same scaling factor for both dimensions to // avoid stretching wallpaper image. float scaleFactor = Math.max(1, Math.max( ((float) mSurfaceSize.x) / mScreenshotSize.x, ((float) mSurfaceSize.y) / mScreenshotSize.y)); int diffX = ((int) (mScreenshotSize.x * scaleFactor)) - mSurfaceSize.x; int diffY = ((int) (mScreenshotSize.y * scaleFactor)) - mSurfaceSize.y; if (DEBUG) { Log.v(TAG, "Adjusting screenshot: scaleFactor=" + scaleFactor + " diffX=" + diffX + " diffY=" + diffY + " mSurfaceSize=" + mSurfaceSize + " mScreenshotSize=" + mScreenshotSize); } new SurfaceControl.Transaction() .setMatrix( mScreenshotSurfaceControl, /* dsdx= */ scaleFactor, /* dtdx= */ 0, /* dtdy= */ 0, /* dsdy= */ scaleFactor) .setWindowCrop( mScreenshotSurfaceControl, new Rect( /* left= */ diffX / 2, /* top= */ diffY / 2, /* right= */ diffX / 2 + mScreenshotSize.x, /* bottom= */ diffY / 2 + mScreenshotSize.y)) .setPosition(mScreenshotSurfaceControl, -diffX / 2, -diffY / 2) .apply(); } private boolean showScreenshotOfWallpaper() { if (mDestroyed || mSurfaceControl == null || !mSurfaceControl.isValid()) { if (DEBUG) Log.v(TAG, "Failed to screenshot wallpaper: surface isn't valid"); return false; } final Rect bounds = new Rect(0, 0, mSurfaceSize.x, mSurfaceSize.y); if (bounds.isEmpty()) { Log.w(TAG, "Failed to screenshot wallpaper: surface bounds are empty"); return false; } if (mScreenshotSurfaceControl != null) { Log.e(TAG, "Screenshot is unexpectedly not null"); // Destroying previous screenshot since it can have different size. cleanUpScreenshotSurfaceControl(); } SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = SurfaceControl.captureLayers( new SurfaceControl.LayerCaptureArgs.Builder(mSurfaceControl) // Needed because SurfaceFlinger#validateScreenshotPermissions // uses this parameter to check whether a caller only attempts // to screenshot itself when call doesn't come from the system. .setUid(Process.myUid()) .setChildrenOnly(false) .setSourceCrop(bounds) .build()); if (screenshotBuffer == null) { Log.w(TAG, "Failed to screenshot wallpaper: screenshotBuffer is null"); return false; } final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); // TODO(b/194399558): Add crossfade transition. mScreenshotSurfaceControl = new SurfaceControl.Builder() .setName("Wallpaper snapshot for engine " + this) .setFormat(hardwareBuffer.getFormat()) .setParent(mSurfaceControl) .setSecure(screenshotBuffer.containsSecureLayers()) .setCallsite("WallpaperService.Engine.showScreenshotOfWallpaper") .setBLASTLayer() .build(); mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y); GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer); t.setBuffer(mScreenshotSurfaceControl, graphicBuffer); t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace()); // Place on top everything else. t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE); t.show(mScreenshotSurfaceControl); t.hide(mBbqSurfaceControl); t.apply(); return true; } void reportSurfaceDestroyed() { if (mSurfaceCreated) { mSurfaceCreated = false; mSurfaceHolder.ungetCallbacks(); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceDestroyed(mSurfaceHolder); } } if (DEBUG) Log.v(TAG, "onSurfaceDestroyed(" + mSurfaceHolder + "): " + this); onSurfaceDestroyed(mSurfaceHolder); } } void detach() { if (mDestroyed) { return; } mDestroyed = true; if (mIWallpaperEngine.mDisplayManager != null) { mIWallpaperEngine.mDisplayManager.unregisterDisplayListener(mDisplayListener); } if (mVisible) { mVisible = false; if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this); onVisibilityChanged(false); } reportSurfaceDestroyed(); if (DEBUG) Log.v(TAG, "onDestroy(): " + this); onDestroy(); if (mCreated) { try { if (DEBUG) Log.v(TAG, "Removing window and destroying surface " + mSurfaceHolder.getSurface() + " of: " + this); if (mInputEventReceiver != null) { mInputEventReceiver.dispose(); mInputEventReceiver = null; } mSession.remove(mWindow); } catch (RemoteException e) { } mSurfaceHolder.mSurface.release(); if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); mBlastBufferQueue = null; } if (mBbqSurfaceControl != null) { new SurfaceControl.Transaction().remove(mBbqSurfaceControl).apply(); mBbqSurfaceControl = null; } mCreated = false; } } private final DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayChanged(int displayId) { if (mDisplay.getDisplayId() == displayId) { reportVisibility(); } } @Override public void onDisplayRemoved(int displayId) { } @Override public void onDisplayAdded(int displayId) { } }; private Surface getOrCreateBLASTSurface(int width, int height, int format) { Surface ret = null; if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mBbqSurfaceControl, width, height, format); // We only return the Surface the first time, as otherwise // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); } else { mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); } return ret; } } private boolean isValid(RectF area) { if (area == null) return false; boolean valid = area.bottom > area.top && area.left < area.right && LOCAL_COLOR_BOUNDS.contains(area); return valid; } private boolean inRectFRange(float number) { return number >= 0f && number <= 1f; } class IWallpaperEngineWrapper extends IWallpaperEngine.Stub implements HandlerCaller.Callback { private final HandlerCaller mCaller; final IWallpaperConnection mConnection; final IBinder mWindowToken; final int mWindowType; final boolean mIsPreview; boolean mShownReported; int mReqWidth; int mReqHeight; final Rect mDisplayPadding = new Rect(); final int mDisplayId; final DisplayManager mDisplayManager; final Display mDisplay; final WallpaperManager mWallpaperManager; private final AtomicBoolean mDetached = new AtomicBoolean(); Engine mEngine; IWallpaperEngineWrapper(WallpaperService context, IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId) { mWallpaperManager = getSystemService(WallpaperManager.class); mCaller = new HandlerCaller(context, context.getMainLooper(), this, true); mConnection = conn; mWindowToken = windowToken; mWindowType = windowType; mIsPreview = isPreview; mReqWidth = reqWidth; mReqHeight = reqHeight; mDisplayPadding.set(padding); mDisplayId = displayId; // Create a display context before onCreateEngine. mDisplayManager = getSystemService(DisplayManager.class); mDisplay = mDisplayManager.getDisplay(mDisplayId); if (mDisplay == null) { // Ignore this engine. throw new IllegalArgumentException("Cannot find display with id" + mDisplayId); } 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); mCaller.sendMessage(msg); } @Override public void setInAmbientMode(boolean inAmbientDisplay, long animationDuration) throws RemoteException { Message msg = mCaller.obtainMessageIO(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0, animationDuration); mCaller.sendMessage(msg); } public void dispatchPointer(MotionEvent event) { if (mEngine != null) { mEngine.dispatchPointer(event); } else { event.recycle(); } } public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras) { if (mEngine != null) { mEngine.mWindow.dispatchWallpaperCommand(action, x, y, z, extras, false); } } public void setZoomOut(float scale) { Message msg = mCaller.obtainMessageI(MSG_ZOOM, Float.floatToIntBits(scale)); mCaller.sendMessage(msg); } public void reportShown() { if (!mShownReported) { mShownReported = true; try { mConnection.engineShown(this); Log.d(TAG, "Wallpaper has updated the surface:" + mWallpaperManager.getWallpaperInfo()); } catch (RemoteException e) { Log.w(TAG, "Wallpaper host disappeared", e); return; } } } public void requestWallpaperColors() { Message msg = mCaller.obtainMessage(MSG_REQUEST_WALLPAPER_COLORS); mCaller.sendMessage(msg); } public void addLocalColorsAreas(List regions) { mEngine.addLocalColorsAreas(regions); } public void removeLocalColorsAreas(List regions) { mEngine.removeLocalColorsAreas(regions); } public void destroy() { Message msg = mCaller.obtainMessage(DO_DETACH); mCaller.sendMessage(msg); } public void detach() { mDetached.set(true); } public void scalePreview(Rect position) { Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position); mCaller.sendMessage(msg); } @Nullable public SurfaceControl mirrorSurfaceControl() { return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl); } private void doDetachEngine() { mActiveEngines.remove(mEngine); mEngine.detach(); // Some wallpapers will not trigger the rendering threads of the remaining engines even // if they are visible, so we need to toggle the state to get their attention. if (!mDetached.get()) { for (Engine eng : mActiveEngines) { if (eng.mVisible) { eng.doVisibilityChanged(false); eng.doVisibilityChanged(true); } } } } @Override public void executeMessage(Message message) { if (mDetached.get()) { if (mActiveEngines.contains(mEngine)) { doDetachEngine(); } return; } switch (message.what) { case DO_ATTACH: { Engine engine = onCreateEngine(); mEngine = engine; try { mConnection.attachEngine(this, mDisplayId); } catch (RemoteException e) { engine.detach(); Log.w(TAG, "Wallpaper host disappeared", e); return; } mActiveEngines.add(engine); engine.attach(this); return; } case DO_DETACH: { doDetachEngine(); return; } case DO_SET_DESIRED_SIZE: { mEngine.doDesiredSizeChanged(message.arg1, message.arg2); return; } case DO_SET_DISPLAY_PADDING: { mEngine.doDisplayPaddingChanged((Rect) message.obj); return; } case DO_IN_AMBIENT_MODE: { mEngine.doAmbientModeChanged(message.arg1 != 0, (Long) message.obj); return; } case MSG_UPDATE_SURFACE: mEngine.updateSurface(true, false, true/*false*/); break; case MSG_ZOOM: mEngine.setZoom(Float.intBitsToFloat(message.arg1)); break; case MSG_SCALE_PREVIEW: mEngine.scalePreview((Rect) message.obj); break; case MSG_VISIBILITY_CHANGED: if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine + ": " + message.arg1); mEngine.doVisibilityChanged(message.arg1 != 0); break; case MSG_WALLPAPER_OFFSETS: { mEngine.doOffsetsChanged(true); } break; case MSG_WALLPAPER_COMMAND: { WallpaperCommand cmd = (WallpaperCommand)message.obj; mEngine.doCommand(cmd); } break; case MSG_WINDOW_RESIZED: { final boolean reportDraw = message.arg1 != 0; mEngine.updateSurface(true, false, reportDraw); mEngine.doOffsetsChanged(true); mEngine.scaleAndCropScreenshot(); } break; case MSG_WINDOW_MOVED: { // Do nothing. What does it mean for a Wallpaper to move? } break; case MSG_TOUCH_EVENT: { boolean skip = false; MotionEvent ev = (MotionEvent)message.obj; if (ev.getAction() == MotionEvent.ACTION_MOVE) { synchronized (mEngine.mLock) { if (mEngine.mPendingMove == ev) { mEngine.mPendingMove = null; } else { // this is not the motion event we are looking for.... skip = true; } } } if (!skip) { if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev); mEngine.onTouchEvent(ev); } ev.recycle(); } break; case MSG_REQUEST_WALLPAPER_COLORS: { if (mConnection == null) { break; } try { WallpaperColors colors = mEngine.onComputeColors(); mEngine.setPrimaryWallpaperColors(colors); mConnection.onWallpaperColorsChanged(colors, mDisplayId); } catch (RemoteException e) { // Connection went away, nothing to do in here. } } break; case MSG_REPORT_SHOWN: { reportShown(); } break; default : Log.w(TAG, "Unknown message type " + message.what); } } } /** * Implements the internal {@link IWallpaperService} interface to convert * incoming calls to it back to calls on an {@link WallpaperService}. */ class IWallpaperServiceWrapper extends IWallpaperService.Stub { private final WallpaperService mTarget; private IWallpaperEngineWrapper mEngineWrapper; public IWallpaperServiceWrapper(WallpaperService context) { mTarget = context; } @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId) { mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId); } @Override public void detach() { mEngineWrapper.detach(); } } @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { super.onDestroy(); for (int i=0; i