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