WallpaperService.java revision 08c7116ab9cd04ad6dd3c04aa1017237e7f409ac
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 android.content.res.TypedArray;
20import android.os.Build;
21import android.os.SystemProperties;
22import android.util.DisplayMetrics;
23import android.util.TypedValue;
24import android.view.ViewRootImpl;
25import android.view.WindowInsets;
26
27import com.android.internal.R;
28import com.android.internal.os.HandlerCaller;
29import com.android.internal.view.BaseIWindow;
30import com.android.internal.view.BaseSurfaceHolder;
31
32import android.annotation.SdkConstant;
33import android.annotation.SdkConstant.SdkConstantType;
34import android.app.Service;
35import android.app.WallpaperManager;
36import android.content.Context;
37import android.content.Intent;
38import android.content.res.Configuration;
39import android.graphics.PixelFormat;
40import android.graphics.Rect;
41import android.hardware.display.DisplayManager;
42import android.hardware.display.DisplayManager.DisplayListener;
43import android.os.Bundle;
44import android.os.IBinder;
45import android.os.Looper;
46import android.os.Message;
47import android.os.RemoteException;
48import android.util.Log;
49import android.view.Display;
50import android.view.Gravity;
51import android.view.IWindowSession;
52import android.view.InputChannel;
53import android.view.InputDevice;
54import android.view.InputEvent;
55import android.view.InputEventReceiver;
56import android.view.MotionEvent;
57import android.view.SurfaceHolder;
58import android.view.View;
59import android.view.ViewGroup;
60import android.view.WindowManager;
61import android.view.WindowManagerGlobal;
62
63import java.io.FileDescriptor;
64import java.io.PrintWriter;
65import java.util.ArrayList;
66
67/**
68 * A wallpaper service is responsible for showing a live wallpaper behind
69 * applications that would like to sit on top of it.  This service object
70 * itself does very little -- its only purpose is to generate instances of
71 * {@link Engine} as needed.  Implementing a wallpaper thus
72 * involves subclassing from this, subclassing an Engine implementation,
73 * and implementing {@link #onCreateEngine()} to return a new instance of
74 * your engine.
75 */
76public abstract class WallpaperService extends Service {
77    /**
78     * The {@link Intent} that must be declared as handled by the service.
79     * To be supported, the service must also require the
80     * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
81     * that other applications can not abuse it.
82     */
83    @SdkConstant(SdkConstantType.SERVICE_ACTION)
84    public static final String SERVICE_INTERFACE =
85            "android.service.wallpaper.WallpaperService";
86
87    /**
88     * Name under which a WallpaperService component publishes information
89     * about itself.  This meta-data must reference an XML resource containing
90     * a <code>&lt;{@link android.R.styleable#Wallpaper wallpaper}&gt;</code>
91     * tag.
92     */
93    public static final String SERVICE_META_DATA = "android.service.wallpaper";
94
95    static final String TAG = "WallpaperService";
96    static final boolean DEBUG = false;
97
98    private static final int DO_ATTACH = 10;
99    private static final int DO_DETACH = 20;
100    private static final int DO_SET_DESIRED_SIZE = 30;
101    private static final int DO_SET_DISPLAY_PADDING = 40;
102
103    private static final int MSG_UPDATE_SURFACE = 10000;
104    private static final int MSG_VISIBILITY_CHANGED = 10010;
105    private static final int MSG_WALLPAPER_OFFSETS = 10020;
106    private static final int MSG_WALLPAPER_COMMAND = 10025;
107    private static final int MSG_WINDOW_RESIZED = 10030;
108    private static final int MSG_WINDOW_MOVED = 10035;
109    private static final int MSG_TOUCH_EVENT = 10040;
110
111    private final ArrayList<Engine> mActiveEngines
112            = new ArrayList<Engine>();
113
114    static final class WallpaperCommand {
115        String action;
116        int x;
117        int y;
118        int z;
119        Bundle extras;
120        boolean sync;
121    }
122
123    /**
124     * The actual implementation of a wallpaper.  A wallpaper service may
125     * have multiple instances running (for example as a real wallpaper
126     * and as a preview), each of which is represented by its own Engine
127     * instance.  You must implement {@link WallpaperService#onCreateEngine()}
128     * to return your concrete Engine implementation.
129     */
130    public class Engine {
131        IWallpaperEngineWrapper mIWallpaperEngine;
132
133        // Copies from mIWallpaperEngine.
134        HandlerCaller mCaller;
135        IWallpaperConnection mConnection;
136        IBinder mWindowToken;
137
138        boolean mInitializing = true;
139        boolean mVisible;
140        boolean mReportedVisible;
141        boolean mDestroyed;
142
143        // Current window state.
144        boolean mCreated;
145        boolean mSurfaceCreated;
146        boolean mIsCreating;
147        boolean mDrawingAllowed;
148        boolean mOffsetsChanged;
149        boolean mFixedSizeAllowed;
150        int mWidth;
151        int mHeight;
152        int mFormat;
153        int mType;
154        int mCurWidth;
155        int mCurHeight;
156        int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
157        int mWindowPrivateFlags =
158                WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
159        int mCurWindowFlags = mWindowFlags;
160        int mCurWindowPrivateFlags = mWindowPrivateFlags;
161        TypedValue mOutsetBottom;
162        final Rect mVisibleInsets = new Rect();
163        final Rect mWinFrame = new Rect();
164        final Rect mOverscanInsets = new Rect();
165        final Rect mContentInsets = new Rect();
166        final Rect mStableInsets = new Rect();
167        final Rect mDispatchedOverscanInsets = new Rect();
168        final Rect mDispatchedContentInsets = new Rect();
169        final Rect mDispatchedStableInsets = new Rect();
170        final Rect mFinalSystemInsets = new Rect();
171        final Rect mFinalStableInsets = new Rect();
172        final Configuration mConfiguration = new Configuration();
173
174        private boolean mIsEmulator;
175        private boolean mIsCircularEmulator;
176        private boolean mWindowIsRound;
177
178        final WindowManager.LayoutParams mLayout
179                = new WindowManager.LayoutParams();
180        IWindowSession mSession;
181        InputChannel mInputChannel;
182
183        final Object mLock = new Object();
184        boolean mOffsetMessageEnqueued;
185        float mPendingXOffset;
186        float mPendingYOffset;
187        float mPendingXOffsetStep;
188        float mPendingYOffsetStep;
189        boolean mPendingSync;
190        MotionEvent mPendingMove;
191
192        DisplayManager mDisplayManager;
193        Display mDisplay;
194
195        final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
196            {
197                mRequestedFormat = PixelFormat.RGBX_8888;
198            }
199
200            @Override
201            public boolean onAllowLockCanvas() {
202                return mDrawingAllowed;
203            }
204
205            @Override
206            public void onRelayoutContainer() {
207                Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE);
208                mCaller.sendMessage(msg);
209            }
210
211            @Override
212            public void onUpdateSurface() {
213                Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE);
214                mCaller.sendMessage(msg);
215            }
216
217            public boolean isCreating() {
218                return mIsCreating;
219            }
220
221            @Override
222            public void setFixedSize(int width, int height) {
223                if (!mFixedSizeAllowed) {
224                    // Regular apps can't do this.  It can only work for
225                    // certain designs of window animations, so you can't
226                    // rely on it.
227                    throw new UnsupportedOperationException(
228                            "Wallpapers currently only support sizing from layout");
229                }
230                super.setFixedSize(width, height);
231            }
232
233            public void setKeepScreenOn(boolean screenOn) {
234                throw new UnsupportedOperationException(
235                        "Wallpapers do not support keep screen on");
236            }
237
238        };
239
240        final class WallpaperInputEventReceiver extends InputEventReceiver {
241            public WallpaperInputEventReceiver(InputChannel inputChannel, Looper looper) {
242                super(inputChannel, looper);
243            }
244
245            @Override
246            public void onInputEvent(InputEvent event) {
247                boolean handled = false;
248                try {
249                    if (event instanceof MotionEvent
250                            && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
251                        MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent)event);
252                        dispatchPointer(dup);
253                        handled = true;
254                    }
255                } finally {
256                    finishInputEvent(event, handled);
257                }
258            }
259        }
260        WallpaperInputEventReceiver mInputEventReceiver;
261
262        final BaseIWindow mWindow = new BaseIWindow() {
263            @Override
264            public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
265                    Rect visibleInsets, Rect stableInsets, boolean reportDraw,
266                    Configuration newConfig) {
267                Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
268                        reportDraw ? 1 : 0);
269                mCaller.sendMessage(msg);
270            }
271
272            @Override
273            public void moved(int newX, int newY) {
274                Message msg = mCaller.obtainMessageII(MSG_WINDOW_MOVED, newX, newY);
275                mCaller.sendMessage(msg);
276            }
277
278            @Override
279            public void dispatchAppVisibility(boolean visible) {
280                // We don't do this in preview mode; we'll let the preview
281                // activity tell us when to run.
282                if (!mIWallpaperEngine.mIsPreview) {
283                    Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
284                            visible ? 1 : 0);
285                    mCaller.sendMessage(msg);
286                }
287            }
288
289            @Override
290            public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
291                    boolean sync) {
292                synchronized (mLock) {
293                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper offsets: " + x + ", " + y);
294                    mPendingXOffset = x;
295                    mPendingYOffset = y;
296                    mPendingXOffsetStep = xStep;
297                    mPendingYOffsetStep = yStep;
298                    if (sync) {
299                        mPendingSync = true;
300                    }
301                    if (!mOffsetMessageEnqueued) {
302                        mOffsetMessageEnqueued = true;
303                        Message msg = mCaller.obtainMessage(MSG_WALLPAPER_OFFSETS);
304                        mCaller.sendMessage(msg);
305                    }
306                }
307            }
308
309            @Override
310            public void dispatchWallpaperCommand(String action, int x, int y,
311                    int z, Bundle extras, boolean sync) {
312                synchronized (mLock) {
313                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper command: " + x + ", " + y);
314                    WallpaperCommand cmd = new WallpaperCommand();
315                    cmd.action = action;
316                    cmd.x = x;
317                    cmd.y = y;
318                    cmd.z = z;
319                    cmd.extras = extras;
320                    cmd.sync = sync;
321                    Message msg = mCaller.obtainMessage(MSG_WALLPAPER_COMMAND);
322                    msg.obj = cmd;
323                    mCaller.sendMessage(msg);
324                }
325            }
326        };
327
328        /**
329         * Provides access to the surface in which this wallpaper is drawn.
330         */
331        public SurfaceHolder getSurfaceHolder() {
332            return mSurfaceHolder;
333        }
334
335        /**
336         * Convenience for {@link WallpaperManager#getDesiredMinimumWidth()
337         * WallpaperManager.getDesiredMinimumWidth()}, returning the width
338         * that the system would like this wallpaper to run in.
339         */
340        public int getDesiredMinimumWidth() {
341            return mIWallpaperEngine.mReqWidth;
342        }
343
344        /**
345         * Convenience for {@link WallpaperManager#getDesiredMinimumHeight()
346         * WallpaperManager.getDesiredMinimumHeight()}, returning the height
347         * that the system would like this wallpaper to run in.
348         */
349        public int getDesiredMinimumHeight() {
350            return mIWallpaperEngine.mReqHeight;
351        }
352
353        /**
354         * Return whether the wallpaper is currently visible to the user,
355         * this is the last value supplied to
356         * {@link #onVisibilityChanged(boolean)}.
357         */
358        public boolean isVisible() {
359            return mReportedVisible;
360        }
361
362        /**
363         * Returns true if this engine is running in preview mode -- that is,
364         * it is being shown to the user before they select it as the actual
365         * wallpaper.
366         */
367        public boolean isPreview() {
368            return mIWallpaperEngine.mIsPreview;
369        }
370
371        /**
372         * Control whether this wallpaper will receive raw touch events
373         * from the window manager as the user interacts with the window
374         * that is currently displaying the wallpaper.  By default they
375         * are turned off.  If enabled, the events will be received in
376         * {@link #onTouchEvent(MotionEvent)}.
377         */
378        public void setTouchEventsEnabled(boolean enabled) {
379            mWindowFlags = enabled
380                    ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
381                    : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
382            if (mCreated) {
383                updateSurface(false, false, false);
384            }
385        }
386
387        /**
388         * Control whether this wallpaper will receive notifications when the wallpaper
389         * has been scrolled. By default, wallpapers will receive notifications, although
390         * the default static image wallpapers do not. It is a performance optimization to
391         * set this to false.
392         *
393         * @param enabled whether the wallpaper wants to receive offset notifications
394         */
395        public void setOffsetNotificationsEnabled(boolean enabled) {
396            mWindowPrivateFlags = enabled
397                    ? (mWindowPrivateFlags |
398                        WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS)
399                    : (mWindowPrivateFlags &
400                        ~WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS);
401            if (mCreated) {
402                updateSurface(false, false, false);
403            }
404        }
405
406        /** {@hide} */
407        public void setFixedSizeAllowed(boolean allowed) {
408            mFixedSizeAllowed = allowed;
409        }
410
411        /**
412         * Called once to initialize the engine.  After returning, the
413         * engine's surface will be created by the framework.
414         */
415        public void onCreate(SurfaceHolder surfaceHolder) {
416        }
417
418        /**
419         * Called right before the engine is going away.  After this the
420         * surface will be destroyed and this Engine object is no longer
421         * valid.
422         */
423        public void onDestroy() {
424        }
425
426        /**
427         * Called to inform you of the wallpaper becoming visible or
428         * hidden.  <em>It is very important that a wallpaper only use
429         * CPU while it is visible.</em>.
430         */
431        public void onVisibilityChanged(boolean visible) {
432        }
433
434        /**
435         * Called with the current insets that are in effect for the wallpaper.
436         * This gives you the part of the overall wallpaper surface that will
437         * generally be visible to the user (ignoring position offsets applied to it).
438         *
439         * @param insets Insets to apply.
440         */
441        public void onApplyWindowInsets(WindowInsets insets) {
442        }
443
444        /**
445         * Called as the user performs touch-screen interaction with the
446         * window that is currently showing this wallpaper.  Note that the
447         * events you receive here are driven by the actual application the
448         * user is interacting with, so if it is slow you will get fewer
449         * move events.
450         */
451        public void onTouchEvent(MotionEvent event) {
452        }
453
454        /**
455         * Called to inform you of the wallpaper's offsets changing
456         * within its contain, corresponding to the container's
457         * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
458         * WallpaperManager.setWallpaperOffsets()}.
459         */
460        public void onOffsetsChanged(float xOffset, float yOffset,
461                float xOffsetStep, float yOffsetStep,
462                int xPixelOffset, int yPixelOffset) {
463        }
464
465        /**
466         * Process a command that was sent to the wallpaper with
467         * {@link WallpaperManager#sendWallpaperCommand}.
468         * The default implementation does nothing, and always returns null
469         * as the result.
470         *
471         * @param action The name of the command to perform.  This tells you
472         * what to do and how to interpret the rest of the arguments.
473         * @param x Generic integer parameter.
474         * @param y Generic integer parameter.
475         * @param z Generic integer parameter.
476         * @param extras Any additional parameters.
477         * @param resultRequested If true, the caller is requesting that
478         * a result, appropriate for the command, be returned back.
479         * @return If returning a result, create a Bundle and place the
480         * result data in to it.  Otherwise return null.
481         */
482        public Bundle onCommand(String action, int x, int y, int z,
483                Bundle extras, boolean resultRequested) {
484            return null;
485        }
486
487        /**
488         * Called when an application has changed the desired virtual size of
489         * the wallpaper.
490         */
491        public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
492        }
493
494        /**
495         * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
496         * SurfaceHolder.Callback.surfaceChanged()}.
497         */
498        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
499        }
500
501        /**
502         * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
503         * SurfaceHolder.Callback.surfaceRedrawNeeded()}.
504         */
505        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
506        }
507
508        /**
509         * Convenience for {@link SurfaceHolder.Callback#surfaceCreated
510         * SurfaceHolder.Callback.surfaceCreated()}.
511         */
512        public void onSurfaceCreated(SurfaceHolder holder) {
513        }
514
515        /**
516         * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
517         * SurfaceHolder.Callback.surfaceDestroyed()}.
518         */
519        public void onSurfaceDestroyed(SurfaceHolder holder) {
520        }
521
522        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
523            out.print(prefix); out.print("mInitializing="); out.print(mInitializing);
524                    out.print(" mDestroyed="); out.println(mDestroyed);
525            out.print(prefix); out.print("mVisible="); out.print(mVisible);
526                    out.print(" mReportedVisible="); out.println(mReportedVisible);
527            out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
528            out.print(prefix); out.print("mCreated="); out.print(mCreated);
529                    out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
530                    out.print(" mIsCreating="); out.print(mIsCreating);
531                    out.print(" mDrawingAllowed="); out.println(mDrawingAllowed);
532            out.print(prefix); out.print("mWidth="); out.print(mWidth);
533                    out.print(" mCurWidth="); out.print(mCurWidth);
534                    out.print(" mHeight="); out.print(mHeight);
535                    out.print(" mCurHeight="); out.println(mCurHeight);
536            out.print(prefix); out.print("mType="); out.print(mType);
537                    out.print(" mWindowFlags="); out.print(mWindowFlags);
538                    out.print(" mCurWindowFlags="); out.println(mCurWindowFlags);
539            out.print(prefix); out.print("mWindowPrivateFlags="); out.print(mWindowPrivateFlags);
540                    out.print(" mCurWindowPrivateFlags="); out.println(mCurWindowPrivateFlags);
541            out.print(prefix); out.print("mVisibleInsets=");
542                    out.print(mVisibleInsets.toShortString());
543                    out.print(" mWinFrame="); out.print(mWinFrame.toShortString());
544                    out.print(" mContentInsets="); out.println(mContentInsets.toShortString());
545            out.print(prefix); out.print("mConfiguration="); out.println(mConfiguration);
546            out.print(prefix); out.print("mLayout="); out.println(mLayout);
547            synchronized (mLock) {
548                out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset);
549                        out.print(" mPendingXOffset="); out.println(mPendingXOffset);
550                out.print(prefix); out.print("mPendingXOffsetStep=");
551                        out.print(mPendingXOffsetStep);
552                        out.print(" mPendingXOffsetStep="); out.println(mPendingXOffsetStep);
553                out.print(prefix); out.print("mOffsetMessageEnqueued=");
554                        out.print(mOffsetMessageEnqueued);
555                        out.print(" mPendingSync="); out.println(mPendingSync);
556                if (mPendingMove != null) {
557                    out.print(prefix); out.print("mPendingMove="); out.println(mPendingMove);
558                }
559            }
560        }
561
562        private void dispatchPointer(MotionEvent event) {
563            if (event.isTouchEvent()) {
564                synchronized (mLock) {
565                    if (event.getAction() == MotionEvent.ACTION_MOVE) {
566                        mPendingMove = event;
567                    } else {
568                        mPendingMove = null;
569                    }
570                }
571                Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
572                mCaller.sendMessage(msg);
573            } else {
574                event.recycle();
575            }
576        }
577
578        void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
579            if (mDestroyed) {
580                Log.w(TAG, "Ignoring updateSurface: destroyed");
581            }
582
583            boolean fixedSize = false;
584            int myWidth = mSurfaceHolder.getRequestedWidth();
585            if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
586            else fixedSize = true;
587            int myHeight = mSurfaceHolder.getRequestedHeight();
588            if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
589            else fixedSize = true;
590
591            final boolean creating = !mCreated;
592            final boolean surfaceCreating = !mSurfaceCreated;
593            final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
594            boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
595            boolean insetsChanged = !mCreated;
596            final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
597            final boolean flagsChanged = mCurWindowFlags != mWindowFlags ||
598                    mCurWindowPrivateFlags != mWindowPrivateFlags;
599            if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
600                    || typeChanged || flagsChanged || redrawNeeded
601                    || !mIWallpaperEngine.mShownReported) {
602
603                if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
604                        + " format=" + formatChanged + " size=" + sizeChanged);
605
606                try {
607                    mWidth = myWidth;
608                    mHeight = myHeight;
609                    mFormat = mSurfaceHolder.getRequestedFormat();
610                    mType = mSurfaceHolder.getRequestedType();
611
612                    mLayout.x = 0;
613                    mLayout.y = 0;
614                    mLayout.width = myWidth;
615                    mLayout.height = myHeight;
616
617                    mLayout.format = mFormat;
618
619                    mCurWindowFlags = mWindowFlags;
620                    mLayout.flags = mWindowFlags
621                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
622                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
623                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
624                            ;
625                    mCurWindowPrivateFlags = mWindowPrivateFlags;
626                    mLayout.privateFlags = mWindowPrivateFlags;
627
628                    mLayout.memoryType = mType;
629                    mLayout.token = mWindowToken;
630
631                    if (!mCreated) {
632                        // Retrieve watch round and outset info
633                        final WindowManager windowService = (WindowManager)getSystemService(
634                                Context.WINDOW_SERVICE);
635                        TypedArray windowStyle = obtainStyledAttributes(
636                                com.android.internal.R.styleable.Window);
637                        final Display display = windowService.getDefaultDisplay();
638                        final boolean shouldUseBottomOutset =
639                                display.getDisplayId() == Display.DEFAULT_DISPLAY;
640                        if (shouldUseBottomOutset && windowStyle.hasValue(
641                                R.styleable.Window_windowOutsetBottom)) {
642                            if (mOutsetBottom == null) mOutsetBottom = new TypedValue();
643                            windowStyle.getValue(R.styleable.Window_windowOutsetBottom,
644                                    mOutsetBottom);
645                        } else {
646                            mOutsetBottom = null;
647                        }
648                        mWindowIsRound = getResources().getBoolean(
649                                com.android.internal.R.bool.config_windowIsRound);
650                        windowStyle.recycle();
651
652                        // detect emulator
653                        mIsEmulator = Build.HARDWARE.contains("goldfish");
654                        mIsCircularEmulator = SystemProperties.getBoolean(
655                                ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false);
656
657                        // Add window
658                        mLayout.type = mIWallpaperEngine.mWindowType;
659                        mLayout.gravity = Gravity.START|Gravity.TOP;
660                        mLayout.setTitle(WallpaperService.this.getClass().getName());
661                        mLayout.windowAnimations =
662                                com.android.internal.R.style.Animation_Wallpaper;
663                        mInputChannel = new InputChannel();
664                        if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
665                            Display.DEFAULT_DISPLAY, mContentInsets, mStableInsets,
666                                mInputChannel) < 0) {
667                            Log.w(TAG, "Failed to add window while updating wallpaper surface.");
668                            return;
669                        }
670                        mCreated = true;
671
672                        mInputEventReceiver = new WallpaperInputEventReceiver(
673                                mInputChannel, Looper.myLooper());
674                    }
675
676                    mSurfaceHolder.mSurfaceLock.lock();
677                    mDrawingAllowed = true;
678
679                    if (!fixedSize) {
680                        mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding);
681                    } else {
682                        mLayout.surfaceInsets.set(0, 0, 0, 0);
683                    }
684                    final int relayoutResult = mSession.relayout(
685                        mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
686                            View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
687                            mVisibleInsets, mStableInsets, mConfiguration, mSurfaceHolder.mSurface);
688
689                    if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
690                            + ", frame=" + mWinFrame);
691
692                    int w = mWinFrame.width();
693                    int h = mWinFrame.height();
694
695                    if (!fixedSize) {
696                        final Rect padding = mIWallpaperEngine.mDisplayPadding;
697                        w += padding.left + padding.right;
698                        h += padding.top + padding.bottom;
699                        mOverscanInsets.left += padding.left;
700                        mOverscanInsets.top += padding.top;
701                        mOverscanInsets.right += padding.right;
702                        mOverscanInsets.bottom += padding.bottom;
703                        mContentInsets.left += padding.left;
704                        mContentInsets.top += padding.top;
705                        mContentInsets.right += padding.right;
706                        mContentInsets.bottom += padding.bottom;
707                        mStableInsets.left += padding.left;
708                        mStableInsets.top += padding.top;
709                        mStableInsets.right += padding.right;
710                        mStableInsets.bottom += padding.bottom;
711                    }
712
713                    if (mCurWidth != w) {
714                        sizeChanged = true;
715                        mCurWidth = w;
716                    }
717                    if (mCurHeight != h) {
718                        sizeChanged = true;
719                        mCurHeight = h;
720                    }
721
722                    insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets);
723                    insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets);
724                    insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets);
725
726                    mSurfaceHolder.setSurfaceFrameSize(w, h);
727                    mSurfaceHolder.mSurfaceLock.unlock();
728
729                    if (!mSurfaceHolder.mSurface.isValid()) {
730                        reportSurfaceDestroyed();
731                        if (DEBUG) Log.v(TAG, "Layout: Surface destroyed");
732                        return;
733                    }
734
735                    boolean didSurface = false;
736
737                    try {
738                        mSurfaceHolder.ungetCallbacks();
739
740                        if (surfaceCreating) {
741                            mIsCreating = true;
742                            didSurface = true;
743                            if (DEBUG) Log.v(TAG, "onSurfaceCreated("
744                                    + mSurfaceHolder + "): " + this);
745                            onSurfaceCreated(mSurfaceHolder);
746                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
747                            if (callbacks != null) {
748                                for (SurfaceHolder.Callback c : callbacks) {
749                                    c.surfaceCreated(mSurfaceHolder);
750                                }
751                            }
752                        }
753
754                        redrawNeeded |= creating || (relayoutResult
755                                & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
756
757                        if (forceReport || creating || surfaceCreating
758                                || formatChanged || sizeChanged) {
759                            if (DEBUG) {
760                                RuntimeException e = new RuntimeException();
761                                e.fillInStackTrace();
762                                Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating
763                                        + " formatChanged=" + formatChanged
764                                        + " sizeChanged=" + sizeChanged, e);
765                            }
766                            if (DEBUG) Log.v(TAG, "onSurfaceChanged("
767                                    + mSurfaceHolder + ", " + mFormat
768                                    + ", " + mCurWidth + ", " + mCurHeight
769                                    + "): " + this);
770                            didSurface = true;
771                            onSurfaceChanged(mSurfaceHolder, mFormat,
772                                    mCurWidth, mCurHeight);
773                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
774                            if (callbacks != null) {
775                                for (SurfaceHolder.Callback c : callbacks) {
776                                    c.surfaceChanged(mSurfaceHolder, mFormat,
777                                            mCurWidth, mCurHeight);
778                                }
779                            }
780                        }
781
782                        if (insetsChanged) {
783                            mDispatchedOverscanInsets.set(mOverscanInsets);
784                            mDispatchedContentInsets.set(mContentInsets);
785                            mDispatchedStableInsets.set(mStableInsets);
786                            final boolean isRound = (mIsEmulator && mIsCircularEmulator)
787                                    || mWindowIsRound;
788                            mFinalSystemInsets.set(mDispatchedOverscanInsets);
789                            mFinalStableInsets.set(mDispatchedStableInsets);
790                            if (mOutsetBottom != null) {
791                                final DisplayMetrics metrics = getResources().getDisplayMetrics();
792                                mFinalSystemInsets.bottom =
793                                        ( (int) mOutsetBottom.getDimension(metrics) )
794                                        + mIWallpaperEngine.mDisplayPadding.bottom;
795                            }
796                            WindowInsets insets = new WindowInsets(mFinalSystemInsets,
797                                    null, mFinalStableInsets, isRound);
798                            onApplyWindowInsets(insets);
799                        }
800
801                        if (redrawNeeded) {
802                            onSurfaceRedrawNeeded(mSurfaceHolder);
803                            SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
804                            if (callbacks != null) {
805                                for (SurfaceHolder.Callback c : callbacks) {
806                                    if (c instanceof SurfaceHolder.Callback2) {
807                                        ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
808                                                mSurfaceHolder);
809                                    }
810                                }
811                            }
812                        }
813
814                        if (didSurface && !mReportedVisible) {
815                            // This wallpaper is currently invisible, but its
816                            // surface has changed.  At this point let's tell it
817                            // again that it is invisible in case the report about
818                            // the surface caused it to start running.  We really
819                            // don't want wallpapers running when not visible.
820                            if (mIsCreating) {
821                                // Some wallpapers will ignore this call if they
822                                // had previously been told they were invisble,
823                                // so if we are creating a new surface then toggle
824                                // the state to get them to notice.
825                                if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: "
826                                        + this);
827                                onVisibilityChanged(true);
828                            }
829                            if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: "
830                                        + this);
831                            onVisibilityChanged(false);
832                        }
833
834                    } finally {
835                        mIsCreating = false;
836                        mSurfaceCreated = true;
837                        if (redrawNeeded) {
838                            mSession.finishDrawing(mWindow);
839                        }
840                        mIWallpaperEngine.reportShown();
841                    }
842                } catch (RemoteException ex) {
843                }
844                if (DEBUG) Log.v(
845                    TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
846                    " w=" + mLayout.width + " h=" + mLayout.height);
847            }
848        }
849
850        void attach(IWallpaperEngineWrapper wrapper) {
851            if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);
852            if (mDestroyed) {
853                return;
854            }
855
856            mIWallpaperEngine = wrapper;
857            mCaller = wrapper.mCaller;
858            mConnection = wrapper.mConnection;
859            mWindowToken = wrapper.mWindowToken;
860            mSurfaceHolder.setSizeFromLayout();
861            mInitializing = true;
862            mSession = WindowManagerGlobal.getWindowSession();
863
864            mWindow.setSession(mSession);
865
866            mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
867            mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler());
868            mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
869
870            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
871            onCreate(mSurfaceHolder);
872
873            mInitializing = false;
874            mReportedVisible = false;
875            updateSurface(false, false, false);
876        }
877
878        void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
879            if (!mDestroyed) {
880                if (DEBUG) Log.v(TAG, "onDesiredSizeChanged("
881                        + desiredWidth + "," + desiredHeight + "): " + this);
882                mIWallpaperEngine.mReqWidth = desiredWidth;
883                mIWallpaperEngine.mReqHeight = desiredHeight;
884                onDesiredSizeChanged(desiredWidth, desiredHeight);
885                doOffsetsChanged(true);
886            }
887        }
888
889        void doDisplayPaddingChanged(Rect padding) {
890            if (!mDestroyed) {
891                if (DEBUG) Log.v(TAG, "onDisplayPaddingChanged(" + padding + "): " + this);
892                if (!mIWallpaperEngine.mDisplayPadding.equals(padding)) {
893                    mIWallpaperEngine.mDisplayPadding.set(padding);
894                    updateSurface(true, false, false);
895                }
896            }
897        }
898
899        void doVisibilityChanged(boolean visible) {
900            if (!mDestroyed) {
901                mVisible = visible;
902                reportVisibility();
903            }
904        }
905
906        void reportVisibility() {
907            if (!mDestroyed) {
908                boolean visible = mVisible
909                        & mDisplay != null && mDisplay.getState() != Display.STATE_OFF;
910                if (mReportedVisible != visible) {
911                    mReportedVisible = visible;
912                    if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible
913                            + "): " + this);
914                    if (visible) {
915                        // If becoming visible, in preview mode the surface
916                        // may have been destroyed so now we need to make
917                        // sure it is re-created.
918                        doOffsetsChanged(false);
919                        updateSurface(false, false, false);
920                    }
921                    onVisibilityChanged(visible);
922                }
923            }
924        }
925
926        void doOffsetsChanged(boolean always) {
927            if (mDestroyed) {
928                return;
929            }
930
931            if (!always && !mOffsetsChanged) {
932                return;
933            }
934
935            float xOffset;
936            float yOffset;
937            float xOffsetStep;
938            float yOffsetStep;
939            boolean sync;
940            synchronized (mLock) {
941                xOffset = mPendingXOffset;
942                yOffset = mPendingYOffset;
943                xOffsetStep = mPendingXOffsetStep;
944                yOffsetStep = mPendingYOffsetStep;
945                sync = mPendingSync;
946                mPendingSync = false;
947                mOffsetMessageEnqueued = false;
948            }
949
950            if (mSurfaceCreated) {
951                if (mReportedVisible) {
952                    if (DEBUG) Log.v(TAG, "Offsets change in " + this
953                            + ": " + xOffset + "," + yOffset);
954                    final int availw = mIWallpaperEngine.mReqWidth-mCurWidth;
955                    final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0;
956                    final int availh = mIWallpaperEngine.mReqHeight-mCurHeight;
957                    final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0;
958                    onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels);
959                } else {
960                    mOffsetsChanged = true;
961                }
962            }
963
964            if (sync) {
965                try {
966                    if (DEBUG) Log.v(TAG, "Reporting offsets change complete");
967                    mSession.wallpaperOffsetsComplete(mWindow.asBinder());
968                } catch (RemoteException e) {
969                }
970            }
971        }
972
973        void doCommand(WallpaperCommand cmd) {
974            Bundle result;
975            if (!mDestroyed) {
976                result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
977                        cmd.extras, cmd.sync);
978            } else {
979                result = null;
980            }
981            if (cmd.sync) {
982                try {
983                    if (DEBUG) Log.v(TAG, "Reporting command complete");
984                    mSession.wallpaperCommandComplete(mWindow.asBinder(), result);
985                } catch (RemoteException e) {
986                }
987            }
988        }
989
990        void reportSurfaceDestroyed() {
991            if (mSurfaceCreated) {
992                mSurfaceCreated = false;
993                mSurfaceHolder.ungetCallbacks();
994                SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
995                if (callbacks != null) {
996                    for (SurfaceHolder.Callback c : callbacks) {
997                        c.surfaceDestroyed(mSurfaceHolder);
998                    }
999                }
1000                if (DEBUG) Log.v(TAG, "onSurfaceDestroyed("
1001                        + mSurfaceHolder + "): " + this);
1002                onSurfaceDestroyed(mSurfaceHolder);
1003            }
1004        }
1005
1006        void detach() {
1007            if (mDestroyed) {
1008                return;
1009            }
1010
1011            mDestroyed = true;
1012
1013            if (mDisplayManager != null) {
1014                mDisplayManager.unregisterDisplayListener(mDisplayListener);
1015            }
1016
1017            if (mVisible) {
1018                mVisible = false;
1019                if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this);
1020                onVisibilityChanged(false);
1021            }
1022
1023            reportSurfaceDestroyed();
1024
1025            if (DEBUG) Log.v(TAG, "onDestroy(): " + this);
1026            onDestroy();
1027
1028            if (mCreated) {
1029                try {
1030                    if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
1031                            + mSurfaceHolder.getSurface() + " of: " + this);
1032
1033                    if (mInputEventReceiver != null) {
1034                        mInputEventReceiver.dispose();
1035                        mInputEventReceiver = null;
1036                    }
1037
1038                    mSession.remove(mWindow);
1039                } catch (RemoteException e) {
1040                }
1041                mSurfaceHolder.mSurface.release();
1042                mCreated = false;
1043
1044                // Dispose the input channel after removing the window so the Window Manager
1045                // doesn't interpret the input channel being closed as an abnormal termination.
1046                if (mInputChannel != null) {
1047                    mInputChannel.dispose();
1048                    mInputChannel = null;
1049                }
1050            }
1051        }
1052
1053        private final DisplayListener mDisplayListener = new DisplayListener() {
1054            @Override
1055            public void onDisplayChanged(int displayId) {
1056                if (mDisplay.getDisplayId() == displayId) {
1057                    reportVisibility();
1058                }
1059            }
1060
1061            @Override
1062            public void onDisplayRemoved(int displayId) {
1063            }
1064
1065            @Override
1066            public void onDisplayAdded(int displayId) {
1067            }
1068        };
1069    }
1070
1071    class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
1072            implements HandlerCaller.Callback {
1073        private final HandlerCaller mCaller;
1074
1075        final IWallpaperConnection mConnection;
1076        final IBinder mWindowToken;
1077        final int mWindowType;
1078        final boolean mIsPreview;
1079        boolean mShownReported;
1080        int mReqWidth;
1081        int mReqHeight;
1082        final Rect mDisplayPadding = new Rect();
1083
1084        Engine mEngine;
1085
1086        IWallpaperEngineWrapper(WallpaperService context,
1087                IWallpaperConnection conn, IBinder windowToken,
1088                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
1089            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
1090            mConnection = conn;
1091            mWindowToken = windowToken;
1092            mWindowType = windowType;
1093            mIsPreview = isPreview;
1094            mReqWidth = reqWidth;
1095            mReqHeight = reqHeight;
1096            mDisplayPadding.set(padding);
1097
1098            Message msg = mCaller.obtainMessage(DO_ATTACH);
1099            mCaller.sendMessage(msg);
1100        }
1101
1102        public void setDesiredSize(int width, int height) {
1103            Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height);
1104            mCaller.sendMessage(msg);
1105        }
1106
1107        public void setDisplayPadding(Rect padding) {
1108            Message msg = mCaller.obtainMessageO(DO_SET_DISPLAY_PADDING, padding);
1109            mCaller.sendMessage(msg);
1110        }
1111
1112        public void setVisibility(boolean visible) {
1113            Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
1114                    visible ? 1 : 0);
1115            mCaller.sendMessage(msg);
1116        }
1117
1118        public void dispatchPointer(MotionEvent event) {
1119            if (mEngine != null) {
1120                mEngine.dispatchPointer(event);
1121            } else {
1122                event.recycle();
1123            }
1124        }
1125
1126        public void dispatchWallpaperCommand(String action, int x, int y,
1127                int z, Bundle extras) {
1128            if (mEngine != null) {
1129                mEngine.mWindow.dispatchWallpaperCommand(action, x, y, z, extras, false);
1130            }
1131        }
1132
1133        public void reportShown() {
1134            if (!mShownReported) {
1135                mShownReported = true;
1136                try {
1137                    mConnection.engineShown(this);
1138                } catch (RemoteException e) {
1139                    Log.w(TAG, "Wallpaper host disappeared", e);
1140                    return;
1141                }
1142            }
1143        }
1144
1145        public void destroy() {
1146            Message msg = mCaller.obtainMessage(DO_DETACH);
1147            mCaller.sendMessage(msg);
1148        }
1149
1150        public void executeMessage(Message message) {
1151            switch (message.what) {
1152                case DO_ATTACH: {
1153                    try {
1154                        mConnection.attachEngine(this);
1155                    } catch (RemoteException e) {
1156                        Log.w(TAG, "Wallpaper host disappeared", e);
1157                        return;
1158                    }
1159                    Engine engine = onCreateEngine();
1160                    mEngine = engine;
1161                    mActiveEngines.add(engine);
1162                    engine.attach(this);
1163                    return;
1164                }
1165                case DO_DETACH: {
1166                    mActiveEngines.remove(mEngine);
1167                    mEngine.detach();
1168                    return;
1169                }
1170                case DO_SET_DESIRED_SIZE: {
1171                    mEngine.doDesiredSizeChanged(message.arg1, message.arg2);
1172                    return;
1173                }
1174                case DO_SET_DISPLAY_PADDING: {
1175                    mEngine.doDisplayPaddingChanged((Rect) message.obj);
1176                }
1177                case MSG_UPDATE_SURFACE:
1178                    mEngine.updateSurface(true, false, false);
1179                    break;
1180                case MSG_VISIBILITY_CHANGED:
1181                    if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
1182                            + ": " + message.arg1);
1183                    mEngine.doVisibilityChanged(message.arg1 != 0);
1184                    break;
1185                case MSG_WALLPAPER_OFFSETS: {
1186                    mEngine.doOffsetsChanged(true);
1187                } break;
1188                case MSG_WALLPAPER_COMMAND: {
1189                    WallpaperCommand cmd = (WallpaperCommand)message.obj;
1190                    mEngine.doCommand(cmd);
1191                } break;
1192                case MSG_WINDOW_RESIZED: {
1193                    final boolean reportDraw = message.arg1 != 0;
1194                    mEngine.updateSurface(true, false, reportDraw);
1195                    mEngine.doOffsetsChanged(true);
1196                } break;
1197                case MSG_WINDOW_MOVED: {
1198                    // Do nothing. What does it mean for a Wallpaper to move?
1199                } break;
1200                case MSG_TOUCH_EVENT: {
1201                    boolean skip = false;
1202                    MotionEvent ev = (MotionEvent)message.obj;
1203                    if (ev.getAction() == MotionEvent.ACTION_MOVE) {
1204                        synchronized (mEngine.mLock) {
1205                            if (mEngine.mPendingMove == ev) {
1206                                mEngine.mPendingMove = null;
1207                            } else {
1208                                // this is not the motion event we are looking for....
1209                                skip = true;
1210                            }
1211                        }
1212                    }
1213                    if (!skip) {
1214                        if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
1215                        mEngine.onTouchEvent(ev);
1216                    }
1217                    ev.recycle();
1218                } break;
1219                default :
1220                    Log.w(TAG, "Unknown message type " + message.what);
1221            }
1222        }
1223    }
1224
1225    /**
1226     * Implements the internal {@link IWallpaperService} interface to convert
1227     * incoming calls to it back to calls on an {@link WallpaperService}.
1228     */
1229    class IWallpaperServiceWrapper extends IWallpaperService.Stub {
1230        private final WallpaperService mTarget;
1231
1232        public IWallpaperServiceWrapper(WallpaperService context) {
1233            mTarget = context;
1234        }
1235
1236        @Override
1237        public void attach(IWallpaperConnection conn, IBinder windowToken,
1238                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
1239            new IWallpaperEngineWrapper(mTarget, conn, windowToken,
1240                    windowType, isPreview, reqWidth, reqHeight, padding);
1241        }
1242    }
1243
1244    @Override
1245    public void onCreate() {
1246        super.onCreate();
1247    }
1248
1249    @Override
1250    public void onDestroy() {
1251        super.onDestroy();
1252        for (int i=0; i<mActiveEngines.size(); i++) {
1253            mActiveEngines.get(i).detach();
1254        }
1255        mActiveEngines.clear();
1256    }
1257
1258    /**
1259     * Implement to return the implementation of the internal accessibility
1260     * service interface.  Subclasses should not override.
1261     */
1262    @Override
1263    public final IBinder onBind(Intent intent) {
1264        return new IWallpaperServiceWrapper(this);
1265    }
1266
1267    /**
1268     * Must be implemented to return a new instance of the wallpaper's engine.
1269     * Note that multiple instances may be active at the same time, such as
1270     * when the wallpaper is currently set as the active wallpaper and the user
1271     * is in the wallpaper picker viewing a preview of it as well.
1272     */
1273    public abstract Engine onCreateEngine();
1274
1275    @Override
1276    protected void dump(FileDescriptor fd, PrintWriter out, String[] args) {
1277        out.print("State of wallpaper "); out.print(this); out.println(":");
1278        for (int i=0; i<mActiveEngines.size(); i++) {
1279            Engine engine = mActiveEngines.get(i);
1280            out.print("  Engine "); out.print(engine); out.println(":");
1281            engine.dump("    ", fd, out, args);
1282        }
1283    }
1284}
1285