• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.server.wm;
18 
19 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 import static android.view.Surface.SCALING_MODE_SCALE_TO_WINDOW;
21 import static android.view.SurfaceControl.METADATA_OWNER_UID;
22 import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
23 
24 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
25 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
26 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
27 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
28 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
29 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
30 import static com.android.server.wm.WindowSurfaceControllerProto.LAYER;
31 import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
32 
33 import android.graphics.Rect;
34 import android.graphics.Region;
35 import android.os.Debug;
36 import android.os.Trace;
37 import android.util.Slog;
38 import android.util.proto.ProtoOutputStream;
39 import android.view.SurfaceControl;
40 import android.view.WindowContentFrameStats;
41 import android.view.WindowManager;
42 
43 import com.android.server.protolog.common.ProtoLog;
44 
45 import java.io.PrintWriter;
46 
47 class WindowSurfaceController {
48     static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
49 
50     final WindowStateAnimator mAnimator;
51 
52     SurfaceControl mSurfaceControl;
53 
54     /**
55      * WM only uses for deferred transactions.
56      */
57     SurfaceControl mBLASTSurfaceControl;
58 
59     // Should only be set from within setShown().
60     private boolean mSurfaceShown = false;
61     private float mSurfaceX = 0;
62     private float mSurfaceY = 0;
63     private int mSurfaceW = 0;
64     private int mSurfaceH = 0;
65     private Rect mSurfaceCrop = new Rect(0, 0, -1, -1);
66 
67     // Initialize to the identity matrix.
68     private float mLastDsdx = 1;
69     private float mLastDtdx = 0;
70     private float mLastDsdy = 0;
71     private float mLastDtdy = 1;
72 
73     private float mSurfaceAlpha = 0;
74 
75     private int mSurfaceLayer = 0;
76 
77     // Surface flinger doesn't support crop rectangles where width or height is non-positive.
78     // However, we need to somehow handle the situation where the cropping would completely hide
79     // the window. We achieve this by explicitly hiding the surface and not letting it be shown.
80     private boolean mHiddenForCrop = false;
81 
82     // Initially a surface is hidden after just being created.
83     private boolean mHiddenForOtherReasons = true;
84     private final String title;
85 
86     private final WindowManagerService mService;
87 
88     private final int mWindowType;
89     private final Session mWindowSession;
90 
91     private final SurfaceControl.Transaction mTmpTransaction;
92 
93     // Used to track whether we have called detach children on the way to invisibility.
94     boolean mChildrenDetached;
95 
WindowSurfaceController(String name, int w, int h, int format, int flags, WindowStateAnimator animator, int windowType, int ownerUid)96     WindowSurfaceController(String name, int w, int h, int format,
97             int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
98         mAnimator = animator;
99 
100         mSurfaceW = w;
101         mSurfaceH = h;
102 
103         title = name;
104 
105         mService = animator.mService;
106         final WindowState win = animator.mWin;
107         mWindowType = windowType;
108         mWindowSession = win.mSession;
109         mTmpTransaction = mService.mTransactionFactory.get();
110 
111         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
112         final SurfaceControl.Builder b = win.makeSurface()
113                 .setParent(win.getSurfaceControl())
114                 .setName(name)
115                 .setBufferSize(w, h)
116                 .setFormat(format)
117                 .setFlags(flags)
118                 .setMetadata(METADATA_WINDOW_TYPE, windowType)
119                 .setMetadata(METADATA_OWNER_UID, ownerUid)
120                 .setCallsite("WindowSurfaceController");
121 
122         final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags &
123                 WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);
124         if (useBLAST) {
125             b.setContainerLayer();
126         }
127 
128         mSurfaceControl = b.build();
129 
130         if (useBLAST) {
131             mBLASTSurfaceControl = win.makeSurface()
132                 .setParent(mSurfaceControl)
133                 .setName(name + "(BLAST)")
134                 .setHidden(false)
135                 .setBLASTLayer()
136                 .setCallsite("WindowSurfaceController")
137                 .build();
138         }
139 
140         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
141     }
142 
reparentChildrenInTransaction(WindowSurfaceController other)143     void reparentChildrenInTransaction(WindowSurfaceController other) {
144         ProtoLog.i(WM_SHOW_TRANSACTIONS, "REPARENT from: %s to: %s", this, other);
145         if ((mSurfaceControl != null) && (other.mSurfaceControl != null)) {
146             mSurfaceControl.reparentChildren(other.mSurfaceControl);
147         }
148     }
149 
detachChildren()150     void detachChildren() {
151         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SEVER CHILDREN");
152         mChildrenDetached = true;
153         if (mSurfaceControl != null) {
154             mSurfaceControl.detachChildren();
155         }
156     }
157 
hide(SurfaceControl.Transaction transaction, String reason)158     void hide(SurfaceControl.Transaction transaction, String reason) {
159         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
160         mHiddenForOtherReasons = true;
161 
162         mAnimator.destroyPreservedSurfaceLocked();
163         if (mSurfaceShown) {
164             hideSurface(transaction);
165         }
166     }
167 
hideSurface(SurfaceControl.Transaction transaction)168     private void hideSurface(SurfaceControl.Transaction transaction) {
169         if (mSurfaceControl == null) {
170             return;
171         }
172         setShown(false);
173         try {
174             transaction.hide(mSurfaceControl);
175         } catch (RuntimeException e) {
176             Slog.w(TAG, "Exception hiding surface in " + this);
177         }
178     }
179 
destroyNotInTransaction()180     void destroyNotInTransaction() {
181         ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
182                     "Destroying surface %s called by %s", this, Debug.getCallers(8));
183         try {
184             if (mSurfaceControl != null) {
185                 mTmpTransaction.remove(mSurfaceControl).apply();
186             }
187         } catch (RuntimeException e) {
188             Slog.w(TAG, "Error destroying surface in: " + this, e);
189         } finally {
190             setShown(false);
191             mSurfaceControl = null;
192             if (mBLASTSurfaceControl != null) {
193                 mBLASTSurfaceControl.release();
194             }
195         }
196     }
197 
setCropInTransaction(Rect clipRect, boolean recoveringMemory)198     void setCropInTransaction(Rect clipRect, boolean recoveringMemory) {
199         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE CROP %s: %s", clipRect.toShortString(), title);
200         try {
201             if (clipRect.width() > 0 && clipRect.height() > 0) {
202                 if (!clipRect.equals(mSurfaceCrop)) {
203                     mSurfaceControl.setWindowCrop(clipRect);
204                     mSurfaceCrop.set(clipRect);
205                 }
206                 mHiddenForCrop = false;
207                 updateVisibility();
208             } else {
209                 mHiddenForCrop = true;
210                 mAnimator.destroyPreservedSurfaceLocked();
211                 updateVisibility();
212             }
213         } catch (RuntimeException e) {
214             Slog.w(TAG, "Error setting crop surface of " + this
215                     + " crop=" + clipRect.toShortString(), e);
216             if (!recoveringMemory) {
217                 mAnimator.reclaimSomeSurfaceMemory("crop", true);
218             }
219         }
220     }
221 
clearCropInTransaction(boolean recoveringMemory)222     void clearCropInTransaction(boolean recoveringMemory) {
223         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE CLEAR CROP: %s", title);
224         try {
225             Rect clipRect = new Rect(0, 0, -1, -1);
226             if (mSurfaceCrop.equals(clipRect)) {
227                 return;
228             }
229             mSurfaceControl.setWindowCrop(clipRect);
230             mSurfaceCrop.set(clipRect);
231         } catch (RuntimeException e) {
232             Slog.w(TAG, "Error setting clearing crop of " + this, e);
233             if (!recoveringMemory) {
234                 mAnimator.reclaimSomeSurfaceMemory("crop", true);
235             }
236         }
237     }
238 
setPositionInTransaction(float left, float top, boolean recoveringMemory)239     void setPositionInTransaction(float left, float top, boolean recoveringMemory) {
240         setPosition(null, left, top, recoveringMemory);
241     }
242 
setPosition(SurfaceControl.Transaction t, float left, float top, boolean recoveringMemory)243     void setPosition(SurfaceControl.Transaction t, float left, float top,
244             boolean recoveringMemory) {
245         final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
246         if (surfaceMoved) {
247             mSurfaceX = left;
248             mSurfaceY = top;
249 
250             try {
251                 ProtoLog.i(WM_SHOW_TRANSACTIONS,
252                         "SURFACE POS (setPositionInTransaction) @ (%f,%f): %s", left, top, title);
253 
254                 if (t == null) {
255                     mSurfaceControl.setPosition(left, top);
256                 } else {
257                     t.setPosition(mSurfaceControl, left, top);
258                 }
259             } catch (RuntimeException e) {
260                 Slog.w(TAG, "Error positioning surface of " + this
261                         + " pos=(" + left + "," + top + ")", e);
262                 if (!recoveringMemory) {
263                     mAnimator.reclaimSomeSurfaceMemory("position", true);
264                 }
265             }
266         }
267     }
268 
setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy, boolean recoveringMemory)269     void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
270             boolean recoveringMemory) {
271         setMatrix(null, dsdx, dtdx, dtdy, dsdy, false);
272     }
273 
setMatrix(SurfaceControl.Transaction t, float dsdx, float dtdx, float dtdy, float dsdy, boolean recoveringMemory)274     void setMatrix(SurfaceControl.Transaction t, float dsdx, float dtdx,
275             float dtdy, float dsdy, boolean recoveringMemory) {
276         final boolean matrixChanged = mLastDsdx != dsdx || mLastDtdx != dtdx ||
277                                       mLastDtdy != dtdy || mLastDsdy != dsdy;
278         if (!matrixChanged) {
279             return;
280         }
281 
282         mLastDsdx = dsdx;
283         mLastDtdx = dtdx;
284         mLastDtdy = dtdy;
285         mLastDsdy = dsdy;
286 
287         try {
288             ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE MATRIX [%f,%f,%f,%f]: %s",
289                     dsdx, dtdx, dtdy, dsdy, title);
290             if (t == null) {
291                 mSurfaceControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
292             } else {
293                 t.setMatrix(mSurfaceControl, dsdx, dtdx, dtdy, dsdy);
294             }
295         } catch (RuntimeException e) {
296             // If something goes wrong with the surface (such
297             // as running out of memory), don't take down the
298             // entire system.
299             Slog.e(TAG, "Error setting matrix on surface surface" + title
300                     + " MATRIX [" + dsdx + "," + dtdx + "," + dtdy + "," + dsdy + "]", null);
301             if (!recoveringMemory) {
302                 mAnimator.reclaimSomeSurfaceMemory("matrix", true);
303             }
304         }
305     }
306 
setBufferSizeInTransaction(int width, int height, boolean recoveringMemory)307     boolean setBufferSizeInTransaction(int width, int height, boolean recoveringMemory) {
308         final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
309         if (surfaceResized) {
310             mSurfaceW = width;
311             mSurfaceH = height;
312 
313             try {
314                 ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SIZE %dx%d: %s", width, height, title);
315                 mSurfaceControl.setBufferSize(width, height);
316             } catch (RuntimeException e) {
317                 // If something goes wrong with the surface (such
318                 // as running out of memory), don't take down the
319                 // entire system.
320                 Slog.e(TAG, "Error resizing surface of " + title
321                         + " size=(" + width + "x" + height + ")", e);
322                 if (!recoveringMemory) {
323                     mAnimator.reclaimSomeSurfaceMemory("size", true);
324                 }
325                 return false;
326             }
327             return true;
328         }
329         return false;
330     }
331 
prepareToShowInTransaction(float alpha, float dsdx, float dtdx, float dsdy, float dtdy, boolean recoveringMemory)332     boolean prepareToShowInTransaction(float alpha,
333             float dsdx, float dtdx, float dsdy,
334             float dtdy, boolean recoveringMemory) {
335         if (mSurfaceControl != null) {
336             try {
337                 mSurfaceAlpha = alpha;
338                 mSurfaceControl.setAlpha(alpha);
339                 mLastDsdx = dsdx;
340                 mLastDtdx = dtdx;
341                 mLastDsdy = dsdy;
342                 mLastDtdy = dtdy;
343                 mSurfaceControl.setMatrix(
344                         dsdx, dtdx, dsdy, dtdy);
345             } catch (RuntimeException e) {
346                 Slog.w(TAG, "Error updating surface in " + title, e);
347                 if (!recoveringMemory) {
348                     mAnimator.reclaimSomeSurfaceMemory("update", true);
349                 }
350                 return false;
351             }
352         }
353         return true;
354     }
355 
setTransparentRegionHint(final Region region)356     void setTransparentRegionHint(final Region region) {
357         if (mSurfaceControl == null) {
358             Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
359             return;
360         }
361         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
362         mService.openSurfaceTransaction();
363         try {
364             mSurfaceControl.setTransparentRegionHint(region);
365         } finally {
366             mService.closeSurfaceTransaction("setTransparentRegion");
367             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
368                     "<<< CLOSE TRANSACTION setTransparentRegion");
369         }
370     }
371 
setOpaque(boolean isOpaque)372     void setOpaque(boolean isOpaque) {
373         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
374 
375         if (mSurfaceControl == null) {
376             return;
377         }
378         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked");
379         mService.openSurfaceTransaction();
380         try {
381             mSurfaceControl.setOpaque(isOpaque);
382         } finally {
383             mService.closeSurfaceTransaction("setOpaqueLocked");
384             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
385         }
386     }
387 
setSecure(boolean isSecure)388     void setSecure(boolean isSecure) {
389         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, title);
390 
391         if (mSurfaceControl == null) {
392             return;
393         }
394         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked");
395         mService.openSurfaceTransaction();
396         try {
397             mSurfaceControl.setSecure(isSecure);
398         } finally {
399             mService.closeSurfaceTransaction("setSecure");
400             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
401         }
402     }
403 
setColorSpaceAgnostic(boolean agnostic)404     void setColorSpaceAgnostic(boolean agnostic) {
405         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
406 
407         if (mSurfaceControl == null) {
408             return;
409         }
410         if (SHOW_LIGHT_TRANSACTIONS) {
411             Slog.i(TAG, ">>> OPEN TRANSACTION setColorSpaceAgnosticLocked");
412         }
413         mService.openSurfaceTransaction();
414         try {
415             mSurfaceControl.setColorSpaceAgnostic(agnostic);
416         } finally {
417             mService.closeSurfaceTransaction("setColorSpaceAgnostic");
418             if (SHOW_LIGHT_TRANSACTIONS) {
419                 Slog.i(TAG, "<<< CLOSE TRANSACTION setColorSpaceAgnosticLocked");
420             }
421         }
422     }
423 
getContainerRect(Rect rect)424     void getContainerRect(Rect rect) {
425         mAnimator.getContainerRect(rect);
426     }
427 
showRobustlyInTransaction()428     boolean showRobustlyInTransaction() {
429         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
430         if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
431                 + " during relayout");
432         mHiddenForOtherReasons = false;
433         return updateVisibility();
434     }
435 
updateVisibility()436     private boolean updateVisibility() {
437         if (mHiddenForCrop || mHiddenForOtherReasons) {
438             if (mSurfaceShown) {
439                 hideSurface(mTmpTransaction);
440                 SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
441             }
442             return false;
443         } else {
444             if (!mSurfaceShown) {
445                 return showSurface();
446             } else {
447                 return true;
448             }
449         }
450     }
451 
showSurface()452     private boolean showSurface() {
453         try {
454             setShown(true);
455             mSurfaceControl.show();
456             return true;
457         } catch (RuntimeException e) {
458             Slog.w(TAG, "Failure showing surface " + mSurfaceControl + " in " + this, e);
459         }
460 
461         mAnimator.reclaimSomeSurfaceMemory("show", true);
462 
463         return false;
464     }
465 
deferTransactionUntil(SurfaceControl barrier, long frame)466     void deferTransactionUntil(SurfaceControl barrier, long frame) {
467         // TODO: Logging
468         mSurfaceControl.deferTransactionUntil(barrier, frame);
469     }
470 
forceScaleableInTransaction(boolean force)471     void forceScaleableInTransaction(boolean force) {
472         // -1 means we don't override the default or client specified
473         // scaling mode.
474         int scalingMode = force ? SCALING_MODE_SCALE_TO_WINDOW : -1;
475         mSurfaceControl.setOverrideScalingMode(scalingMode);
476     }
477 
clearWindowContentFrameStats()478     boolean clearWindowContentFrameStats() {
479         if (mSurfaceControl == null) {
480             return false;
481         }
482         return mSurfaceControl.clearContentFrameStats();
483     }
484 
getWindowContentFrameStats(WindowContentFrameStats outStats)485     boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
486         if (mSurfaceControl == null) {
487             return false;
488         }
489         return mSurfaceControl.getContentFrameStats(outStats);
490     }
491 
492 
hasSurface()493     boolean hasSurface() {
494         return mSurfaceControl != null;
495     }
496 
getSurfaceControl(SurfaceControl outSurfaceControl)497     void getSurfaceControl(SurfaceControl outSurfaceControl) {
498         outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
499     }
500 
getBLASTSurfaceControl(SurfaceControl outSurfaceControl)501     void getBLASTSurfaceControl(SurfaceControl outSurfaceControl) {
502         if (mBLASTSurfaceControl != null) {
503             outSurfaceControl.copyFrom(mBLASTSurfaceControl, "WindowSurfaceController.getBLASTSurfaceControl");
504         }
505     }
506 
getLayer()507     int getLayer() {
508         return mSurfaceLayer;
509     }
510 
getShown()511     boolean getShown() {
512         return mSurfaceShown;
513     }
514 
setShown(boolean surfaceShown)515     void setShown(boolean surfaceShown) {
516         mSurfaceShown = surfaceShown;
517 
518         mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
519 
520         mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
521 
522         if (mWindowSession != null) {
523             mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
524         }
525     }
526 
getX()527     float getX() {
528         return mSurfaceX;
529     }
530 
getY()531     float getY() {
532         return mSurfaceY;
533     }
534 
getWidth()535     int getWidth() {
536         return mSurfaceW;
537     }
538 
getHeight()539     int getHeight() {
540         return mSurfaceH;
541     }
542 
543     /**
544      * Returns the Surface which the client-framework ViewRootImpl will be using.
545      * This is either the WSA SurfaceControl or it's BLAST child surface.
546      * This has too main uses:
547      * 1. This is the Surface the client will add children to, we use this to make
548      *    sure we don't reparent the BLAST surface itself when calling reparentChildren
549      * 2. We use this as the barrier Surface for some deferTransaction operations.
550      */
getClientViewRootSurface()551     SurfaceControl getClientViewRootSurface() {
552         if (mBLASTSurfaceControl != null) {
553             return mBLASTSurfaceControl;
554         }
555         return mSurfaceControl;
556     }
557 
dumpDebug(ProtoOutputStream proto, long fieldId)558     void dumpDebug(ProtoOutputStream proto, long fieldId) {
559         final long token = proto.start(fieldId);
560         proto.write(SHOWN, mSurfaceShown);
561         proto.write(LAYER, mSurfaceLayer);
562         proto.end(token);
563     }
564 
dump(PrintWriter pw, String prefix, boolean dumpAll)565     public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
566         if (dumpAll) {
567             pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
568         }
569         pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
570         pw.print(" layer="); pw.print(mSurfaceLayer);
571         pw.print(" alpha="); pw.print(mSurfaceAlpha);
572         pw.print(" rect=("); pw.print(mSurfaceX);
573         pw.print(","); pw.print(mSurfaceY);
574         pw.print(") "); pw.print(mSurfaceW);
575         pw.print(" x "); pw.print(mSurfaceH);
576         pw.print(" transform=("); pw.print(mLastDsdx); pw.print(", ");
577         pw.print(mLastDtdx); pw.print(", "); pw.print(mLastDsdy);
578         pw.print(", "); pw.print(mLastDtdy); pw.println(")");
579     }
580 
581     @Override
toString()582     public String toString() {
583         return mSurfaceControl.toString();
584     }
585 }
586