WallpaperService.java revision c5ed5910c9ef066cec6a13bbb404ec57b1e92637
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.service.wallpaper;
18
19import com.android.internal.os.HandlerCaller;
20import com.android.internal.view.BaseIWindow;
21import com.android.internal.view.BaseInputHandler;
22import com.android.internal.view.BaseSurfaceHolder;
23
24import android.annotation.SdkConstant;
25import android.annotation.SdkConstant.SdkConstantType;
26import android.app.Service;
27import android.app.WallpaperManager;
28import android.content.BroadcastReceiver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.res.Configuration;
33import android.graphics.Rect;
34import android.os.Bundle;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.Message;
38import android.os.RemoteException;
39import android.util.Log;
40import android.util.LogPrinter;
41import android.view.Gravity;
42import android.view.IWindowSession;
43import android.view.InputChannel;
44import android.view.InputDevice;
45import android.view.InputHandler;
46import android.view.InputQueue;
47import android.view.KeyEvent;
48import android.view.MotionEvent;
49import android.view.SurfaceHolder;
50import android.view.View;
51import android.view.ViewGroup;
52import android.view.ViewRoot;
53import android.view.WindowManager;
54import android.view.WindowManagerImpl;
55import android.view.WindowManagerPolicy;
56
57import java.util.ArrayList;
58
59/**
60 * A wallpaper service is responsible for showing a live wallpaper behind
61 * applications that would like to sit on top of it.  This service object
62 * itself does very little -- its only purpose is to generate instances of
63 * {@link Engine} as needed.  Implementing a wallpaper thus
64 * involves subclassing from this, subclassing an Engine implementation,
65 * and implementing {@link #onCreateEngine()} to return a new instance of
66 * your engine.
67 */
68public abstract class WallpaperService extends Service {
69    /**
70     * The {@link Intent} that must be declared as handled by the service.
71     * To be supported, the service must also require the
72     * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
73     * that other applications can not abuse it.
74     */
75    @SdkConstant(SdkConstantType.SERVICE_ACTION)
76    public static final String SERVICE_INTERFACE =
77            "android.service.wallpaper.WallpaperService";
78
79    /**
80     * Name under which a WallpaperService component publishes information
81     * about itself.  This meta-data must reference an XML resource containing
82     * a <code>&lt;{@link android.R.styleable#Wallpaper wallpaper}&gt;</code>
83     * tag.
84     */
85    public static final String SERVICE_META_DATA = "android.service.wallpaper";
86
87    static final String TAG = "WallpaperService";
88    static final boolean DEBUG = false;
89
90    private static final int DO_ATTACH = 10;
91    private static final int DO_DETACH = 20;
92    private static final int DO_SET_DESIRED_SIZE = 30;
93
94    private static final int MSG_UPDATE_SURFACE = 10000;
95    private static final int MSG_VISIBILITY_CHANGED = 10010;
96    private static final int MSG_WALLPAPER_OFFSETS = 10020;
97    private static final int MSG_WALLPAPER_COMMAND = 10025;
98    private static final int MSG_WINDOW_RESIZED = 10030;
99    private static final int MSG_TOUCH_EVENT = 10040;
100
101    private Looper mCallbackLooper;
102    private final ArrayList<Engine> mActiveEngines
103            = new ArrayList<Engine>();
104
105    static final class WallpaperCommand {
106        String action;
107        int x;
108        int y;
109        int z;
110        Bundle extras;
111        boolean sync;
112    }
113
114    /**
115     * The actual implementation of a wallpaper.  A wallpaper service may
116     * have multiple instances running (for example as a real wallpaper
117     * and as a preview), each of which is represented by its own Engine
118     * instance.  You must implement {@link WallpaperService#onCreateEngine()}
119     * to return your concrete Engine implementation.
120     */
121    public class Engine {
122        IWallpaperEngineWrapper mIWallpaperEngine;
123
124        // Copies from mIWallpaperEngine.
125        HandlerCaller mCaller;
126        IWallpaperConnection mConnection;
127        IBinder mWindowToken;
128
129        boolean mInitializing = true;
130        boolean mVisible;
131        boolean mScreenOn = true;
132        boolean mReportedVisible;
133        boolean mDestroyed;
134
135        // Current window state.
136        boolean mCreated;
137        boolean mSurfaceCreated;
138        boolean mIsCreating;
139        boolean mDrawingAllowed;
140        int mWidth;
141        int mHeight;
142        int mFormat;
143        int mType;
144        int mCurWidth;
145        int mCurHeight;
146        int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
147        int mCurWindowFlags = mWindowFlags;
148        final Rect mVisibleInsets = new Rect();
149        final Rect mWinFrame = new Rect();
150        final Rect mContentInsets = new Rect();
151        final Configuration mConfiguration = new Configuration();
152
153        final WindowManager.LayoutParams mLayout
154                = new WindowManager.LayoutParams();
155        IWindowSession mSession;
156        InputChannel mInputChannel;
157
158        final Object mLock = new Object();
159        boolean mOffsetMessageEnqueued;
160        float mPendingXOffset;
161        float mPendingYOffset;
162        float mPendingXOffsetStep;
163        float mPendingYOffsetStep;
164        boolean mPendingSync;
165        MotionEvent mPendingMove;
166
167        final BroadcastReceiver mReceiver = new BroadcastReceiver() {
168            @Override
169            public void onReceive(Context context, Intent intent) {
170                if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
171                    mScreenOn = true;
172                    reportVisibility();
173                } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
174                    mScreenOn = false;
175                    reportVisibility();
176                }
177            }
178        };
179
180        final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
181
182            @Override
183            public boolean onAllowLockCanvas() {
184                return mDrawingAllowed;
185            }
186
187            @Override
188            public void onRelayoutContainer() {
189                Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE);
190                mCaller.sendMessage(msg);
191            }
192
193            @Override
194            public void onUpdateSurface() {
195                Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE);
196                mCaller.sendMessage(msg);
197            }
198
199            public boolean isCreating() {
200                return mIsCreating;
201            }
202
203            @Override
204            public void setFixedSize(int width, int height) {
205                throw new UnsupportedOperationException(
206                        "Wallpapers currently only support sizing from layout");
207            }
208
209            public void setKeepScreenOn(boolean screenOn) {
210                throw new UnsupportedOperationException(
211                        "Wallpapers do not support keep screen on");
212            }
213
214        };
215
216        final InputHandler mInputHandler = new BaseInputHandler() {
217            @Override
218            public void handleMotion(MotionEvent event, Runnable finishedCallback) {
219                try {
220                    int source = event.getSource();
221                    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
222                        dispatchPointer(event);
223                    }
224                } finally {
225                    finishedCallback.run();
226                }
227            }
228        };
229
230        final BaseIWindow mWindow = new BaseIWindow() {
231            @Override
232            public void resized(int w, int h, Rect coveredInsets,
233                    Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
234                Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
235                        reportDraw ? 1 : 0);
236                mCaller.sendMessage(msg);
237            }
238
239            @Override
240            public void dispatchAppVisibility(boolean visible) {
241                // We don't do this in preview mode; we'll let the preview
242                // activity tell us when to run.
243                if (!mIWallpaperEngine.mIsPreview) {
244                    Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
245                            visible ? 1 : 0);
246                    mCaller.sendMessage(msg);
247                }
248            }
249
250            @Override
251            public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
252                    boolean sync) {
253                synchronized (mLock) {
254                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper offsets: " + x + ", " + y);
255                    mPendingXOffset = x;
256                    mPendingYOffset = y;
257                    mPendingXOffsetStep = xStep;
258                    mPendingYOffsetStep = yStep;
259                    if (sync) {
260                        mPendingSync = true;
261                    }
262                    if (!mOffsetMessageEnqueued) {
263                        mOffsetMessageEnqueued = true;
264                        Message msg = mCaller.obtainMessage(MSG_WALLPAPER_OFFSETS);
265                        mCaller.sendMessage(msg);
266                    }
267                }
268            }
269
270            public void dispatchWallpaperCommand(String action, int x, int y,
271                    int z, Bundle extras, boolean sync) {
272                synchronized (mLock) {
273                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper command: " + x + ", " + y);
274                    WallpaperCommand cmd = new WallpaperCommand();
275                    cmd.action = action;
276                    cmd.x = x;
277                    cmd.y = y;
278                    cmd.z = z;
279                    cmd.extras = extras;
280                    cmd.sync = sync;
281                    Message msg = mCaller.obtainMessage(MSG_WALLPAPER_COMMAND);
282                    msg.obj = cmd;
283                    mCaller.sendMessage(msg);
284                }
285            }
286        };
287
288        /**
289         * Provides access to the surface in which this wallpaper is drawn.
290         */
291        public SurfaceHolder getSurfaceHolder() {
292            return mSurfaceHolder;
293        }
294
295        /**
296         * Convenience for {@link WallpaperManager#getDesiredMinimumWidth()
297         * WallpaperManager.getDesiredMinimumWidth()}, returning the width
298         * that the system would like this wallpaper to run in.
299         */
300        public int getDesiredMinimumWidth() {
301            return mIWallpaperEngine.mReqWidth;
302        }
303
304        /**
305         * Convenience for {@link WallpaperManager#getDesiredMinimumHeight()
306         * WallpaperManager.getDesiredMinimumHeight()}, returning the height
307         * that the system would like this wallpaper to run in.
308         */
309        public int getDesiredMinimumHeight() {
310            return mIWallpaperEngine.mReqHeight;
311        }
312
313        /**
314         * Return whether the wallpaper is currently visible to the user,
315         * this is the last value supplied to
316         * {@link #onVisibilityChanged(boolean)}.
317         */
318        public boolean isVisible() {
319            return mReportedVisible;
320        }
321
322        /**
323         * Returns true if this engine is running in preview mode -- that is,
324         * it is being shown to the user before they select it as the actual
325         * wallpaper.
326         */
327        public boolean isPreview() {
328            return mIWallpaperEngine.mIsPreview;
329        }
330
331        /**
332         * Control whether this wallpaper will receive raw touch events
333         * from the window manager as the user interacts with the window
334         * that is currently displaying the wallpaper.  By default they
335         * are turned off.  If enabled, the events will be received in
336         * {@link #onTouchEvent(MotionEvent)}.
337         */
338        public void setTouchEventsEnabled(boolean enabled) {
339            mWindowFlags = enabled
340                    ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
341                    : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
342            if (mCreated) {
343                updateSurface(false, false, false);
344            }
345        }
346
347        /**
348         * Called once to initialize the engine.  After returning, the
349         * engine's surface will be created by the framework.
350         */
351        public void onCreate(SurfaceHolder surfaceHolder) {
352        }
353
354        /**
355         * Called right before the engine is going away.  After this the
356         * surface will be destroyed and this Engine object is no longer
357         * valid.
358         */
359        public void onDestroy() {
360        }
361
362        /**
363         * Called to inform you of the wallpaper becoming visible or
364         * hidden.  <em>It is very important that a wallpaper only use
365         * CPU while it is visible.</em>.
366         */
367        public void onVisibilityChanged(boolean visible) {
368        }
369
370        /**
371         * Called as the user performs touch-screen interaction with the
372         * window that is currently showing this wallpaper.  Note that the
373         * events you receive here are driven by the actual application the
374         * user is interacting with, so if it is slow you will get fewer
375         * move events.
376         */
377        public void onTouchEvent(MotionEvent event) {
378        }
379
380        /**
381         * Called to inform you of the wallpaper's offsets changing
382         * within its contain, corresponding to the container's
383         * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
384         * WallpaperManager.setWallpaperOffsets()}.
385         */
386        public void onOffsetsChanged(float xOffset, float yOffset,
387                float xOffsetStep, float yOffsetStep,
388                int xPixelOffset, int yPixelOffset) {
389        }
390
391        /**
392         * Process a command that was sent to the wallpaper with
393         * {@link WallpaperManager#sendWallpaperCommand}.
394         * The default implementation does nothing, and always returns null
395         * as the result.
396         *
397         * @param action The name of the command to perform.  This tells you
398         * what to do and how to interpret the rest of the arguments.
399         * @param x Generic integer parameter.
400         * @param y Generic integer parameter.
401         * @param z Generic integer parameter.
402         * @param extras Any additional parameters.
403         * @param resultRequested If true, the caller is requesting that
404         * a result, appropriate for the command, be returned back.
405         * @return If returning a result, create a Bundle and place the
406         * result data in to it.  Otherwise return null.
407         */
408        public Bundle onCommand(String action, int x, int y, int z,
409                Bundle extras, boolean resultRequested) {
410            return null;
411        }
412
413        /**
414         * Called when an application has changed the desired virtual size of
415         * the wallpaper.
416         */
417        public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
418        }
419
420        /**
421         * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
422         * SurfaceHolder.Callback.surfaceChanged()}.
423         */
424        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
425        }
426
427        /**
428         * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
429         * SurfaceHolder.Callback.surfaceRedrawNeeded()}.
430         */
431        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
432        }
433
434        /**
435         * Convenience for {@link SurfaceHolder.Callback#surfaceCreated
436         * SurfaceHolder.Callback.surfaceCreated()}.
437         */
438        public void onSurfaceCreated(SurfaceHolder holder) {
439        }
440
441        /**
442         * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
443         * SurfaceHolder.Callback.surfaceDestroyed()}.
444         */
445        public void onSurfaceDestroyed(SurfaceHolder holder) {
446        }
447
448        private void dispatchPointer(MotionEvent event) {
449            synchronized (mLock) {
450                if (event.getAction() == MotionEvent.ACTION_MOVE) {
451                    if (mPendingMove != null) {
452                        mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
453                        mPendingMove.recycle();
454                    }
455                    mPendingMove = event;
456                } else {
457                    mPendingMove = null;
458                }
459                Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
460                mCaller.sendMessage(msg);
461            }
462        }
463
464        void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
465            if (mDestroyed) {
466                Log.w(TAG, "Ignoring updateSurface: destroyed");
467            }
468
469            int myWidth = mSurfaceHolder.getRequestedWidth();
470            if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
471            int myHeight = mSurfaceHolder.getRequestedHeight();
472            if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
473
474            final boolean creating = !mCreated;
475            final boolean surfaceCreating = !mSurfaceCreated;
476            final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
477            boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
478            final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
479            final boolean flagsChanged = mCurWindowFlags != mWindowFlags;
480            if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
481                    || typeChanged || flagsChanged || redrawNeeded) {
482
483                if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
484                        + " format=" + formatChanged + " size=" + sizeChanged);
485
486                try {
487                    mWidth = myWidth;
488                    mHeight = myHeight;
489                    mFormat = mSurfaceHolder.getRequestedFormat();
490                    mType = mSurfaceHolder.getRequestedType();
491
492                    mLayout.x = 0;
493                    mLayout.y = 0;
494                    mLayout.width = myWidth;
495                    mLayout.height = myHeight;
496
497                    mLayout.format = mFormat;
498
499                    mCurWindowFlags = mWindowFlags;
500                    mLayout.flags = mWindowFlags
501                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
502                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
503                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
504                            ;
505
506                    mLayout.memoryType = mType;
507                    mLayout.token = mWindowToken;
508
509                    if (!mCreated) {
510                        mLayout.type = mIWallpaperEngine.mWindowType;
511                        mLayout.gravity = Gravity.LEFT|Gravity.TOP;
512                        mLayout.setTitle(WallpaperService.this.getClass().getName());
513                        mLayout.windowAnimations =
514                                com.android.internal.R.style.Animation_Wallpaper;
515                        mInputChannel = new InputChannel();
516                        mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets,
517                                mInputChannel);
518                        mCreated = true;
519
520                        InputQueue.registerInputChannel(mInputChannel, mInputHandler,
521                                Looper.myQueue());
522                    }
523
524                    mSurfaceHolder.mSurfaceLock.lock();
525                    mDrawingAllowed = true;
526
527                    final int relayoutResult = mSession.relayout(
528                        mWindow, mLayout, mWidth, mHeight,
529                            View.VISIBLE, false, mWinFrame, mContentInsets,
530                            mVisibleInsets, mConfiguration, mSurfaceHolder.mSurface);
531
532                    if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
533                            + ", frame=" + mWinFrame);
534
535                    int w = mWinFrame.width();
536                    if (mCurWidth != w) {
537                        sizeChanged = true;
538                        mCurWidth = w;
539                    }
540                    int h = mWinFrame.height();
541                    if (mCurHeight != h) {
542                        sizeChanged = true;
543                        mCurHeight = h;
544                    }
545
546                    mSurfaceHolder.mSurfaceLock.unlock();
547
548                    if (!mSurfaceHolder.mSurface.isValid()) {
549                        reportSurfaceDestroyed();
550                        if (DEBUG) Log.v(TAG, "Layout: Surface destroyed");
551                        return;
552                    }
553
554                    try {
555                        mSurfaceHolder.ungetCallbacks();
556
557                        if (surfaceCreating) {
558                            mIsCreating = true;
559                            if (DEBUG) Log.v(TAG, "onSurfaceCreated("
560                                    + mSurfaceHolder + "): " + this);
561                            onSurfaceCreated(mSurfaceHolder);
562                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
563                            if (callbacks != null) {
564                                for (SurfaceHolder.Callback c : callbacks) {
565                                    c.surfaceCreated(mSurfaceHolder);
566                                }
567                            }
568                        }
569
570                        redrawNeeded |= creating
571                                || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0;
572
573                        if (forceReport || creating || surfaceCreating
574                                || formatChanged || sizeChanged) {
575                            if (DEBUG) {
576                                RuntimeException e = new RuntimeException();
577                                e.fillInStackTrace();
578                                Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating
579                                        + " formatChanged=" + formatChanged
580                                        + " sizeChanged=" + sizeChanged, e);
581                            }
582                            if (DEBUG) Log.v(TAG, "onSurfaceChanged("
583                                    + mSurfaceHolder + ", " + mFormat
584                                    + ", " + mCurWidth + ", " + mCurHeight
585                                    + "): " + this);
586                            onSurfaceChanged(mSurfaceHolder, mFormat,
587                                    mCurWidth, mCurHeight);
588                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
589                            if (callbacks != null) {
590                                for (SurfaceHolder.Callback c : callbacks) {
591                                    c.surfaceChanged(mSurfaceHolder, mFormat,
592                                            mCurWidth, mCurHeight);
593                                }
594                            }
595                        }
596
597                        if (redrawNeeded) {
598                            onSurfaceRedrawNeeded(mSurfaceHolder);
599                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
600                            if (callbacks != null) {
601                                for (SurfaceHolder.Callback c : callbacks) {
602                                    if (c instanceof SurfaceHolder.Callback2) {
603                                        ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
604                                                mSurfaceHolder);
605                                    }
606                                }
607                            }
608                        }
609
610                    } finally {
611                        mIsCreating = false;
612                        mSurfaceCreated = true;
613                        if (redrawNeeded) {
614                            mSession.finishDrawing(mWindow);
615                        }
616                    }
617                } catch (RemoteException ex) {
618                }
619                if (DEBUG) Log.v(
620                    TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
621                    " w=" + mLayout.width + " h=" + mLayout.height);
622            }
623        }
624
625        void attach(IWallpaperEngineWrapper wrapper) {
626            if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);
627            if (mDestroyed) {
628                return;
629            }
630
631            mIWallpaperEngine = wrapper;
632            mCaller = wrapper.mCaller;
633            mConnection = wrapper.mConnection;
634            mWindowToken = wrapper.mWindowToken;
635            mSurfaceHolder.setSizeFromLayout();
636            mInitializing = true;
637            mSession = ViewRoot.getWindowSession(getMainLooper());
638
639            mWindow.setSession(mSession);
640
641            IntentFilter filter = new IntentFilter();
642            filter.addAction(Intent.ACTION_SCREEN_ON);
643            filter.addAction(Intent.ACTION_SCREEN_OFF);
644            registerReceiver(mReceiver, filter);
645
646            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
647            onCreate(mSurfaceHolder);
648
649            mInitializing = false;
650            updateSurface(false, false, false);
651        }
652
653        void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
654            if (!mDestroyed) {
655                if (DEBUG) Log.v(TAG, "onDesiredSizeChanged("
656                        + desiredWidth + "," + desiredHeight + "): " + this);
657                onDesiredSizeChanged(desiredWidth, desiredHeight);
658            }
659        }
660
661        void doVisibilityChanged(boolean visible) {
662            if (!mDestroyed) {
663                mVisible = visible;
664                reportVisibility();
665            }
666        }
667
668        void reportVisibility() {
669            if (!mDestroyed) {
670                boolean visible = mVisible && mScreenOn;
671                if (mReportedVisible != visible) {
672                    mReportedVisible = visible;
673                    if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible
674                            + "): " + this);
675                    if (visible) {
676                        // If becoming visible, in preview mode the surface
677                        // may have been destroyed so now we need to make
678                        // sure it is re-created.
679                        updateSurface(false, false, false);
680                    }
681                    onVisibilityChanged(visible);
682                }
683            }
684        }
685
686        void doOffsetsChanged() {
687            if (mDestroyed) {
688                return;
689            }
690
691            float xOffset;
692            float yOffset;
693            float xOffsetStep;
694            float yOffsetStep;
695            boolean sync;
696            synchronized (mLock) {
697                xOffset = mPendingXOffset;
698                yOffset = mPendingYOffset;
699                xOffsetStep = mPendingXOffsetStep;
700                yOffsetStep = mPendingYOffsetStep;
701                sync = mPendingSync;
702                mPendingSync = false;
703                mOffsetMessageEnqueued = false;
704            }
705
706            if (mSurfaceCreated) {
707                if (DEBUG) Log.v(TAG, "Offsets change in " + this
708                        + ": " + xOffset + "," + yOffset);
709                final int availw = mIWallpaperEngine.mReqWidth-mCurWidth;
710                final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0;
711                final int availh = mIWallpaperEngine.mReqHeight-mCurHeight;
712                final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0;
713                onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels);
714            }
715
716            if (sync) {
717                try {
718                    if (DEBUG) Log.v(TAG, "Reporting offsets change complete");
719                    mSession.wallpaperOffsetsComplete(mWindow.asBinder());
720                } catch (RemoteException e) {
721                }
722            }
723        }
724
725        void doCommand(WallpaperCommand cmd) {
726            Bundle result;
727            if (!mDestroyed) {
728                result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
729                        cmd.extras, cmd.sync);
730            } else {
731                result = null;
732            }
733            if (cmd.sync) {
734                try {
735                    if (DEBUG) Log.v(TAG, "Reporting command complete");
736                    mSession.wallpaperCommandComplete(mWindow.asBinder(), result);
737                } catch (RemoteException e) {
738                }
739            }
740        }
741
742        void reportSurfaceDestroyed() {
743            if (mSurfaceCreated) {
744                mSurfaceCreated = false;
745                mSurfaceHolder.ungetCallbacks();
746                SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
747                if (callbacks != null) {
748                    for (SurfaceHolder.Callback c : callbacks) {
749                        c.surfaceDestroyed(mSurfaceHolder);
750                    }
751                }
752                if (DEBUG) Log.v(TAG, "onSurfaceDestroyed("
753                        + mSurfaceHolder + "): " + this);
754                onSurfaceDestroyed(mSurfaceHolder);
755            }
756        }
757
758        void detach() {
759            if (mDestroyed) {
760                return;
761            }
762
763            mDestroyed = true;
764
765            if (mVisible) {
766                mVisible = false;
767                if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this);
768                onVisibilityChanged(false);
769            }
770
771            reportSurfaceDestroyed();
772
773            if (DEBUG) Log.v(TAG, "onDestroy(): " + this);
774            onDestroy();
775
776            unregisterReceiver(mReceiver);
777
778            if (mCreated) {
779                try {
780                    if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
781                            + mSurfaceHolder.getSurface() + " of: " + this);
782
783                    if (mInputChannel != null) {
784                        InputQueue.unregisterInputChannel(mInputChannel);
785                    }
786
787                    mSession.remove(mWindow);
788                } catch (RemoteException e) {
789                }
790                mSurfaceHolder.mSurface.release();
791                mCreated = false;
792
793                // Dispose the input channel after removing the window so the Window Manager
794                // doesn't interpret the input channel being closed as an abnormal termination.
795                if (mInputChannel != null) {
796                    mInputChannel.dispose();
797                    mInputChannel = null;
798                }
799            }
800        }
801    }
802
803    class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
804            implements HandlerCaller.Callback {
805        private final HandlerCaller mCaller;
806
807        final IWallpaperConnection mConnection;
808        final IBinder mWindowToken;
809        final int mWindowType;
810        final boolean mIsPreview;
811        int mReqWidth;
812        int mReqHeight;
813
814        Engine mEngine;
815
816        IWallpaperEngineWrapper(WallpaperService context,
817                IWallpaperConnection conn, IBinder windowToken,
818                int windowType, boolean isPreview, int reqWidth, int reqHeight) {
819            if (DEBUG && mCallbackLooper != null) {
820                mCallbackLooper.setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
821            }
822            mCaller = new HandlerCaller(context,
823                    mCallbackLooper != null
824                            ? mCallbackLooper : context.getMainLooper(),
825                    this);
826            mConnection = conn;
827            mWindowToken = windowToken;
828            mWindowType = windowType;
829            mIsPreview = isPreview;
830            mReqWidth = reqWidth;
831            mReqHeight = reqHeight;
832
833            Message msg = mCaller.obtainMessage(DO_ATTACH);
834            mCaller.sendMessage(msg);
835        }
836
837        public void setDesiredSize(int width, int height) {
838            Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height);
839            mCaller.sendMessage(msg);
840        }
841
842        public void setVisibility(boolean visible) {
843            Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
844                    visible ? 1 : 0);
845            mCaller.sendMessage(msg);
846        }
847
848        public void dispatchPointer(MotionEvent event) {
849            if (mEngine != null) {
850                mEngine.dispatchPointer(event);
851            }
852        }
853
854        public void destroy() {
855            Message msg = mCaller.obtainMessage(DO_DETACH);
856            mCaller.sendMessage(msg);
857        }
858
859        public void executeMessage(Message message) {
860            switch (message.what) {
861                case DO_ATTACH: {
862                    try {
863                        mConnection.attachEngine(this);
864                    } catch (RemoteException e) {
865                        Log.w(TAG, "Wallpaper host disappeared", e);
866                        return;
867                    }
868                    Engine engine = onCreateEngine();
869                    mEngine = engine;
870                    mActiveEngines.add(engine);
871                    engine.attach(this);
872                    return;
873                }
874                case DO_DETACH: {
875                    mActiveEngines.remove(mEngine);
876                    mEngine.detach();
877                    return;
878                }
879                case DO_SET_DESIRED_SIZE: {
880                    mEngine.doDesiredSizeChanged(message.arg1, message.arg2);
881                    return;
882                }
883                case MSG_UPDATE_SURFACE:
884                    mEngine.updateSurface(true, false, false);
885                    break;
886                case MSG_VISIBILITY_CHANGED:
887                    if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
888                            + ": " + message.arg1);
889                    mEngine.doVisibilityChanged(message.arg1 != 0);
890                    break;
891                case MSG_WALLPAPER_OFFSETS: {
892                    mEngine.doOffsetsChanged();
893                } break;
894                case MSG_WALLPAPER_COMMAND: {
895                    WallpaperCommand cmd = (WallpaperCommand)message.obj;
896                    mEngine.doCommand(cmd);
897                } break;
898                case MSG_WINDOW_RESIZED: {
899                    final boolean reportDraw = message.arg1 != 0;
900                    mEngine.updateSurface(true, false, reportDraw);
901                    mEngine.doOffsetsChanged();
902                } break;
903                case MSG_TOUCH_EVENT: {
904                    MotionEvent ev = (MotionEvent)message.obj;
905                    synchronized (mEngine.mLock) {
906                        if (mEngine.mPendingMove == ev) {
907                            mEngine.mPendingMove = null;
908                        }
909                    }
910                    if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
911                    mEngine.onTouchEvent(ev);
912                    ev.recycle();
913                } break;
914                default :
915                    Log.w(TAG, "Unknown message type " + message.what);
916            }
917        }
918    }
919
920    /**
921     * Implements the internal {@link IWallpaperService} interface to convert
922     * incoming calls to it back to calls on an {@link WallpaperService}.
923     */
924    class IWallpaperServiceWrapper extends IWallpaperService.Stub {
925        private final WallpaperService mTarget;
926
927        public IWallpaperServiceWrapper(WallpaperService context) {
928            mTarget = context;
929        }
930
931        public void attach(IWallpaperConnection conn, IBinder windowToken,
932                int windowType, boolean isPreview, int reqWidth, int reqHeight) {
933            new IWallpaperEngineWrapper(mTarget, conn, windowToken,
934                    windowType, isPreview, reqWidth, reqHeight);
935        }
936    }
937
938    @Override
939    public void onCreate() {
940        super.onCreate();
941    }
942
943    @Override
944    public void onDestroy() {
945        super.onDestroy();
946        for (int i=0; i<mActiveEngines.size(); i++) {
947            mActiveEngines.get(i).detach();
948        }
949        mActiveEngines.clear();
950    }
951
952    /**
953     * Implement to return the implementation of the internal accessibility
954     * service interface.  Subclasses should not override.
955     */
956    @Override
957    public final IBinder onBind(Intent intent) {
958        return new IWallpaperServiceWrapper(this);
959    }
960
961    /**
962     * This allows subclasses to change the thread that most callbacks
963     * occur on.  Currently hidden because it is mostly needed for the
964     * image wallpaper (which runs in the system process and doesn't want
965     * to get stuck running on that seriously in use main thread).  Not
966     * exposed right now because the semantics of this are not totally
967     * well defined and some callbacks can still happen on the main thread).
968     * @hide
969     */
970    public void setCallbackLooper(Looper looper) {
971        mCallbackLooper = looper;
972    }
973
974    /**
975     * Must be implemented to return a new instance of the wallpaper's engine.
976     * Note that multiple instances may be active at the same time, such as
977     * when the wallpaper is currently set as the active wallpaper and the user
978     * is in the wallpaper picker viewing a preview of it as well.
979     */
980    public abstract Engine onCreateEngine();
981}
982