Home | History | Annotate | Download | only in phone
      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.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.PropertyValuesHolder;
     22 import android.animation.ValueAnimator;
     23 import android.content.Context;
     24 import android.graphics.Color;
     25 import android.graphics.Rect;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.view.ViewTreeObserver;
     29 import android.view.animation.DecelerateInterpolator;
     30 import android.view.animation.Interpolator;
     31 import android.view.animation.PathInterpolator;
     32 
     33 import com.android.keyguard.KeyguardUpdateMonitor;
     34 import com.android.systemui.R;
     35 import com.android.systemui.statusbar.ExpandableNotificationRow;
     36 import com.android.systemui.statusbar.NotificationData;
     37 import com.android.systemui.statusbar.ScrimView;
     38 import com.android.systemui.statusbar.policy.HeadsUpManager;
     39 import com.android.systemui.statusbar.stack.StackStateAnimator;
     40 
     41 /**
     42  * Controls both the scrim behind the notifications and in front of the notifications (when a
     43  * security method gets shown).
     44  */
     45 public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
     46         HeadsUpManager.OnHeadsUpChangedListener {
     47     public static final long ANIMATION_DURATION = 220;
     48     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
     49             = new PathInterpolator(0f, 0, 0.7f, 1f);
     50     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
     51             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
     52     private static final float SCRIM_BEHIND_ALPHA = 0.62f;
     53     protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
     54     protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
     55     private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
     56     private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = 0.85f;
     57     private static final int TAG_KEY_ANIM = R.id.scrim;
     58     private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
     59     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
     60     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
     61 
     62     protected final ScrimView mScrimBehind;
     63     private final ScrimView mScrimInFront;
     64     private final UnlockMethodCache mUnlockMethodCache;
     65     private final View mHeadsUpScrim;
     66     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     67 
     68     private float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
     69     private float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
     70     private float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
     71 
     72     protected boolean mKeyguardShowing;
     73     private float mFraction;
     74 
     75     private boolean mDarkenWhileDragging;
     76     protected boolean mBouncerShowing;
     77     private boolean mWakeAndUnlocking;
     78     private boolean mAnimateChange;
     79     private boolean mUpdatePending;
     80     private boolean mExpanding;
     81     private boolean mAnimateKeyguardFadingOut;
     82     private long mDurationOverride = -1;
     83     private long mAnimationDelay;
     84     private Runnable mOnAnimationFinished;
     85     private final Interpolator mInterpolator = new DecelerateInterpolator();
     86     private boolean mDozing;
     87     private float mDozeInFrontAlpha;
     88     private float mDozeBehindAlpha;
     89     private float mCurrentInFrontAlpha;
     90     private float mCurrentBehindAlpha;
     91     private float mCurrentHeadsUpAlpha = 1;
     92     private int mPinnedHeadsUpCount;
     93     private float mTopHeadsUpDragAmount;
     94     private View mDraggedHeadsUpView;
     95     private boolean mForceHideScrims;
     96     private boolean mSkipFirstFrame;
     97     private boolean mDontAnimateBouncerChanges;
     98     private boolean mKeyguardFadingOutInProgress;
     99     private ValueAnimator mKeyguardFadeoutAnimation;
    100 
    101     public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim) {
    102         mScrimBehind = scrimBehind;
    103         mScrimInFront = scrimInFront;
    104         mHeadsUpScrim = headsUpScrim;
    105         final Context context = scrimBehind.getContext();
    106         mUnlockMethodCache = UnlockMethodCache.getInstance(context);
    107         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
    108         updateHeadsUpScrim(false);
    109     }
    110 
    111     public void setKeyguardShowing(boolean showing) {
    112         mKeyguardShowing = showing;
    113         scheduleUpdate();
    114     }
    115 
    116     public void setShowScrimBehind(boolean show) {
    117         if (show) {
    118             mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
    119             mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
    120             mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
    121         } else {
    122             mScrimBehindAlpha = 0;
    123             mScrimBehindAlphaKeyguard = 0;
    124             mScrimBehindAlphaUnlocking = 0;
    125         }
    126         scheduleUpdate();
    127     }
    128 
    129     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
    130             float scrimBehindAlphaUnlocking) {
    131         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
    132         mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
    133         scheduleUpdate();
    134     }
    135 
    136     public void onTrackingStarted() {
    137         mExpanding = true;
    138         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
    139     }
    140 
    141     public void onExpandingFinished() {
    142         mExpanding = false;
    143     }
    144 
    145     public void setPanelExpansion(float fraction) {
    146         if (mFraction != fraction) {
    147             mFraction = fraction;
    148             scheduleUpdate();
    149             if (mPinnedHeadsUpCount != 0) {
    150                 updateHeadsUpScrim(false);
    151             }
    152             if (mKeyguardFadeoutAnimation != null) {
    153                 mKeyguardFadeoutAnimation.cancel();
    154             }
    155         }
    156     }
    157 
    158     public void setBouncerShowing(boolean showing) {
    159         mBouncerShowing = showing;
    160         mAnimateChange = !mExpanding && !mDontAnimateBouncerChanges;
    161         scheduleUpdate();
    162     }
    163 
    164     public void setWakeAndUnlocking() {
    165         mWakeAndUnlocking = true;
    166         scheduleUpdate();
    167     }
    168 
    169     public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
    170             boolean skipFirstFrame) {
    171         mWakeAndUnlocking = false;
    172         mAnimateKeyguardFadingOut = true;
    173         mDurationOverride = duration;
    174         mAnimationDelay = delay;
    175         mAnimateChange = true;
    176         mSkipFirstFrame = skipFirstFrame;
    177         mOnAnimationFinished = onAnimationFinished;
    178 
    179         if (mKeyguardUpdateMonitor.isUserUnlocked()) {
    180             scheduleUpdate();
    181 
    182             // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
    183             // the changes we just scheduled.
    184             onPreDraw();
    185         } else {
    186 
    187             // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
    188             // with too many things in this case, in order to not skip the initial frames.
    189             mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
    190         }
    191     }
    192 
    193     public void abortKeyguardFadingOut() {
    194         if (mAnimateKeyguardFadingOut) {
    195             endAnimateKeyguardFadingOut(true /* force */);
    196         }
    197     }
    198 
    199     public void animateGoingToFullShade(long delay, long duration) {
    200         mDurationOverride = duration;
    201         mAnimationDelay = delay;
    202         mAnimateChange = true;
    203         scheduleUpdate();
    204     }
    205 
    206     public void animateNextChange() {
    207         mAnimateChange = true;
    208     }
    209 
    210     public void setDozing(boolean dozing) {
    211         if (mDozing != dozing) {
    212             mDozing = dozing;
    213             scheduleUpdate();
    214         }
    215     }
    216 
    217     public void setDozeInFrontAlpha(float alpha) {
    218         mDozeInFrontAlpha = alpha;
    219         updateScrimColor(mScrimInFront);
    220     }
    221 
    222     public void setDozeBehindAlpha(float alpha) {
    223         mDozeBehindAlpha = alpha;
    224         updateScrimColor(mScrimBehind);
    225     }
    226 
    227     public float getDozeBehindAlpha() {
    228         return mDozeBehindAlpha;
    229     }
    230 
    231     public float getDozeInFrontAlpha() {
    232         return mDozeInFrontAlpha;
    233     }
    234 
    235     private float getScrimInFrontAlpha() {
    236         return mKeyguardUpdateMonitor.isUserUnlocked()
    237                 ? SCRIM_IN_FRONT_ALPHA
    238                 : SCRIM_IN_FRONT_ALPHA_LOCKED;
    239     }
    240     private void scheduleUpdate() {
    241         if (mUpdatePending) return;
    242 
    243         // Make sure that a frame gets scheduled.
    244         mScrimBehind.invalidate();
    245         mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
    246         mUpdatePending = true;
    247     }
    248 
    249     protected void updateScrims() {
    250         if (mAnimateKeyguardFadingOut || mForceHideScrims) {
    251             setScrimInFrontColor(0f);
    252             setScrimBehindColor(0f);
    253         } else if (mWakeAndUnlocking) {
    254 
    255             // During wake and unlock, we first hide everything behind a black scrim, which then
    256             // gets faded out from animateKeyguardFadingOut.
    257             if (mDozing) {
    258                 setScrimInFrontColor(0f);
    259                 setScrimBehindColor(1f);
    260             } else {
    261                 setScrimInFrontColor(1f);
    262                 setScrimBehindColor(0f);
    263             }
    264         } else if (!mKeyguardShowing && !mBouncerShowing) {
    265             updateScrimNormal();
    266             setScrimInFrontColor(0);
    267         } else {
    268             updateScrimKeyguard();
    269         }
    270         mAnimateChange = false;
    271     }
    272 
    273     private void updateScrimKeyguard() {
    274         if (mExpanding && mDarkenWhileDragging) {
    275             float behindFraction = Math.max(0, Math.min(mFraction, 1));
    276             float fraction = 1 - behindFraction;
    277             fraction = (float) Math.pow(fraction, 0.8f);
    278             behindFraction = (float) Math.pow(behindFraction, 0.8f);
    279             setScrimInFrontColor(fraction * getScrimInFrontAlpha());
    280             setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
    281         } else if (mBouncerShowing) {
    282             setScrimInFrontColor(getScrimInFrontAlpha());
    283             setScrimBehindColor(0f);
    284         } else {
    285             float fraction = Math.max(0, Math.min(mFraction, 1));
    286             setScrimInFrontColor(0f);
    287             setScrimBehindColor(fraction
    288                     * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
    289                     + mScrimBehindAlphaUnlocking);
    290         }
    291     }
    292 
    293     private void updateScrimNormal() {
    294         float frac = mFraction;
    295         // let's start this 20% of the way down the screen
    296         frac = frac * 1.2f - 0.2f;
    297         if (frac <= 0) {
    298             setScrimBehindColor(0);
    299         } else {
    300             // woo, special effects
    301             final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
    302             setScrimBehindColor(k * mScrimBehindAlpha);
    303         }
    304     }
    305 
    306     private void setScrimBehindColor(float alpha) {
    307         setScrimColor(mScrimBehind, alpha);
    308     }
    309 
    310     private void setScrimInFrontColor(float alpha) {
    311         setScrimColor(mScrimInFront, alpha);
    312         if (alpha == 0f) {
    313             mScrimInFront.setClickable(false);
    314         } else {
    315 
    316             // Eat touch events (unless dozing).
    317             mScrimInFront.setClickable(!mDozing);
    318         }
    319     }
    320 
    321     private void setScrimColor(View scrim, float alpha) {
    322         updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
    323     }
    324 
    325     private float getDozeAlpha(View scrim) {
    326         return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
    327     }
    328 
    329     private float getCurrentScrimAlpha(View scrim) {
    330         return scrim == mScrimBehind ? mCurrentBehindAlpha
    331                 : scrim == mScrimInFront ? mCurrentInFrontAlpha
    332                 : mCurrentHeadsUpAlpha;
    333     }
    334 
    335     private void setCurrentScrimAlpha(View scrim, float alpha) {
    336         if (scrim == mScrimBehind) {
    337             mCurrentBehindAlpha = alpha;
    338         } else if (scrim == mScrimInFront) {
    339             mCurrentInFrontAlpha = alpha;
    340         } else {
    341             alpha = Math.max(0.0f, Math.min(1.0f, alpha));
    342             mCurrentHeadsUpAlpha = alpha;
    343         }
    344     }
    345 
    346     private void updateScrimColor(View scrim) {
    347         float alpha1 = getCurrentScrimAlpha(scrim);
    348         if (scrim instanceof ScrimView) {
    349             float alpha2 = getDozeAlpha(scrim);
    350             float alpha = 1 - (1 - alpha1) * (1 - alpha2);
    351             alpha = Math.max(0, Math.min(1.0f, alpha));
    352             ((ScrimView) scrim).setScrimColor(Color.argb((int) (alpha * 255), 0, 0, 0));
    353         } else {
    354             scrim.setAlpha(alpha1);
    355         }
    356     }
    357 
    358     private void startScrimAnimation(final View scrim, float target) {
    359         float current = getCurrentScrimAlpha(scrim);
    360         ValueAnimator anim = ValueAnimator.ofFloat(current, target);
    361         anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    362             @Override
    363             public void onAnimationUpdate(ValueAnimator animation) {
    364                 float alpha = (float) animation.getAnimatedValue();
    365                 setCurrentScrimAlpha(scrim, alpha);
    366                 updateScrimColor(scrim);
    367             }
    368         });
    369         anim.setInterpolator(getInterpolator());
    370         anim.setStartDelay(mAnimationDelay);
    371         anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
    372         anim.addListener(new AnimatorListenerAdapter() {
    373             @Override
    374             public void onAnimationEnd(Animator animation) {
    375                 if (mOnAnimationFinished != null) {
    376                     mOnAnimationFinished.run();
    377                     mOnAnimationFinished = null;
    378                 }
    379                 if (mKeyguardFadingOutInProgress) {
    380                     mKeyguardFadeoutAnimation = null;
    381                     mKeyguardFadingOutInProgress = false;
    382                 }
    383                 scrim.setTag(TAG_KEY_ANIM, null);
    384                 scrim.setTag(TAG_KEY_ANIM_TARGET, null);
    385             }
    386         });
    387         anim.start();
    388         if (mAnimateKeyguardFadingOut) {
    389             mKeyguardFadingOutInProgress = true;
    390             mKeyguardFadeoutAnimation = anim;
    391         }
    392         if (mSkipFirstFrame) {
    393             anim.setCurrentPlayTime(16);
    394         }
    395         scrim.setTag(TAG_KEY_ANIM, anim);
    396         scrim.setTag(TAG_KEY_ANIM_TARGET, target);
    397     }
    398 
    399     private Interpolator getInterpolator() {
    400         if (mAnimateKeyguardFadingOut && !mKeyguardUpdateMonitor.isUserUnlocked()) {
    401             return KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED;
    402         } else if (mAnimateKeyguardFadingOut) {
    403             return KEYGUARD_FADE_OUT_INTERPOLATOR;
    404         } else {
    405             return mInterpolator;
    406         }
    407     }
    408 
    409     @Override
    410     public boolean onPreDraw() {
    411         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
    412         mUpdatePending = false;
    413         if (mDontAnimateBouncerChanges) {
    414             mDontAnimateBouncerChanges = false;
    415         }
    416         updateScrims();
    417         mDurationOverride = -1;
    418         mAnimationDelay = 0;
    419         mSkipFirstFrame = false;
    420 
    421         // Make sure that we always call the listener even if we didn't start an animation.
    422         endAnimateKeyguardFadingOut(false /* force */);
    423         return true;
    424     }
    425 
    426     private void endAnimateKeyguardFadingOut(boolean force) {
    427         mAnimateKeyguardFadingOut = false;
    428         if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
    429             if (mOnAnimationFinished != null) {
    430                 mOnAnimationFinished.run();
    431                 mOnAnimationFinished = null;
    432             }
    433             mKeyguardFadingOutInProgress = false;
    434         }
    435     }
    436 
    437     private boolean isAnimating(View scrim) {
    438         return scrim.getTag(TAG_KEY_ANIM) != null;
    439     }
    440 
    441     public void setDrawBehindAsSrc(boolean asSrc) {
    442         mScrimBehind.setDrawAsSrc(asSrc);
    443     }
    444 
    445     @Override
    446     public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
    447     }
    448 
    449     @Override
    450     public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
    451         mPinnedHeadsUpCount++;
    452         updateHeadsUpScrim(true);
    453     }
    454 
    455     @Override
    456     public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
    457         mPinnedHeadsUpCount--;
    458         if (headsUp == mDraggedHeadsUpView) {
    459             mDraggedHeadsUpView = null;
    460             mTopHeadsUpDragAmount = 0.0f;
    461         }
    462         updateHeadsUpScrim(true);
    463     }
    464 
    465     @Override
    466     public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
    467     }
    468 
    469     private void updateHeadsUpScrim(boolean animate) {
    470         updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
    471     }
    472 
    473     private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
    474         if (mKeyguardFadingOutInProgress) {
    475             return;
    476         }
    477 
    478         ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim,
    479                 TAG_KEY_ANIM);
    480         float animEndValue = -1;
    481         if (previousAnimator != null) {
    482             if (animate || alpha == currentAlpha) {
    483                 previousAnimator.cancel();
    484             } else {
    485                 animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA);
    486             }
    487         }
    488         if (alpha != currentAlpha && alpha != animEndValue) {
    489             if (animate) {
    490                 startScrimAnimation(scrim, alpha);
    491                 scrim.setTag(TAG_START_ALPHA, currentAlpha);
    492                 scrim.setTag(TAG_END_ALPHA, alpha);
    493             } else {
    494                 if (previousAnimator != null) {
    495                     float previousStartValue = StackStateAnimator.getChildTag(scrim,
    496                             TAG_START_ALPHA);
    497                     float previousEndValue = StackStateAnimator.getChildTag(scrim,
    498                             TAG_END_ALPHA);
    499                     // we need to increase all animation keyframes of the previous animator by the
    500                     // relative change to the end value
    501                     PropertyValuesHolder[] values = previousAnimator.getValues();
    502                     float relativeDiff = alpha - previousEndValue;
    503                     float newStartValue = previousStartValue + relativeDiff;
    504                     newStartValue = Math.max(0, Math.min(1.0f, newStartValue));
    505                     values[0].setFloatValues(newStartValue, alpha);
    506                     scrim.setTag(TAG_START_ALPHA, newStartValue);
    507                     scrim.setTag(TAG_END_ALPHA, alpha);
    508                     previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    509                 } else {
    510                     // update the alpha directly
    511                     setCurrentScrimAlpha(scrim, alpha);
    512                     updateScrimColor(scrim);
    513                 }
    514             }
    515         }
    516     }
    517 
    518     /**
    519      * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
    520      * the heads up is in its resting space and 1 means it's fully dragged out.
    521      *
    522      * @param draggedHeadsUpView the dragged view
    523      * @param topHeadsUpDragAmount how far is it dragged
    524      */
    525     public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) {
    526         mTopHeadsUpDragAmount = topHeadsUpDragAmount;
    527         mDraggedHeadsUpView = draggedHeadsUpView;
    528         updateHeadsUpScrim(false);
    529     }
    530 
    531     private float calculateHeadsUpAlpha() {
    532         float alpha;
    533         if (mPinnedHeadsUpCount >= 2) {
    534             alpha = 1.0f;
    535         } else if (mPinnedHeadsUpCount == 0) {
    536             alpha = 0.0f;
    537         } else {
    538             alpha = 1.0f - mTopHeadsUpDragAmount;
    539         }
    540         float expandFactor = (1.0f - mFraction);
    541         expandFactor = Math.max(expandFactor, 0.0f);
    542         return alpha * expandFactor;
    543     }
    544 
    545     public void forceHideScrims(boolean hide) {
    546         mForceHideScrims = hide;
    547         mAnimateChange = false;
    548         scheduleUpdate();
    549     }
    550 
    551     public void dontAnimateBouncerChangesUntilNextFrame() {
    552         mDontAnimateBouncerChanges = true;
    553     }
    554 
    555     public void setExcludedBackgroundArea(Rect area) {
    556         mScrimBehind.setExcludedArea(area);
    557     }
    558 
    559     public void setLeftInset(int inset) {
    560         mScrimBehind.setLeftInset(inset);
    561     }
    562 
    563     public int getScrimBehindColor() {
    564         return mScrimBehind.getScrimColorWithAlpha();
    565     }
    566 
    567     public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
    568         mScrimBehind.setChangeRunnable(changeRunnable);
    569     }
    570 
    571     public void onDensityOrFontScaleChanged() {
    572         ViewGroup.LayoutParams layoutParams = mHeadsUpScrim.getLayoutParams();
    573         layoutParams.height = mHeadsUpScrim.getResources().getDimensionPixelSize(
    574                 R.dimen.heads_up_scrim_height);
    575         mHeadsUpScrim.setLayoutParams(layoutParams);
    576     }
    577 
    578     public void setCurrentUser(int currentUser) {
    579         // Don't care in the base class.
    580     }
    581 }
    582