• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.systemui.statusbar.phone;
18 
19 import android.content.ComponentCallbacks2;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.os.SystemClock;
23 import android.os.Trace;
24 import android.view.KeyEvent;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.view.ViewRootImpl;
28 import android.view.WindowManagerGlobal;
29 
30 import com.android.internal.widget.LockPatternUtils;
31 import com.android.keyguard.KeyguardUpdateMonitor;
32 import com.android.keyguard.ViewMediatorCallback;
33 import com.android.systemui.SystemUIFactory;
34 import com.android.systemui.statusbar.CommandQueue;
35 import com.android.systemui.statusbar.RemoteInputController;
36 
37 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
38 
39 /**
40  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
41  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
42  * which is in turn, reported to this class by the current
43  * {@link com.android.keyguard.KeyguardViewBase}.
44  */
45 public class StatusBarKeyguardViewManager implements RemoteInputController.Callback {
46 
47     // When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
48     private static final long HIDE_TIMING_CORRECTION_MS = -3 * 16;
49 
50     // Delay for showing the navigation bar when the bouncer appears. This should be kept in sync
51     // with the appear animations of the PIN/pattern/password views.
52     private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320;
53 
54     private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200;
55 
56     // Duration of the Keyguard dismissal animation in case the user is currently locked. This is to
57     // make everything a bit slower to bridge a gap until the user is unlocked and home screen has
58     // dranw its first frame.
59     private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
60 
61     private static String TAG = "StatusBarKeyguardViewManager";
62 
63     protected final Context mContext;
64 
65     protected LockPatternUtils mLockPatternUtils;
66     protected ViewMediatorCallback mViewMediatorCallback;
67     protected PhoneStatusBar mPhoneStatusBar;
68     private ScrimController mScrimController;
69     private FingerprintUnlockController mFingerprintUnlockController;
70 
71     private ViewGroup mContainer;
72     private StatusBarWindowManager mStatusBarWindowManager;
73 
74     private boolean mDeviceInteractive = false;
75     private boolean mScreenTurnedOn;
76     protected KeyguardBouncer mBouncer;
77     protected boolean mShowing;
78     protected boolean mOccluded;
79     protected boolean mRemoteInputActive;
80 
81     protected boolean mFirstUpdate = true;
82     protected boolean mLastShowing;
83     protected boolean mLastOccluded;
84     private boolean mLastBouncerShowing;
85     private boolean mLastBouncerDismissible;
86     protected boolean mLastRemoteInputActive;
87 
88     private OnDismissAction mAfterKeyguardGoneAction;
89     private boolean mDeviceWillWakeUp;
90     private boolean mDeferScrimFadeOut;
91 
StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback, LockPatternUtils lockPatternUtils)92     public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback,
93             LockPatternUtils lockPatternUtils) {
94         mContext = context;
95         mViewMediatorCallback = callback;
96         mLockPatternUtils = lockPatternUtils;
97     }
98 
registerStatusBar(PhoneStatusBar phoneStatusBar, ViewGroup container, StatusBarWindowManager statusBarWindowManager, ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController)99     public void registerStatusBar(PhoneStatusBar phoneStatusBar,
100             ViewGroup container, StatusBarWindowManager statusBarWindowManager,
101             ScrimController scrimController,
102             FingerprintUnlockController fingerprintUnlockController) {
103         mPhoneStatusBar = phoneStatusBar;
104         mContainer = container;
105         mStatusBarWindowManager = statusBarWindowManager;
106         mScrimController = scrimController;
107         mFingerprintUnlockController = fingerprintUnlockController;
108         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
109                 mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container);
110     }
111 
112     /**
113      * Show the keyguard.  Will handle creating and attaching to the view manager
114      * lazily.
115      */
show(Bundle options)116     public void show(Bundle options) {
117         mShowing = true;
118         mStatusBarWindowManager.setKeyguardShowing(true);
119         mScrimController.abortKeyguardFadingOut();
120         reset();
121     }
122 
123     /**
124      * Shows the notification keyguard or the bouncer depending on
125      * {@link KeyguardBouncer#needsFullscreenBouncer()}.
126      */
showBouncerOrKeyguard()127     protected void showBouncerOrKeyguard() {
128         if (mBouncer.needsFullscreenBouncer()) {
129 
130             // The keyguard might be showing (already). So we need to hide it.
131             mPhoneStatusBar.hideKeyguard();
132             mBouncer.show(true /* resetSecuritySelection */);
133         } else {
134             mPhoneStatusBar.showKeyguard();
135             mBouncer.hide(false /* destroyView */);
136             mBouncer.prepare();
137         }
138     }
139 
showBouncer()140     private void showBouncer() {
141         if (mShowing) {
142             mBouncer.show(false /* resetSecuritySelection */);
143         }
144         updateStates();
145     }
146 
dismissWithAction(OnDismissAction r, Runnable cancelAction, boolean afterKeyguardGone)147     public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
148             boolean afterKeyguardGone) {
149         if (mShowing) {
150             if (!afterKeyguardGone) {
151                 mBouncer.showWithDismissAction(r, cancelAction);
152             } else {
153                 mBouncer.show(false /* resetSecuritySelection */);
154                 mAfterKeyguardGoneAction = r;
155             }
156         }
157         updateStates();
158     }
159 
160     /**
161      * Reset the state of the view.
162      */
reset()163     public void reset() {
164         if (mShowing) {
165             if (mOccluded) {
166                 mPhoneStatusBar.hideKeyguard();
167                 mPhoneStatusBar.stopWaitingForKeyguardExit();
168                 mBouncer.hide(false /* destroyView */);
169             } else {
170                 showBouncerOrKeyguard();
171             }
172             KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset();
173             updateStates();
174         }
175     }
176 
onStartedGoingToSleep()177     public void onStartedGoingToSleep() {
178         mPhoneStatusBar.onStartedGoingToSleep();
179     }
180 
onFinishedGoingToSleep()181     public void onFinishedGoingToSleep() {
182         mDeviceInteractive = false;
183         mPhoneStatusBar.onFinishedGoingToSleep();
184         mBouncer.onScreenTurnedOff();
185     }
186 
onStartedWakingUp()187     public void onStartedWakingUp() {
188         Trace.beginSection("StatusBarKeyguardViewManager#onStartedWakingUp");
189         mDeviceInteractive = true;
190         mDeviceWillWakeUp = false;
191         mPhoneStatusBar.onStartedWakingUp();
192         Trace.endSection();
193     }
194 
onScreenTurningOn()195     public void onScreenTurningOn() {
196         Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurningOn");
197         mPhoneStatusBar.onScreenTurningOn();
198         Trace.endSection();
199     }
200 
isScreenTurnedOn()201     public boolean isScreenTurnedOn() {
202         return mScreenTurnedOn;
203     }
204 
onScreenTurnedOn()205     public void onScreenTurnedOn() {
206         Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
207         mScreenTurnedOn = true;
208         if (mDeferScrimFadeOut) {
209             mDeferScrimFadeOut = false;
210             animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
211                     true /* skipFirstFrame */);
212             updateStates();
213         }
214         mPhoneStatusBar.onScreenTurnedOn();
215         Trace.endSection();
216     }
217 
218     @Override
onRemoteInputActive(boolean active)219     public void onRemoteInputActive(boolean active) {
220         mRemoteInputActive = active;
221         updateStates();
222     }
223 
onScreenTurnedOff()224     public void onScreenTurnedOff() {
225         mScreenTurnedOn = false;
226         mPhoneStatusBar.onScreenTurnedOff();
227     }
228 
notifyDeviceWakeUpRequested()229     public void notifyDeviceWakeUpRequested() {
230         mDeviceWillWakeUp = !mDeviceInteractive;
231     }
232 
verifyUnlock()233     public void verifyUnlock() {
234         dismiss();
235     }
236 
setNeedsInput(boolean needsInput)237     public void setNeedsInput(boolean needsInput) {
238         mStatusBarWindowManager.setKeyguardNeedsInput(needsInput);
239     }
240 
isUnlockWithWallpaper()241     public boolean isUnlockWithWallpaper() {
242         return mStatusBarWindowManager.isShowingWallpaper();
243     }
244 
setOccluded(boolean occluded)245     public void setOccluded(boolean occluded) {
246         if (occluded && !mOccluded && mShowing) {
247             if (mPhoneStatusBar.isInLaunchTransition()) {
248                 mOccluded = true;
249                 mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
250                         new Runnable() {
251                             @Override
252                             public void run() {
253                                 mStatusBarWindowManager.setKeyguardOccluded(mOccluded);
254                                 reset();
255                             }
256                         });
257                 return;
258             }
259         }
260         mOccluded = occluded;
261         mPhoneStatusBar.updateMediaMetaData(false, false);
262         mStatusBarWindowManager.setKeyguardOccluded(occluded);
263         reset();
264     }
265 
isOccluded()266     public boolean isOccluded() {
267         return mOccluded;
268     }
269 
270     /**
271      * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
272      * security view of the bouncer.
273      *
274      * @param finishRunnable the runnable to be run after the animation finished, or {@code null} if
275      *                       no action should be run
276      */
startPreHideAnimation(Runnable finishRunnable)277     public void startPreHideAnimation(Runnable finishRunnable) {
278         if (mBouncer.isShowing()) {
279             mBouncer.startPreHideAnimation(finishRunnable);
280         } else if (finishRunnable != null) {
281             finishRunnable.run();
282         }
283     }
284 
285     /**
286      * Hides the keyguard view
287      */
hide(long startTime, long fadeoutDuration)288     public void hide(long startTime, long fadeoutDuration) {
289         mShowing = false;
290 
291         if (!KeyguardUpdateMonitor.getInstance(mContext).isUserUnlocked()) {
292             fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
293         }
294         long uptimeMillis = SystemClock.uptimeMillis();
295         long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
296 
297         if (mPhoneStatusBar.isInLaunchTransition() ) {
298             mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
299                 @Override
300                 public void run() {
301                     mStatusBarWindowManager.setKeyguardShowing(false);
302                     mStatusBarWindowManager.setKeyguardFadingAway(true);
303                     mBouncer.hide(true /* destroyView */);
304                     updateStates();
305                     mScrimController.animateKeyguardFadingOut(
306                             PhoneStatusBar.FADE_KEYGUARD_START_DELAY,
307                             PhoneStatusBar.FADE_KEYGUARD_DURATION, null,
308                             false /* skipFirstFrame */);
309                 }
310             }, new Runnable() {
311                 @Override
312                 public void run() {
313                     mPhoneStatusBar.hideKeyguard();
314                     mStatusBarWindowManager.setKeyguardFadingAway(false);
315                     mViewMediatorCallback.keyguardGone();
316                     executeAfterKeyguardGoneAction();
317                 }
318             });
319         } else {
320             if (mFingerprintUnlockController.getMode()
321                     == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) {
322                 mFingerprintUnlockController.startKeyguardFadingAway();
323                 mPhoneStatusBar.setKeyguardFadingAway(startTime, 0, 240);
324                 mStatusBarWindowManager.setKeyguardFadingAway(true);
325                 mPhoneStatusBar.fadeKeyguardWhilePulsing();
326                 animateScrimControllerKeyguardFadingOut(0, 240, new Runnable() {
327                     @Override
328                     public void run() {
329                         mPhoneStatusBar.hideKeyguard();
330                     }
331                 }, false /* skipFirstFrame */);
332             } else {
333                 mFingerprintUnlockController.startKeyguardFadingAway();
334                 mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
335                 boolean staying = mPhoneStatusBar.hideKeyguard();
336                 if (!staying) {
337                     mStatusBarWindowManager.setKeyguardFadingAway(true);
338                     if (mFingerprintUnlockController.getMode()
339                             == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
340                         if (!mScreenTurnedOn) {
341                             mDeferScrimFadeOut = true;
342                         } else {
343 
344                             // Screen is already on, don't defer with fading out.
345                             animateScrimControllerKeyguardFadingOut(0,
346                                     WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
347                                     true /* skipFirstFrame */);
348                         }
349                     } else {
350                         animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
351                                 false /* skipFirstFrame */);
352                     }
353                 } else {
354                     mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
355                     mPhoneStatusBar.finishKeyguardFadingAway();
356                 }
357             }
358             mStatusBarWindowManager.setKeyguardShowing(false);
359             mBouncer.hide(true /* destroyView */);
360             mViewMediatorCallback.keyguardGone();
361             executeAfterKeyguardGoneAction();
362             updateStates();
363         }
364     }
365 
onDensityOrFontScaleChanged()366     public void onDensityOrFontScaleChanged() {
367         mBouncer.hide(true /* destroyView */);
368     }
369 
animateScrimControllerKeyguardFadingOut(long delay, long duration, boolean skipFirstFrame)370     private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
371             boolean skipFirstFrame) {
372         animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
373                 skipFirstFrame);
374     }
375 
animateScrimControllerKeyguardFadingOut(long delay, long duration, final Runnable endRunnable, boolean skipFirstFrame)376     private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
377             final Runnable endRunnable, boolean skipFirstFrame) {
378         Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
379         mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
380             @Override
381             public void run() {
382                 if (endRunnable != null) {
383                     endRunnable.run();
384                 }
385                 mStatusBarWindowManager.setKeyguardFadingAway(false);
386                 mPhoneStatusBar.finishKeyguardFadingAway();
387                 mFingerprintUnlockController.finishKeyguardFadingAway();
388                 WindowManagerGlobal.getInstance().trimMemory(
389                         ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
390                 Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
391             }
392         }, skipFirstFrame);
393     }
394 
executeAfterKeyguardGoneAction()395     private void executeAfterKeyguardGoneAction() {
396         if (mAfterKeyguardGoneAction != null) {
397             mAfterKeyguardGoneAction.onDismiss();
398             mAfterKeyguardGoneAction = null;
399         }
400     }
401 
402     /**
403      * Dismisses the keyguard by going to the next screen or making it gone.
404      */
dismiss()405     public void dismiss() {
406         if (mDeviceInteractive || mDeviceWillWakeUp) {
407             showBouncer();
408         }
409     }
410 
411     /**
412      * WARNING: This method might cause Binder calls.
413      */
isSecure()414     public boolean isSecure() {
415         return mBouncer.isSecure();
416     }
417 
418     /**
419      * @return Whether the keyguard is showing
420      */
isShowing()421     public boolean isShowing() {
422         return mShowing;
423     }
424 
425     /**
426      * Notifies this manager that the back button has been pressed.
427      *
428      * @return whether the back press has been handled
429      */
onBackPressed()430     public boolean onBackPressed() {
431         if (mBouncer.isShowing()) {
432             mPhoneStatusBar.endAffordanceLaunch();
433             reset();
434             return true;
435         }
436         return false;
437     }
438 
isBouncerShowing()439     public boolean isBouncerShowing() {
440         return mBouncer.isShowing();
441     }
442 
getNavBarShowDelay()443     private long getNavBarShowDelay() {
444         if (mPhoneStatusBar.isKeyguardFadingAway()) {
445             return mPhoneStatusBar.getKeyguardFadingAwayDelay();
446         } else {
447 
448             // Keyguard is not going away, thus we are showing the navigation bar because the
449             // bouncer is appearing.
450             return NAV_BAR_SHOW_DELAY_BOUNCER;
451         }
452     }
453 
454     private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() {
455         @Override
456         public void run() {
457             mPhoneStatusBar.getNavigationBarView().setVisibility(View.VISIBLE);
458         }
459     };
460 
updateStates()461     protected void updateStates() {
462         int vis = mContainer.getSystemUiVisibility();
463         boolean showing = mShowing;
464         boolean occluded = mOccluded;
465         boolean bouncerShowing = mBouncer.isShowing();
466         boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
467         boolean remoteInputActive = mRemoteInputActive;
468 
469         if ((bouncerDismissible || !showing || remoteInputActive) !=
470                 (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
471                 || mFirstUpdate) {
472             if (bouncerDismissible || !showing || remoteInputActive) {
473                 mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK);
474             } else {
475                 mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK);
476             }
477         }
478 
479         boolean navBarVisible = isNavBarVisible();
480         boolean lastNavBarVisible = getLastNavBarVisible();
481         if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
482             if (mPhoneStatusBar.getNavigationBarView() != null) {
483                 if (navBarVisible) {
484                     long delay = getNavBarShowDelay();
485                     if (delay == 0) {
486                         mMakeNavigationBarVisibleRunnable.run();
487                     } else {
488                         mContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable,
489                                 delay);
490                     }
491                 } else {
492                     mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
493                     mPhoneStatusBar.getNavigationBarView().setVisibility(View.GONE);
494                 }
495             }
496         }
497 
498         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
499             mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
500             mPhoneStatusBar.setBouncerShowing(bouncerShowing);
501             mScrimController.setBouncerShowing(bouncerShowing);
502         }
503 
504         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
505         if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
506             updateMonitor.onKeyguardVisibilityChanged(showing && !occluded);
507         }
508         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
509             updateMonitor.sendKeyguardBouncerChanged(bouncerShowing);
510         }
511 
512         mFirstUpdate = false;
513         mLastShowing = showing;
514         mLastOccluded = occluded;
515         mLastBouncerShowing = bouncerShowing;
516         mLastBouncerDismissible = bouncerDismissible;
517         mLastRemoteInputActive = remoteInputActive;
518 
519         mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
520     }
521 
522     /**
523      * @return Whether the navigation bar should be made visible based on the current state.
524      */
isNavBarVisible()525     protected boolean isNavBarVisible() {
526         return !(mShowing && !mOccluded) || mBouncer.isShowing() || mRemoteInputActive;
527     }
528 
529     /**
530      * @return Whether the navigation bar was made visible based on the last known state.
531      */
getLastNavBarVisible()532     protected boolean getLastNavBarVisible() {
533         return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive;
534     }
535 
shouldDismissOnMenuPressed()536     public boolean shouldDismissOnMenuPressed() {
537         return mBouncer.shouldDismissOnMenuPressed();
538     }
539 
interceptMediaKey(KeyEvent event)540     public boolean interceptMediaKey(KeyEvent event) {
541         return mBouncer.interceptMediaKey(event);
542     }
543 
onActivityDrawn()544     public void onActivityDrawn() {
545         if (mPhoneStatusBar.isCollapsing()) {
546             mPhoneStatusBar.addPostCollapseAction(new Runnable() {
547                 @Override
548                 public void run() {
549                     mViewMediatorCallback.readyForKeyguardDone();
550                 }
551             });
552         } else {
553             mViewMediatorCallback.readyForKeyguardDone();
554         }
555     }
556 
shouldDisableWindowAnimationsForUnlock()557     public boolean shouldDisableWindowAnimationsForUnlock() {
558         return mPhoneStatusBar.isInLaunchTransition();
559     }
560 
isGoingToNotificationShade()561     public boolean isGoingToNotificationShade() {
562         return mPhoneStatusBar.isGoingToNotificationShade();
563     }
564 
isSecure(int userId)565     public boolean isSecure(int userId) {
566         return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId);
567     }
568 
isInputRestricted()569     public boolean isInputRestricted() {
570         return mViewMediatorCallback.isInputRestricted();
571     }
572 
keyguardGoingAway()573     public void keyguardGoingAway() {
574         mPhoneStatusBar.keyguardGoingAway();
575     }
576 
animateCollapsePanels(float speedUpFactor)577     public void animateCollapsePanels(float speedUpFactor) {
578         mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
579                 false /* delayed */, speedUpFactor);
580     }
581 
582     /**
583      * Notifies that the user has authenticated by other means than using the bouncer, for example,
584      * fingerprint.
585      */
notifyKeyguardAuthenticated(boolean strongAuth)586     public void notifyKeyguardAuthenticated(boolean strongAuth) {
587         mBouncer.notifyKeyguardAuthenticated(strongAuth);
588     }
589 
showBouncerMessage(String message, int color)590     public void showBouncerMessage(String message, int color) {
591         mBouncer.showMessage(message, color);
592     }
593 
getViewRootImpl()594     public ViewRootImpl getViewRootImpl() {
595         return mPhoneStatusBar.getStatusBarView().getViewRootImpl();
596     }
597 }
598