Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2008 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.internal.policy.impl;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.os.CountDownTimer;
     22 import android.os.SystemClock;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 import android.view.MotionEvent;
     27 import android.widget.Button;
     28 import android.widget.TextView;
     29 import android.text.format.DateFormat;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 import com.android.internal.R;
     33 import com.android.internal.telephony.IccCard;
     34 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
     35 import com.android.internal.widget.LockPatternUtils;
     36 import com.android.internal.widget.LockPatternView;
     37 import com.android.internal.widget.LockPatternView.Cell;
     38 
     39 import java.util.List;
     40 import java.util.Date;
     41 
     42 /**
     43  * This is the screen that shows the 9 circle unlock widget and instructs
     44  * the user how to unlock their device, or make an emergency call.
     45  */
     46 class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient
     47         implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback,
     48         KeyguardUpdateMonitor.SimStateCallback {
     49 
     50     private static final boolean DEBUG = false;
     51     private static final String TAG = "UnlockScreen";
     52 
     53     // how long before we clear the wrong pattern
     54     private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
     55 
     56     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
     57     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
     58 
     59     // how long we stay awake after the user hits the first dot.
     60     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
     61 
     62     // how many cells the user has to cross before we poke the wakelock
     63     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
     64 
     65     private int mFailedPatternAttemptsSinceLastTimeout = 0;
     66     private int mTotalFailedPatternAttempts = 0;
     67     private CountDownTimer mCountdownTimer = null;
     68 
     69     private final LockPatternUtils mLockPatternUtils;
     70     private final KeyguardUpdateMonitor mUpdateMonitor;
     71     private final KeyguardScreenCallback mCallback;
     72 
     73     /**
     74      * whether there is a fallback option available when the pattern is forgotten.
     75      */
     76     private boolean mEnableFallback;
     77 
     78     private String mDateFormatString;
     79 
     80     private TextView mCarrier;
     81     private TextView mDate;
     82 
     83     // are we showing battery information?
     84     private boolean mShowingBatteryInfo = false;
     85 
     86     // last known plugged in state
     87     private boolean mPluggedIn = false;
     88 
     89     // last known battery level
     90     private int mBatteryLevel = 100;
     91 
     92     private String mNextAlarm = null;
     93 
     94     private String mInstructions = null;
     95     private TextView mStatus1;
     96     private TextView mStatusSep;
     97     private TextView mStatus2;
     98 
     99 
    100     private LockPatternView mLockPatternView;
    101 
    102     private ViewGroup mFooterNormal;
    103     private ViewGroup mFooterForgotPattern;
    104 
    105     /**
    106      * Keeps track of the last time we poked the wake lock during dispatching
    107      * of the touch event, initalized to something gauranteed to make us
    108      * poke it when the user starts drawing the pattern.
    109      * @see #dispatchTouchEvent(android.view.MotionEvent)
    110      */
    111     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
    112 
    113     /**
    114      * Useful for clearing out the wrong pattern after a delay
    115      */
    116     private Runnable mCancelPatternRunnable = new Runnable() {
    117         public void run() {
    118             mLockPatternView.clearPattern();
    119         }
    120     };
    121 
    122     private Button mForgotPatternButton;
    123     private Button mEmergencyAlone;
    124     private Button mEmergencyTogether;
    125     private int mCreationOrientation;
    126 
    127     enum FooterMode {
    128         Normal,
    129         ForgotLockPattern,
    130         VerifyUnlocked
    131     }
    132 
    133     private void updateFooter(FooterMode mode) {
    134         switch (mode) {
    135             case Normal:
    136                 mFooterNormal.setVisibility(View.VISIBLE);
    137                 mFooterForgotPattern.setVisibility(View.GONE);
    138                 break;
    139             case ForgotLockPattern:
    140                 mFooterNormal.setVisibility(View.GONE);
    141                 mFooterForgotPattern.setVisibility(View.VISIBLE);
    142                 mForgotPatternButton.setVisibility(View.VISIBLE);
    143                 break;
    144             case VerifyUnlocked:
    145                 mFooterNormal.setVisibility(View.GONE);
    146                 mFooterForgotPattern.setVisibility(View.GONE);
    147         }
    148     }
    149 
    150     /**
    151      * @param context The context.
    152      * @param configuration
    153      * @param lockPatternUtils Used to lookup lock pattern settings.
    154      * @param updateMonitor Used to lookup state affecting keyguard.
    155      * @param callback Used to notify the manager when we're done, etc.
    156      * @param totalFailedAttempts The current number of failed attempts.
    157      * @param enableFallback True if a backup unlock option is available when the user has forgotten
    158      *        their pattern (e.g they have a google account so we can show them the account based
    159      *        backup option).
    160      */
    161     PatternUnlockScreen(Context context,
    162                  Configuration configuration, LockPatternUtils lockPatternUtils,
    163                  KeyguardUpdateMonitor updateMonitor,
    164                  KeyguardScreenCallback callback,
    165                  int totalFailedAttempts) {
    166         super(context);
    167         mLockPatternUtils = lockPatternUtils;
    168         mUpdateMonitor = updateMonitor;
    169         mCallback = callback;
    170         mTotalFailedPatternAttempts = totalFailedAttempts;
    171         mFailedPatternAttemptsSinceLastTimeout =
    172             totalFailedAttempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT;
    173 
    174         if (DEBUG) Log.d(TAG,
    175             "UnlockScreen() ctor: totalFailedAttempts="
    176                  + totalFailedAttempts + ", mFailedPat...="
    177                  + mFailedPatternAttemptsSinceLastTimeout
    178                  );
    179 
    180         mCreationOrientation = configuration.orientation;
    181 
    182         LayoutInflater inflater = LayoutInflater.from(context);
    183         if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) {
    184             inflater.inflate(R.layout.keyguard_screen_unlock_portrait, this, true);
    185         } else {
    186             inflater.inflate(R.layout.keyguard_screen_unlock_landscape, this, true);
    187         }
    188 
    189         mCarrier = (TextView) findViewById(R.id.carrier);
    190         mDate = (TextView) findViewById(R.id.date);
    191 
    192         mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
    193         refreshTimeAndDateDisplay();
    194 
    195         mStatus1 = (TextView) findViewById(R.id.status1);
    196         mStatusSep = (TextView) findViewById(R.id.statusSep);
    197         mStatus2 = (TextView) findViewById(R.id.status2);
    198 
    199         resetStatusInfo();
    200 
    201 
    202         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
    203 
    204         mFooterNormal = (ViewGroup) findViewById(R.id.footerNormal);
    205         mFooterForgotPattern = (ViewGroup) findViewById(R.id.footerForgotPattern);
    206 
    207         // emergency call buttons
    208         final OnClickListener emergencyClick = new OnClickListener() {
    209             public void onClick(View v) {
    210                 mCallback.takeEmergencyCallAction();
    211             }
    212         };
    213 
    214         mEmergencyAlone = (Button) findViewById(R.id.emergencyCallAlone);
    215         mEmergencyAlone.setFocusable(false); // touch only!
    216         mEmergencyAlone.setOnClickListener(emergencyClick);
    217         mEmergencyTogether = (Button) findViewById(R.id.emergencyCallTogether);
    218         mEmergencyTogether.setFocusable(false);
    219         mEmergencyTogether.setOnClickListener(emergencyClick);
    220         refreshEmergencyButtonText();
    221 
    222         mForgotPatternButton = (Button) findViewById(R.id.forgotPattern);
    223         mForgotPatternButton.setText(R.string.lockscreen_forgot_pattern_button_text);
    224         mForgotPatternButton.setOnClickListener(new OnClickListener() {
    225 
    226             public void onClick(View v) {
    227                 mCallback.forgotPattern(true);
    228             }
    229         });
    230 
    231         // make it so unhandled touch events within the unlock screen go to the
    232         // lock pattern view.
    233         setDefaultTouchRecepient(mLockPatternView);
    234 
    235         mLockPatternView.setSaveEnabled(false);
    236         mLockPatternView.setFocusable(false);
    237         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
    238 
    239         // stealth mode will be the same for the life of this screen
    240         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
    241 
    242         // vibrate mode will be the same for the life of this screen
    243         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
    244 
    245         // assume normal footer mode for now
    246         updateFooter(FooterMode.Normal);
    247 
    248         updateMonitor.registerInfoCallback(this);
    249         updateMonitor.registerSimStateCallback(this);
    250         setFocusableInTouchMode(true);
    251 
    252         // Required to get Marquee to work.
    253         mCarrier.setSelected(true);
    254         mCarrier.setTextColor(0xffffffff);
    255 
    256         // until we get an update...
    257         mCarrier.setText(
    258                 LockScreen.getCarrierString(
    259                         mUpdateMonitor.getTelephonyPlmn(),
    260                         mUpdateMonitor.getTelephonySpn()));
    261     }
    262 
    263     private void refreshEmergencyButtonText() {
    264         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyAlone);
    265         mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyTogether);
    266     }
    267 
    268     public void setEnableFallback(boolean state) {
    269         if (DEBUG) Log.d(TAG, "setEnableFallback(" + state + ")");
    270         mEnableFallback = state;
    271     }
    272 
    273     private void resetStatusInfo() {
    274         mInstructions = null;
    275         mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo();
    276         mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
    277         mBatteryLevel = mUpdateMonitor.getBatteryLevel();
    278         mNextAlarm = mLockPatternUtils.getNextAlarm();
    279         updateStatusLines();
    280     }
    281 
    282     private void updateStatusLines() {
    283         if (mInstructions != null) {
    284             // instructions only
    285             mStatus1.setText(mInstructions);
    286             if (TextUtils.isEmpty(mInstructions)) {
    287                 mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
    288             } else {
    289                 mStatus1.setCompoundDrawablesWithIntrinsicBounds(
    290                         R.drawable.ic_lock_idle_lock, 0, 0, 0);
    291             }
    292 
    293             mStatus1.setVisibility(View.VISIBLE);
    294             mStatusSep.setVisibility(View.GONE);
    295             mStatus2.setVisibility(View.GONE);
    296         } else if (mShowingBatteryInfo && mNextAlarm == null) {
    297             // battery only
    298             if (mPluggedIn) {
    299               if (mBatteryLevel >= 100) {
    300                 mStatus1.setText(getContext().getString(R.string.lockscreen_charged));
    301               } else {
    302                   mStatus1.setText(getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel));
    303               }
    304             } else {
    305                 mStatus1.setText(getContext().getString(R.string.lockscreen_low_battery));
    306             }
    307             mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
    308 
    309             mStatus1.setVisibility(View.VISIBLE);
    310             mStatusSep.setVisibility(View.GONE);
    311             mStatus2.setVisibility(View.GONE);
    312 
    313         } else if (mNextAlarm != null && !mShowingBatteryInfo) {
    314             // alarm only
    315             mStatus1.setText(mNextAlarm);
    316             mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
    317 
    318             mStatus1.setVisibility(View.VISIBLE);
    319             mStatusSep.setVisibility(View.GONE);
    320             mStatus2.setVisibility(View.GONE);
    321         } else if (mNextAlarm != null && mShowingBatteryInfo) {
    322             // both battery and next alarm
    323             mStatus1.setText(mNextAlarm);
    324             mStatusSep.setText("|");
    325             mStatus2.setText(getContext().getString(
    326                     R.string.lockscreen_battery_short,
    327                     Math.min(100, mBatteryLevel)));
    328             mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_alarm, 0, 0, 0);
    329             if (mPluggedIn) {
    330                 mStatus2.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_charging, 0, 0, 0);
    331             } else {
    332                 mStatus2.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
    333             }
    334 
    335             mStatus1.setVisibility(View.VISIBLE);
    336             mStatusSep.setVisibility(View.VISIBLE);
    337             mStatus2.setVisibility(View.VISIBLE);
    338         } else {
    339             // nothing specific to show; show general instructions
    340             mStatus1.setText(R.string.lockscreen_pattern_instructions);
    341             mStatus1.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_idle_lock, 0, 0, 0);
    342 
    343             mStatus1.setVisibility(View.VISIBLE);
    344             mStatusSep.setVisibility(View.GONE);
    345             mStatus2.setVisibility(View.GONE);
    346         }
    347     }
    348 
    349 
    350     private void refreshTimeAndDateDisplay() {
    351         mDate.setText(DateFormat.format(mDateFormatString, new Date()));
    352     }
    353 
    354 
    355     @Override
    356     public boolean dispatchTouchEvent(MotionEvent ev) {
    357         // as long as the user is entering a pattern (i.e sending a touch
    358         // event that was handled by this screen), keep poking the
    359         // wake lock so that the screen will stay on.
    360         final boolean result = super.dispatchTouchEvent(ev);
    361         if (result &&
    362                 ((SystemClock.elapsedRealtime() - mLastPokeTime)
    363                         >  (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
    364             mLastPokeTime = SystemClock.elapsedRealtime();
    365         }
    366         return result;
    367     }
    368 
    369 
    370     // ---------- InfoCallback
    371 
    372     /** {@inheritDoc} */
    373     public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, int batteryLevel) {
    374         mShowingBatteryInfo = showBatteryInfo;
    375         mPluggedIn = pluggedIn;
    376         mBatteryLevel = batteryLevel;
    377         updateStatusLines();
    378     }
    379 
    380     /** {@inheritDoc} */
    381     public void onTimeChanged() {
    382         refreshTimeAndDateDisplay();
    383     }
    384 
    385     /** {@inheritDoc} */
    386     public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
    387         mCarrier.setText(LockScreen.getCarrierString(plmn, spn));
    388     }
    389 
    390     /** {@inheritDoc} */
    391     public void onRingerModeChanged(int state) {
    392         // not currently used
    393     }
    394 
    395     // ---------- SimStateCallback
    396 
    397     /** {@inheritDoc} */
    398     public void onSimStateChanged(IccCard.State simState) {
    399     }
    400 
    401     @Override
    402     protected void onAttachedToWindow() {
    403         super.onAttachedToWindow();
    404         if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
    405             Log.v(TAG, "***** PATTERN ATTACHED TO WINDOW");
    406             Log.v(TAG, "Cur orient=" + mCreationOrientation
    407                     + ", new config=" + getResources().getConfiguration());
    408         }
    409         if (getResources().getConfiguration().orientation != mCreationOrientation) {
    410             mCallback.recreateMe(getResources().getConfiguration());
    411         }
    412     }
    413 
    414 
    415     /** {@inheritDoc} */
    416     @Override
    417     protected void onConfigurationChanged(Configuration newConfig) {
    418         super.onConfigurationChanged(newConfig);
    419         if (LockPatternKeyguardView.DEBUG_CONFIGURATION) {
    420             Log.v(TAG, "***** PATTERN CONFIGURATION CHANGED");
    421             Log.v(TAG, "Cur orient=" + mCreationOrientation
    422                     + ", new config=" + getResources().getConfiguration());
    423         }
    424         if (newConfig.orientation != mCreationOrientation) {
    425             mCallback.recreateMe(newConfig);
    426         }
    427     }
    428 
    429     /** {@inheritDoc} */
    430     public void onKeyboardChange(boolean isKeyboardOpen) {}
    431 
    432     /** {@inheritDoc} */
    433     public boolean needsInput() {
    434         return false;
    435     }
    436 
    437     /** {@inheritDoc} */
    438     public void onPause() {
    439         if (mCountdownTimer != null) {
    440             mCountdownTimer.cancel();
    441             mCountdownTimer = null;
    442         }
    443     }
    444 
    445     /** {@inheritDoc} */
    446     public void onResume() {
    447         // reset header
    448         resetStatusInfo();
    449 
    450         // reset lock pattern
    451         mLockPatternView.enableInput();
    452         mLockPatternView.setEnabled(true);
    453         mLockPatternView.clearPattern();
    454 
    455         // show "forgot pattern?" button if we have an alternate authentication method
    456         mForgotPatternButton.setVisibility(mCallback.doesFallbackUnlockScreenExist()
    457                 ? View.VISIBLE : View.INVISIBLE);
    458 
    459         // if the user is currently locked out, enforce it.
    460         long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
    461         if (deadline != 0) {
    462             handleAttemptLockout(deadline);
    463         }
    464 
    465         // the footer depends on how many total attempts the user has failed
    466         if (mCallback.isVerifyUnlockOnly()) {
    467             updateFooter(FooterMode.VerifyUnlocked);
    468         } else if (mEnableFallback &&
    469                 (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
    470             updateFooter(FooterMode.ForgotLockPattern);
    471         } else {
    472             updateFooter(FooterMode.Normal);
    473         }
    474 
    475         refreshEmergencyButtonText();
    476     }
    477 
    478     /** {@inheritDoc} */
    479     public void cleanUp() {
    480         mUpdateMonitor.removeCallback(this);
    481     }
    482 
    483     @Override
    484     public void onWindowFocusChanged(boolean hasWindowFocus) {
    485         super.onWindowFocusChanged(hasWindowFocus);
    486         if (hasWindowFocus) {
    487             // when timeout dialog closes we want to update our state
    488             onResume();
    489         }
    490     }
    491 
    492     private class UnlockPatternListener
    493             implements LockPatternView.OnPatternListener {
    494 
    495         public void onPatternStart() {
    496             mLockPatternView.removeCallbacks(mCancelPatternRunnable);
    497         }
    498 
    499         public void onPatternCleared() {
    500         }
    501 
    502         public void onPatternCellAdded(List<Cell> pattern) {
    503             // To guard against accidental poking of the wakelock, look for
    504             // the user actually trying to draw a pattern of some minimal length.
    505             if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
    506                 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
    507             } else {
    508                 // Give just a little extra time if they hit one of the first few dots
    509                 mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
    510             }
    511         }
    512 
    513         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
    514             if (mLockPatternUtils.checkPattern(pattern)) {
    515                 mLockPatternView
    516                         .setDisplayMode(LockPatternView.DisplayMode.Correct);
    517                 mInstructions = "";
    518                 updateStatusLines();
    519                 mCallback.keyguardDone(true);
    520                 mCallback.reportSuccessfulUnlockAttempt();
    521             } else {
    522                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
    523                     mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
    524                 }
    525                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
    526                 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
    527                     mTotalFailedPatternAttempts++;
    528                     mFailedPatternAttemptsSinceLastTimeout++;
    529                     mCallback.reportFailedUnlockAttempt();
    530                 }
    531                 if (mFailedPatternAttemptsSinceLastTimeout >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
    532                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
    533                     handleAttemptLockout(deadline);
    534                 } else {
    535                     // TODO mUnlockIcon.setVisibility(View.VISIBLE);
    536                     mInstructions = getContext().getString(R.string.lockscreen_pattern_wrong);
    537                     updateStatusLines();
    538                     mLockPatternView.postDelayed(
    539                             mCancelPatternRunnable,
    540                             PATTERN_CLEAR_TIMEOUT_MS);
    541                 }
    542             }
    543         }
    544     }
    545 
    546     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
    547         mLockPatternView.clearPattern();
    548         mLockPatternView.setEnabled(false);
    549         long elapsedRealtime = SystemClock.elapsedRealtime();
    550         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
    551 
    552             @Override
    553             public void onTick(long millisUntilFinished) {
    554                 int secondsRemaining = (int) (millisUntilFinished / 1000);
    555                 mInstructions = getContext().getString(
    556                         R.string.lockscreen_too_many_failed_attempts_countdown,
    557                         secondsRemaining);
    558                 updateStatusLines();
    559             }
    560 
    561             @Override
    562             public void onFinish() {
    563                 mLockPatternView.setEnabled(true);
    564                 mInstructions = getContext().getString(R.string.lockscreen_pattern_instructions);
    565                 updateStatusLines();
    566                 // TODO mUnlockIcon.setVisibility(View.VISIBLE);
    567                 mFailedPatternAttemptsSinceLastTimeout = 0;
    568                 if (mEnableFallback) {
    569                     updateFooter(FooterMode.ForgotLockPattern);
    570                 } else {
    571                     updateFooter(FooterMode.Normal);
    572                 }
    573             }
    574         }.start();
    575     }
    576 
    577     public void onPhoneStateChanged(String newState) {
    578         refreshEmergencyButtonText();
    579     }
    580 }
    581