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