blob: d645e3f746d78c430542f210f48de6b47ecd8343 [file] [log] [blame]
/*
* 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 android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.Service;
import android.app.WallpaperColors;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
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.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.MergedConfiguration;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
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.InsetsState;
import android.view.MotionEvent;
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 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.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 <code>&lt;{@link android.R.styleable#Wallpaper wallpaper}&gt;</code>
* tag.
*/
public static final String SERVICE_META_DATA = "android.service.wallpaper";
static final String TAG = "WallpaperService";
static final boolean DEBUG = false;
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
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 NOTIFY_COLORS_RATE_LIMIT_MS = 1000;
private final ArrayList<Engine> mActiveEngines
= new ArrayList<Engine>();
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;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
IWallpaperConnection mConnection;
IBinder mWindowToken;
boolean mInitializing = true;
boolean mVisible;
boolean mReportedVisible;
boolean mDestroyed;
// Current window state.
boolean mCreated;
boolean mSurfaceCreated;
boolean mIsCreating;
boolean mDrawingAllowed;
boolean mOffsetsChanged;
boolean mFixedSizeAllowed;
int mWidth;
int mHeight;
int mFormat;
int mType;
int mCurWidth;
int mCurHeight;
int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
int mWindowPrivateFlags =
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
int mCurWindowFlags = mWindowFlags;
int mCurWindowPrivateFlags = mWindowPrivateFlags;
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 mOutsets = new Rect();
final Rect mDispatchedOverscanInsets = new Rect();
final Rect mDispatchedContentInsets = new Rect();
final Rect mDispatchedStableInsets = new Rect();
final Rect mDispatchedOutsets = new Rect();
final Rect mFinalSystemInsets = new Rect();
final Rect mFinalStableInsets = new Rect();
final Rect mBackdropFrame = new Rect();
final DisplayCutout.ParcelableWrapper mDisplayCutout =
new DisplayCutout.ParcelableWrapper();
DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
final InsetsState mInsetsState = new InsetsState();
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
IWindowSession mSession;
InputChannel mInputChannel;
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<Long> mClockFunction;
private final Handler mHandler;
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
SurfaceControl mSurfaceControl = new SurfaceControl();
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(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
MergedConfiguration mergedConfiguration, Rect backDropRect, boolean forceLayout,
boolean alwaysConsumeSystemBars, int displayId,
DisplayCutout.ParcelableWrapper displayCutout) {
Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0, outsets);
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,
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);
}
}
}
@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<Long> 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;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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. <em>It is very important that a wallpaper only use
* CPU while it is visible.</em>.
*/
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) {
}
/**
* 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.");
}
} 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.
* <p>
* 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;
}
/**
* 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.print("mVisibleInsets=");
out.print(mVisibleInsets.toShortString());
out.print(" mWinFrame="); out.print(mWinFrame.toShortString());
out.print(" mContentInsets="); out.println(mContentInsets.toShortString());
out.print(prefix); out.print("mConfiguration=");
out.println(mMergedConfiguration.getMergedConfiguration());
out.print(prefix); out.print("mLayout="); out.println(mLayout);
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);
}
}
}
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: 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;
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;
if (!fixedSize) {
mLayout.width = myWidth;
mLayout.height = myHeight;
} else {
// Force the wallpaper to cover the screen in both dimensions
// only internal implementations like ImageWallpaper
DisplayInfo displayInfo = new DisplayInfo();
mDisplay.getDisplayInfo(displayInfo);
final float layoutScale = Math.max(
(float) displayInfo.logicalHeight / (float) myHeight,
(float) displayInfo.logicalWidth / (float) myWidth);
mLayout.height = (int) (myHeight * layoutScale);
mLayout.width = (int) (myWidth * layoutScale);
mWindowFlags |= WindowManager.LayoutParams.FLAG_SCALED;
}
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.setTitle(WallpaperService.this.getClass().getName());
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
mInputChannel = new InputChannel();
if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
mDisplay.getDisplayId(), mWinFrame, mContentInsets, mStableInsets,
mOutsets, mDisplayCutout, mInputChannel,
mInsetsState) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
mCreated = true;
mInputEventReceiver = new WallpaperInputEventReceiver(
mInputChannel, Looper.myLooper());
}
mSurfaceHolder.mSurfaceLock.lock();
mDrawingAllowed = true;
if (!fixedSize) {
mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding);
mLayout.surfaceInsets.left += mOutsets.left;
mLayout.surfaceInsets.top += mOutsets.top;
mLayout.surfaceInsets.right += mOutsets.right;
mLayout.surfaceInsets.bottom += mOutsets.bottom;
} else {
mLayout.surfaceInsets.set(0, 0, 0, 0);
}
final int relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
View.VISIBLE, 0, -1, mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
mDisplayCutout, mMergedConfiguration, mSurfaceControl,
mInsetsState);
if (mSurfaceControl.isValid()) {
mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
mSurfaceControl.release();
}
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
+ ", frame=" + mWinFrame);
int w = mWinFrame.width();
int h = mWinFrame.height();
if (!fixedSize) {
final Rect padding = mIWallpaperEngine.mDisplayPadding;
w += padding.left + padding.right + mOutsets.left + mOutsets.right;
h += padding.top + padding.bottom + mOutsets.top + mOutsets.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;
mDisplayCutout.set(mDisplayCutout.get().inset(-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);
}
insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets);
insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets);
insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets);
insetsChanged |= !mDispatchedOutsets.equals(mOutsets);
insetsChanged |= !mDispatchedDisplayCutout.equals(mDisplayCutout.get());
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) {
mDispatchedOverscanInsets.set(mOverscanInsets);
mDispatchedOverscanInsets.left += mOutsets.left;
mDispatchedOverscanInsets.top += mOutsets.top;
mDispatchedOverscanInsets.right += mOutsets.right;
mDispatchedOverscanInsets.bottom += mOutsets.bottom;
mDispatchedContentInsets.set(mContentInsets);
mDispatchedStableInsets.set(mStableInsets);
mDispatchedOutsets.set(mOutsets);
mDispatchedDisplayCutout = mDisplayCutout.get();
mFinalSystemInsets.set(mDispatchedOverscanInsets);
mFinalStableInsets.set(mDispatchedStableInsets);
WindowInsets insets = new WindowInsets(mFinalSystemInsets,
mFinalStableInsets,
getResources().getConfiguration().isScreenRound(), false,
mDispatchedDisplayCutout);
if (DEBUG) {
Log.v(TAG, "dispatching insets=" + insets);
}
onApplyWindowInsets(insets);
}
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) {
mSession.finishDrawing(mWindow);
}
mIWallpaperEngine.reportShown();
}
} catch (RemoteException ex) {
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
" w=" + mLayout.width + " h=" + mLayout.height);
}
}
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;
mDisplayContext = createDisplayContext(mDisplay);
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.
* <p>
* 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();
}
}
void reportVisibility() {
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);
updateSurface(false, false, false);
}
onVisibilityChanged(visible);
}
}
}
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) {
}
}
}
void doCommand(WallpaperCommand cmd) {
Bundle result;
if (!mDestroyed) {
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) {
}
}
}
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();
mCreated = false;
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
mInputChannel.dispose();
mInputChannel = null;
}
}
}
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) {
}
};
}
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;
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) {
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 reportShown() {
if (!mShownReported) {
mShownReported = true;
try {
mConnection.engineShown(this);
} 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 destroy() {
Message msg = mCaller.obtainMessage(DO_DETACH);
mCaller.sendMessage(msg);
}
public void detach() {
mDetached.set(true);
}
private void doDetachEngine() {
mActiveEngines.remove(mEngine);
mEngine.detach();
}
@Override
public void executeMessage(Message message) {
if (mDetached.get()) {
if (mActiveEngines.contains(mEngine)) {
doDetachEngine();
}
return;
}
switch (message.what) {
case DO_ATTACH: {
try {
mConnection.attachEngine(this, mDisplayId);
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
Engine engine = onCreateEngine();
mEngine = engine;
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, false);
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.mOutsets.set((Rect) message.obj);
mEngine.updateSurface(true, false, reportDraw);
mEngine.doOffsetsChanged(true);
} 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 {
mConnection.onWallpaperColorsChanged(mEngine.onComputeColors(), mDisplayId);
} catch (RemoteException e) {
// Connection went away, nothing to do in here.
}
} 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<mActiveEngines.size(); i++) {
mActiveEngines.get(i).detach();
}
mActiveEngines.clear();
}
/**
* Implement to return the implementation of the internal accessibility
* service interface. Subclasses should not override.
*/
@Override
public final IBinder onBind(Intent intent) {
return new IWallpaperServiceWrapper(this);
}
/**
* Must be implemented to return a new instance of the wallpaper's engine.
* Note that multiple instances may be active at the same time, such as
* when the wallpaper is currently set as the active wallpaper and the user
* is in the wallpaper picker viewing a preview of it as well.
*/
public abstract Engine onCreateEngine();
@Override
protected void dump(FileDescriptor fd, PrintWriter out, String[] args) {
out.print("State of wallpaper "); out.print(this); out.println(":");
for (int i=0; i<mActiveEngines.size(); i++) {
Engine engine = mActiveEngines.get(i);
out.print(" Engine "); out.print(engine); out.println(":");
engine.dump(" ", fd, out, args);
}
}
}