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