• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.deskclock.timer;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.Typeface;
24 import android.text.TextUtils;
25 import android.util.AttributeSet;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.accessibility.AccessibilityManager;
29 import android.widget.TextView;
30 
31 import com.android.deskclock.Log;
32 import com.android.deskclock.R;
33 import com.android.deskclock.Utils;
34 
35 
36 public class CountingTimerView extends View {
37     private static final String TWO_DIGITS = "%02d";
38     private static final String ONE_DIGIT = "%01d";
39     private static final String NEG_TWO_DIGITS = "-%02d";
40     private static final String NEG_ONE_DIGIT = "-%01d";
41     private static final float TEXT_SIZE_TO_WIDTH_RATIO = 0.75f;
42     // This is the ratio of the font typeface we need to offset the font by vertically to align it
43     // vertically center.
44     private static final float FONT_VERTICAL_OFFSET = 0.14f;
45 
46     private String mHours, mMinutes, mSeconds, mHundredths;
47 
48     private boolean mShowTimeStr = true;
49     private final Typeface mAndroidClockMonoThin, mAndroidClockMonoBold, mAndroidClockMonoLight;
50     private final Typeface mRobotoLabel;
51     private final Paint mPaintBig = new Paint();
52     private final Paint mPaintBigThin = new Paint();
53     private final Paint mPaintMed = new Paint();
54     private final Paint mPaintLabel = new Paint();
55     private final float mBigFontSize, mSmallFontSize;
56     private final SignedTime mBigHours, mBigMinutes;
57     private final UnsignedTime mBigThinSeconds;
58     private final Hundredths mMedHundredths;
59     private float mTextHeight = 0;
60     private float mTotalTextWidth;
61     private static final String HUNDREDTH_SEPERATOR = ".";
62     private boolean mRemeasureText = true;
63 
64     private int mDefaultColor;
65     private final int mPressedColor;
66     private final int mWhiteColor;
67     private final int mRedColor;
68     private TextView mStopStartTextView;
69     private final AccessibilityManager mAccessibilityManager;
70 
71     // Fields for the text serving as a virtual button.
72     private boolean mVirtualButtonEnabled = false;
73     private boolean mVirtualButtonPressedOn = false;
74 
75     Runnable mBlinkThread = new Runnable() {
76         private boolean mVisible = true;
77         @Override
78         public void run() {
79             mVisible = !mVisible;
80             CountingTimerView.this.showTime(mVisible);
81             postDelayed(mBlinkThread, 500);
82         }
83 
84     };
85 
86     class UnsignedTime {
87         protected Paint mPaint;
88         protected float mEm;
89         protected float mWidth = 0;
90         private final String mWidest;
91         protected String mLabel;
92         private float mLabelWidth = 0;
93 
UnsignedTime(Paint paint, final String label, String allDigits)94         public UnsignedTime(Paint paint, final String label, String allDigits) {
95             mPaint = paint;
96             mLabel = label;
97 
98             if (TextUtils.isEmpty(allDigits)) {
99                 Log.wtf("Locale digits missing - using English");
100                 allDigits = "0123456789";
101             }
102 
103             float widths[] = new float[allDigits.length()];
104             int ll = mPaint.getTextWidths(allDigits, widths);
105             int largest = 0;
106             for (int ii = 1; ii < ll; ii++) {
107                 if (widths[ii] > widths[largest]) {
108                     largest = ii;
109                 }
110             }
111 
112             mEm = widths[largest];
113             mWidest = allDigits.substring(largest, largest + 1);
114         }
115 
UnsignedTime(UnsignedTime unsignedTime, final String label)116         public UnsignedTime(UnsignedTime unsignedTime, final String label) {
117             this.mPaint = unsignedTime.mPaint;
118             this.mEm = unsignedTime.mEm;
119             this.mWidth = unsignedTime.mWidth;
120             this.mWidest = unsignedTime.mWidest;
121             this.mLabel = label;
122         }
123 
updateWidth(final String time)124         protected void updateWidth(final String time) {
125             mEm = mPaint.measureText(mWidest);
126             mLabelWidth = mLabel == null ? 0 : mPaintLabel.measureText(mLabel);
127             mWidth = time.length() * mEm;
128         }
129 
resetWidth()130         protected void resetWidth() {
131             mWidth = mLabelWidth = 0;
132         }
133 
calcTotalWidth(final String time)134         public float calcTotalWidth(final String time) {
135             if (time != null) {
136                 updateWidth(time);
137                 return mWidth + mLabelWidth;
138             } else {
139                 resetWidth();
140                 return 0;
141             }
142         }
143 
getWidth()144         public float getWidth() {
145             return mWidth;
146         }
147 
getLabelWidth()148         public float getLabelWidth() {
149             return mLabelWidth;
150         }
151 
drawTime(Canvas canvas, final String time, int ii, float x, float y)152         protected float drawTime(Canvas canvas, final String time, int ii, float x, float y) {
153             float textEm  = mEm / 2f;
154             while (ii < time.length()) {
155                 x += textEm;
156                 canvas.drawText(time.substring(ii, ii + 1), x, y, mPaint);
157                 x += textEm;
158                 ii++;
159             }
160             return x;
161         }
162 
draw(Canvas canvas, final String time, float x, float y, float yLabel)163         public float draw(Canvas canvas, final String time, float x, float y, float yLabel) {
164             x = drawTime(canvas, time, 0, x, y);
165             if (mLabel != null ) {
166                 canvas.drawText(mLabel, x, yLabel, mPaintLabel);
167             }
168             return x + getLabelWidth();
169         }
170     }
171 
172     class Hundredths extends UnsignedTime {
Hundredths(Paint paint, final String label, final String allDigits)173         public Hundredths(Paint paint, final String label, final String allDigits) {
174             super(paint, label, allDigits);
175         }
176 
177         @Override
draw(Canvas canvas, final String time, float x, float y, float yLabel)178         public float draw(Canvas canvas, final String time, float x, float y, float yLabel) {
179             if (mLabel != null) {
180                 canvas.drawText(mLabel, x, yLabel, mPaintLabel);
181             }
182             return drawTime(canvas, time, 0, x + getLabelWidth(), y);
183         }
184     }
185 
186     class SignedTime extends UnsignedTime {
187         private float mMinusWidth = 0;
188 
SignedTime(Paint paint, final String label, final String allDigits)189         public SignedTime(Paint paint, final String label, final String allDigits) {
190             super(paint, label, allDigits);
191         }
192 
SignedTime(SignedTime signedTime, final String label)193         public SignedTime (SignedTime signedTime, final String label) {
194             super(signedTime, label);
195         }
196 
197         @Override
updateWidth(final String time)198         protected void updateWidth(final String time) {
199             super.updateWidth(time);
200             if (time.contains("-")) {
201                 mMinusWidth = mPaint.measureText("-");
202                 mWidth += (mMinusWidth - mEm);
203             } else {
204                 mMinusWidth = 0;
205             }
206         }
207 
208         @Override
resetWidth()209         protected void resetWidth() {
210             super.resetWidth();
211             mMinusWidth = 0;
212         }
213 
214         @Override
draw(Canvas canvas, final String time, float x, float y, float yLabel)215         public float draw(Canvas canvas, final String time, float x, float y, float yLabel) {
216             int ii = 0;
217             if (mMinusWidth != 0f) {
218                 float minusWidth = mMinusWidth / 2;
219                 x += minusWidth;
220                 canvas.drawText(time.substring(ii, ii + 1), x, y, mPaint);
221                 x += minusWidth;
222                 ii++;
223             }
224             x = drawTime(canvas, time, ii, x, y);
225             if (mLabel != null) {
226                 canvas.drawText(mLabel, x, yLabel, mPaintLabel);
227             }
228             return x + getLabelWidth();
229         }
230     }
231 
CountingTimerView(Context context)232     public CountingTimerView(Context context) {
233         this(context, null);
234     }
235 
CountingTimerView(Context context, AttributeSet attrs)236     public CountingTimerView(Context context, AttributeSet attrs) {
237         super(context, attrs);
238         mAndroidClockMonoThin = Typeface.createFromAsset(
239                 context.getAssets(), "fonts/AndroidClockMono-Thin.ttf");
240         mAndroidClockMonoBold = Typeface.createFromAsset(
241                 context.getAssets(), "fonts/AndroidClockMono-Bold.ttf");
242         mAndroidClockMonoLight = Typeface.createFromAsset(
243                 context.getAssets(), "fonts/AndroidClockMono-Light.ttf");
244         mAccessibilityManager =
245                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
246         mRobotoLabel= Typeface.create("sans-serif-condensed", Typeface.BOLD);
247         Resources r = context.getResources();
248         mWhiteColor = r.getColor(R.color.clock_white);
249         mDefaultColor = mWhiteColor;
250         mPressedColor = r.getColor(Utils.getPressedColorId());
251         mRedColor = r.getColor(R.color.clock_red);
252 
253         mPaintBig.setAntiAlias(true);
254         mPaintBig.setStyle(Paint.Style.STROKE);
255         mPaintBig.setTextAlign(Paint.Align.CENTER);
256         mPaintBig.setTypeface(mAndroidClockMonoBold);
257         mBigFontSize = r.getDimension(R.dimen.big_font_size);
258         mSmallFontSize = r.getDimension(R.dimen.small_font_size);
259 
260         mPaintBigThin.setAntiAlias(true);
261         mPaintBigThin.setStyle(Paint.Style.STROKE);
262         mPaintBigThin.setTextAlign(Paint.Align.CENTER);
263         mPaintBigThin.setTypeface(mAndroidClockMonoThin);
264 
265         mPaintMed.setAntiAlias(true);
266         mPaintMed.setStyle(Paint.Style.STROKE);
267         mPaintMed.setTextAlign(Paint.Align.CENTER);
268         mPaintMed.setTypeface(mAndroidClockMonoLight);
269 
270         mPaintLabel.setAntiAlias(true);
271         mPaintLabel.setStyle(Paint.Style.STROKE);
272         mPaintLabel.setTextAlign(Paint.Align.LEFT);
273         mPaintLabel.setTypeface(mRobotoLabel);
274         mPaintLabel.setTextSize(r.getDimension(R.dimen.label_font_size));
275 
276         resetTextSize();
277         setTextColor(mDefaultColor);
278 
279         // allDigits will contain ten digits: "0123456789" in the default locale
280         final String allDigits = String.format("%010d", 123456789);
281         mBigHours = new SignedTime(mPaintBig,
282                 r.getString(R.string.hours_label).toUpperCase(), allDigits);
283         mBigMinutes = new SignedTime(mBigHours,
284                 r.getString(R.string.minutes_label).toUpperCase());
285         mBigThinSeconds = new UnsignedTime(mPaintBigThin,
286                 r.getString(R.string.seconds_label).toUpperCase(), allDigits);
287         mMedHundredths = new Hundredths(mPaintMed, HUNDREDTH_SEPERATOR, allDigits);
288     }
289 
resetTextSize()290     protected void resetTextSize() {
291         mPaintBig.setTextSize(mBigFontSize);
292         mTextHeight = mBigFontSize;
293         mPaintBigThin.setTextSize(mBigFontSize);
294         mPaintMed.setTextSize(mSmallFontSize);
295     }
296 
setTextColor(int textColor)297     protected void setTextColor(int textColor) {
298         mPaintBig.setColor(textColor);
299         mPaintBigThin.setColor(textColor);
300         mPaintMed.setColor(textColor);
301         mPaintLabel.setColor(textColor);
302     }
303 
setTime(long time, boolean showHundredths, boolean update)304     public void setTime(long time, boolean showHundredths, boolean update) {
305         boolean neg = false, showNeg = false;
306         String format = null;
307         if (time < 0) {
308             time = -time;
309             neg = showNeg = true;
310         }
311         long hundreds, seconds, minutes, hours;
312         seconds = time / 1000;
313         hundreds = (time - seconds * 1000) / 10;
314         minutes = seconds / 60;
315         seconds = seconds - minutes * 60;
316         hours = minutes / 60;
317         minutes = minutes - hours * 60;
318         if (hours > 999) {
319             hours = 0;
320         }
321         // time may less than a second below zero, since we do not show fractions of seconds
322         // when counting down, do not show the minus sign.
323         if (hours ==0 && minutes == 0 && seconds == 0) {
324             showNeg = false;
325         }
326 
327         if (!showHundredths) {
328             if (!neg && hundreds != 0) {
329                 seconds++;
330                 if (seconds == 60) {
331                     seconds = 0;
332                     minutes++;
333                     if (minutes == 60) {
334                         minutes = 0;
335                         hours++;
336                     }
337                 }
338             }
339             if (hundreds < 10 || hundreds > 90) {
340                 update = true;
341             }
342         }
343 
344         int oldLength = getDigitsLength();
345 
346         if (hours >= 10) {
347             format = showNeg ? NEG_TWO_DIGITS : TWO_DIGITS;
348             mHours = String.format(format, hours);
349         } else if (hours > 0) {
350             format = showNeg ? NEG_ONE_DIGIT : ONE_DIGIT;
351             mHours = String.format(format, hours);
352         } else {
353             mHours = null;
354         }
355 
356         if (minutes >= 10 || hours > 0) {
357             format = (showNeg && hours == 0) ? NEG_TWO_DIGITS : TWO_DIGITS;
358             mMinutes = String.format(format, minutes);
359         } else {
360             format = (showNeg && hours == 0) ? NEG_ONE_DIGIT : ONE_DIGIT;
361             mMinutes = String.format(format, minutes);
362         }
363 
364         mSeconds = String.format(TWO_DIGITS, seconds);
365         if (showHundredths) {
366             mHundredths = String.format(TWO_DIGITS, hundreds);
367         } else {
368             mHundredths = null;
369         }
370 
371         int newLength = getDigitsLength();
372         if (oldLength != newLength) {
373             if (oldLength > newLength) {
374                 resetTextSize();
375             }
376             mRemeasureText = true;
377         }
378 
379         if (update) {
380             setContentDescription(getTimeStringForAccessibility((int) hours, (int) minutes,
381                     (int) seconds, showNeg, getResources()));
382             invalidate();
383         }
384     }
385 
getDigitsLength()386     private int getDigitsLength() {
387         return ((mHours == null) ? 0 : mHours.length())
388                 + ((mMinutes == null) ? 0 : mMinutes.length())
389                 + ((mSeconds == null) ? 0 : mSeconds.length())
390                 + ((mHundredths == null) ? 0 : mHundredths.length());
391     }
392 
calcTotalTextWidth()393     private void calcTotalTextWidth() {
394         mTotalTextWidth = mBigHours.calcTotalWidth(mHours) + mBigMinutes.calcTotalWidth(mMinutes)
395                 + mBigThinSeconds.calcTotalWidth(mSeconds)
396                 + mMedHundredths.calcTotalWidth(mHundredths);
397     }
398 
setTotalTextWidth()399     private void setTotalTextWidth() {
400         calcTotalTextWidth();
401         // To determine the maximum width, we find the minimum of the height and width (since the
402         // circle we are trying to fit the text into has its radius sized to the smaller of the
403         // two.
404         int width = Math.min(getWidth(), getHeight());
405         if (width != 0) {
406             float wantWidth = (int)(TEXT_SIZE_TO_WIDTH_RATIO * width);
407             // If the text is too wide, reduce all the paint text sizes
408             while (mTotalTextWidth > wantWidth) {
409                 // Get fixed and variant parts of the total size
410                 float fixedWidths = mBigHours.getLabelWidth() + mBigMinutes.getLabelWidth()
411                         + mBigThinSeconds.getLabelWidth() + mMedHundredths.getLabelWidth();
412                 float varWidths = mBigHours.getWidth() + mBigMinutes.getWidth()
413                         + mBigThinSeconds.getWidth() + mMedHundredths.getWidth();
414                 // Avoid divide by zero || sizeRatio == 1 || sizeRatio <= 0
415                 if (varWidths == 0 || fixedWidths == 0 || fixedWidths >= wantWidth) {
416                     break;
417                 }
418                 // Variant-section reduction
419                 float sizeRatio = (wantWidth - fixedWidths) / varWidths;
420                 mPaintBig.setTextSize(mPaintBig.getTextSize() * sizeRatio);
421                 mPaintBigThin.setTextSize(mPaintBigThin.getTextSize() * sizeRatio);
422                 mPaintMed.setTextSize(mPaintMed.getTextSize() * sizeRatio);
423                 //recalculate the new total text width and half text height
424                 mTextHeight = mPaintBig.getTextSize();
425                 calcTotalTextWidth();
426             }
427         }
428     }
429 
blinkTimeStr(boolean blink)430     public void blinkTimeStr(boolean blink) {
431         if (blink) {
432             removeCallbacks(mBlinkThread);
433             post(mBlinkThread);
434         } else {
435             removeCallbacks(mBlinkThread);
436             showTime(true);
437         }
438     }
439 
showTime(boolean visible)440     public void showTime(boolean visible) {
441         mShowTimeStr = visible;
442         invalidate();
443     }
444 
redTimeStr(boolean red, boolean forceUpdate)445     public void redTimeStr(boolean red, boolean forceUpdate) {
446         mDefaultColor = red ? mRedColor : mWhiteColor;
447         setTextColor(mDefaultColor);
448         if (forceUpdate) {
449             invalidate();
450         }
451     }
452 
getTimeString()453     public String getTimeString() {
454         // Though only called from Stopwatch Share, so hundredth are never null,
455         // protect the future and check for null mHundredths
456         if (mHundredths == null) {
457             if (mHours == null) {
458                 return String.format("%s:%s", mMinutes, mSeconds);
459             }
460             return String.format("%s:%s:%s", mHours, mMinutes, mSeconds);
461         } else if (mHours == null) {
462             return String.format("%s:%s.%s", mMinutes, mSeconds, mHundredths);
463         }
464         return String.format("%s:%s:%s.%s", mHours, mMinutes, mSeconds, mHundredths);
465     }
466 
getTimeStringForAccessibility(int hours, int minutes, int seconds, boolean showNeg, Resources r)467     private static String getTimeStringForAccessibility(int hours, int minutes, int seconds,
468             boolean showNeg, Resources r) {
469         StringBuilder s = new StringBuilder();
470         if (showNeg) {
471             // This must be followed by a non-zero number or it will be audible as "hyphen"
472             // instead of "minus".
473             s.append("-");
474         }
475         if (showNeg && hours == 0 && minutes == 0) {
476             // Non-negative time will always have minutes, eg. "0 minutes 7 seconds", but negative
477             // time must start with non-zero digit, eg. -0m7s will be audible as just "-7 seconds"
478             s.append(String.format(
479                     r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(),
480                     seconds));
481         } else if (hours == 0) {
482             s.append(String.format(
483                     r.getQuantityText(R.plurals.Nminutes_description, minutes).toString(),
484                     minutes));
485             s.append(" ");
486             s.append(String.format(
487                     r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(),
488                     seconds));
489         } else {
490             s.append(String.format(
491                     r.getQuantityText(R.plurals.Nhours_description, hours).toString(),
492                     hours));
493             s.append(" ");
494             s.append(String.format(
495                     r.getQuantityText(R.plurals.Nminutes_description, minutes).toString(),
496                     minutes));
497             s.append(" ");
498             s.append(String.format(
499                     r.getQuantityText(R.plurals.Nseconds_description, seconds).toString(),
500                     seconds));
501         }
502         return s.toString();
503     }
504 
setVirtualButtonEnabled(boolean enabled)505     public void setVirtualButtonEnabled(boolean enabled) {
506         mVirtualButtonEnabled = enabled;
507     }
508 
virtualButtonPressed(boolean pressedOn)509     private void virtualButtonPressed(boolean pressedOn) {
510         mVirtualButtonPressedOn = pressedOn;
511         mStopStartTextView.setTextColor(pressedOn ? mPressedColor : mWhiteColor);
512         invalidate();
513     }
514 
withinVirtualButtonBounds(float x, float y)515     private boolean withinVirtualButtonBounds(float x, float y) {
516         int width = getWidth();
517         int height = getHeight();
518         float centerX = width / 2;
519         float centerY = height / 2;
520         float radius = Math.min(width, height) / 2;
521 
522         // Within the circle button if distance to the center is less than the radius.
523         double distance = Math.sqrt(Math.pow(centerX - x, 2) + Math.pow(centerY - y, 2));
524         return distance < radius;
525     }
526 
registerVirtualButtonAction(final Runnable runnable)527     public void registerVirtualButtonAction(final Runnable runnable) {
528         if (!mAccessibilityManager.isEnabled()) {
529             this.setOnTouchListener(new OnTouchListener() {
530                 @Override
531                 public boolean onTouch(View v, MotionEvent event) {
532                     if (mVirtualButtonEnabled) {
533                         switch (event.getAction()) {
534                             case MotionEvent.ACTION_DOWN:
535                                 if (withinVirtualButtonBounds(event.getX(), event.getY())) {
536                                     virtualButtonPressed(true);
537                                     return true;
538                                 } else {
539                                     virtualButtonPressed(false);
540                                     return false;
541                                 }
542                             case MotionEvent.ACTION_CANCEL:
543                                 virtualButtonPressed(false);
544                                 return true;
545                             case MotionEvent.ACTION_OUTSIDE:
546                                 virtualButtonPressed(false);
547                                 return false;
548                             case MotionEvent.ACTION_UP:
549                                 virtualButtonPressed(false);
550                                 if (withinVirtualButtonBounds(event.getX(), event.getY())) {
551                                     runnable.run();
552                                 }
553                                 return true;
554                         }
555                     }
556                     return false;
557                 }
558             });
559         } else {
560             this.setOnClickListener(new OnClickListener() {
561                 @Override
562                 public void onClick(View v) {
563                     runnable.run();
564                 }
565             });
566         }
567     }
568 
569     @Override
onDraw(Canvas canvas)570     public void onDraw(Canvas canvas) {
571         // Blink functionality.
572         if (!mShowTimeStr && !mVirtualButtonPressedOn) {
573             return;
574         }
575 
576         int width = getWidth();
577         if (mRemeasureText && width != 0) {
578             setTotalTextWidth();
579             width = getWidth();
580             mRemeasureText = false;
581         }
582 
583         int xCenter = width / 2;
584         int yCenter = getHeight() / 2;
585 
586         float textXstart = xCenter - mTotalTextWidth / 2;
587         float textYstart = yCenter + mTextHeight/2 - (mTextHeight * FONT_VERTICAL_OFFSET);
588         // align the labels vertically to the top of the rest of the text
589         float labelYStart = textYstart - (mTextHeight * (1 - 2 * FONT_VERTICAL_OFFSET))
590                 + (1 - 2 * FONT_VERTICAL_OFFSET) * mPaintLabel.getTextSize();
591 
592         // Text color differs based on pressed state.
593         int textColor;
594         if (mVirtualButtonPressedOn) {
595             textColor = mPressedColor;
596             mStopStartTextView.setTextColor(mPressedColor);
597         } else {
598             textColor = mDefaultColor;
599         }
600         mPaintBig.setColor(textColor);
601         mPaintBigThin.setColor(textColor);
602         mPaintLabel.setColor(textColor);
603         mPaintMed.setColor(textColor);
604 
605         if (mHours != null) {
606             textXstart = mBigHours.draw(canvas, mHours, textXstart, textYstart, labelYStart);
607         }
608         if (mMinutes != null) {
609             textXstart = mBigMinutes.draw(canvas, mMinutes, textXstart, textYstart, labelYStart);
610         }
611         if (mSeconds != null) {
612             textXstart = mBigThinSeconds.draw(canvas, mSeconds,
613                     textXstart, textYstart, labelYStart);
614         }
615         if (mHundredths != null) {
616             textXstart = mMedHundredths.draw(canvas, mHundredths,
617                     textXstart, textYstart, textYstart);
618         }
619     }
620 
registerStopTextView(TextView stopStartTextView)621     public void registerStopTextView(TextView stopStartTextView) {
622         mStopStartTextView = stopStartTextView;
623     }
624 }
625