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