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