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