• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 android.text.method;
18 
19 import android.annotation.NonNull;
20 import android.text.Layout;
21 import android.text.Spannable;
22 import android.view.InputDevice;
23 import android.view.KeyEvent;
24 import android.view.MotionEvent;
25 import android.widget.TextView;
26 
27 /**
28  * Base classes for movement methods.
29  */
30 @android.ravenwood.annotation.RavenwoodKeepWholeClass
31 public class BaseMovementMethod implements MovementMethod {
32     @Override
canSelectArbitrarily()33     public boolean canSelectArbitrarily() {
34         return false;
35     }
36 
37     @Override
initialize(TextView widget, Spannable text)38     public void initialize(TextView widget, Spannable text) {
39     }
40 
41     @Override
onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event)42     public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) {
43         final int movementMetaState = getMovementMetaState(text, event);
44         boolean handled = handleMovementKey(widget, text, keyCode, movementMetaState, event);
45         if (handled) {
46             MetaKeyKeyListener.adjustMetaAfterKeypress(text);
47             MetaKeyKeyListener.resetLockedMeta(text);
48         }
49         return handled;
50     }
51 
52     @Override
onKeyOther(TextView widget, Spannable text, KeyEvent event)53     public boolean onKeyOther(TextView widget, Spannable text, KeyEvent event) {
54         final int movementMetaState = getMovementMetaState(text, event);
55         final int keyCode = event.getKeyCode();
56         if (keyCode != KeyEvent.KEYCODE_UNKNOWN
57                 && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
58             final int repeat = event.getRepeatCount();
59             boolean handled = false;
60             for (int i = 0; i < repeat; i++) {
61                 if (!handleMovementKey(widget, text, keyCode, movementMetaState, event)) {
62                     break;
63                 }
64                 handled = true;
65             }
66             if (handled) {
67                 MetaKeyKeyListener.adjustMetaAfterKeypress(text);
68                 MetaKeyKeyListener.resetLockedMeta(text);
69             }
70             return handled;
71         }
72         return false;
73     }
74 
75     @Override
onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event)76     public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) {
77         return false;
78     }
79 
80     @Override
onTakeFocus(TextView widget, Spannable text, int direction)81     public void onTakeFocus(TextView widget, Spannable text, int direction) {
82     }
83 
84     @Override
onTouchEvent(TextView widget, Spannable text, MotionEvent event)85     public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) {
86         return false;
87     }
88 
89     @Override
onTrackballEvent(TextView widget, Spannable text, MotionEvent event)90     public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
91         return false;
92     }
93 
94     @Override
onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event)95     public boolean onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event) {
96         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
97             switch (event.getAction()) {
98                 case MotionEvent.ACTION_SCROLL: {
99                     final float vscroll;
100                     final float hscroll;
101                     if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
102                         vscroll = 0;
103                         hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
104                     } else {
105                         vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
106                         hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
107                     }
108 
109                     boolean handled = false;
110                     if (hscroll < 0) {
111                         handled |= scrollLeft(widget, text, (int)Math.ceil(-hscroll));
112                     } else if (hscroll > 0) {
113                         handled |= scrollRight(widget, text, (int)Math.ceil(hscroll));
114                     }
115                     if (vscroll < 0) {
116                         handled |= scrollUp(widget, text, (int)Math.ceil(-vscroll));
117                     } else if (vscroll > 0) {
118                         handled |= scrollDown(widget, text, (int)Math.ceil(vscroll));
119                     }
120                     return handled;
121                 }
122             }
123         }
124         return false;
125     }
126 
127     /**
128      * Gets the meta state used for movement using the modifiers tracked by the text
129      * buffer as well as those present in the key event.
130      *
131      * The movement meta state excludes the state of locked modifiers or the SHIFT key
132      * since they are not used by movement actions (but they may be used for selection).
133      *
134      * @param buffer The text buffer.
135      * @param event The key event.
136      * @return The keyboard meta states used for movement.
137      */
getMovementMetaState(Spannable buffer, KeyEvent event)138     protected int getMovementMetaState(Spannable buffer, KeyEvent event) {
139         // We ignore locked modifiers and SHIFT.
140         int metaState = MetaKeyKeyListener.getMetaState(buffer, event)
141                 & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED);
142         return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK;
143     }
144 
145     /**
146      * Performs a movement key action.
147      * The default implementation decodes the key down and invokes movement actions
148      * such as {@link #down} and {@link #up}.
149      * {@link #onKeyDown(TextView, Spannable, int, KeyEvent)} calls this method once
150      * to handle an {@link KeyEvent#ACTION_DOWN}.
151      * {@link #onKeyOther(TextView, Spannable, KeyEvent)} calls this method repeatedly
152      * to handle each repetition of an {@link KeyEvent#ACTION_MULTIPLE}.
153      *
154      * @param widget The text view.
155      * @param buffer The text buffer.
156      * @param event The key event.
157      * @param keyCode The key code.
158      * @param movementMetaState The keyboard meta states used for movement.
159      * @param event The key event.
160      * @return True if the event was handled.
161      */
handleMovementKey(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event)162     protected boolean handleMovementKey(TextView widget, Spannable buffer,
163             int keyCode, int movementMetaState, KeyEvent event) {
164         switch (keyCode) {
165             case KeyEvent.KEYCODE_DPAD_LEFT:
166                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
167                     return left(widget, buffer);
168                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
169                         KeyEvent.META_CTRL_ON)) {
170                     return leftWord(widget, buffer);
171                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
172                         KeyEvent.META_ALT_ON)) {
173                     return lineStart(widget, buffer);
174                 }
175                 break;
176 
177             case KeyEvent.KEYCODE_DPAD_RIGHT:
178                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
179                     return right(widget, buffer);
180                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
181                         KeyEvent.META_CTRL_ON)) {
182                     return rightWord(widget, buffer);
183                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
184                         KeyEvent.META_ALT_ON)) {
185                     return lineEnd(widget, buffer);
186                 }
187                 break;
188 
189             case KeyEvent.KEYCODE_DPAD_UP:
190                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
191                     return up(widget, buffer);
192                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
193                         KeyEvent.META_ALT_ON)) {
194                     return top(widget, buffer);
195                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
196                         KeyEvent.META_CTRL_ON)) {
197                     return previousParagraph(widget, buffer);
198                 }
199                 break;
200 
201             case KeyEvent.KEYCODE_DPAD_DOWN:
202                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
203                     return down(widget, buffer);
204                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
205                         KeyEvent.META_ALT_ON)) {
206                     return bottom(widget, buffer);
207                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
208                         KeyEvent.META_CTRL_ON)) {
209                     return nextParagraph(widget, buffer);
210                 }
211                 break;
212 
213             case KeyEvent.KEYCODE_PAGE_UP:
214                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
215                     return pageUp(widget, buffer);
216                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
217                         KeyEvent.META_ALT_ON)) {
218                     return top(widget, buffer);
219                 }
220                 break;
221 
222             case KeyEvent.KEYCODE_PAGE_DOWN:
223                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
224                     return pageDown(widget, buffer);
225                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
226                         KeyEvent.META_ALT_ON)) {
227                     return bottom(widget, buffer);
228                 }
229                 break;
230 
231             case KeyEvent.KEYCODE_MOVE_HOME:
232                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
233                     return home(widget, buffer);
234                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
235                         KeyEvent.META_CTRL_ON)) {
236                     return top(widget, buffer);
237                 }
238                 break;
239 
240             case KeyEvent.KEYCODE_MOVE_END:
241                 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
242                     return end(widget, buffer);
243                 } else if (KeyEvent.metaStateHasModifiers(movementMetaState,
244                         KeyEvent.META_CTRL_ON)) {
245                     return bottom(widget, buffer);
246                 }
247                 break;
248         }
249         return false;
250     }
251 
252     /**
253      * Performs a left movement action.
254      * Moves the cursor or scrolls left by one character.
255      *
256      * @param widget The text view.
257      * @param buffer The text buffer.
258      * @return True if the event was handled.
259      */
left(TextView widget, Spannable buffer)260     protected boolean left(TextView widget, Spannable buffer) {
261         return false;
262     }
263 
264     /**
265      * Performs a right movement action.
266      * Moves the cursor or scrolls right by one character.
267      *
268      * @param widget The text view.
269      * @param buffer The text buffer.
270      * @return True if the event was handled.
271      */
right(TextView widget, Spannable buffer)272     protected boolean right(TextView widget, Spannable buffer) {
273         return false;
274     }
275 
276     /**
277      * Performs an up movement action.
278      * Moves the cursor or scrolls up by one line.
279      *
280      * @param widget The text view.
281      * @param buffer The text buffer.
282      * @return True if the event was handled.
283      */
up(TextView widget, Spannable buffer)284     protected boolean up(TextView widget, Spannable buffer) {
285         return false;
286     }
287 
288     /**
289      * Performs a down movement action.
290      * Moves the cursor or scrolls down by one line.
291      *
292      * @param widget The text view.
293      * @param buffer The text buffer.
294      * @return True if the event was handled.
295      */
down(TextView widget, Spannable buffer)296     protected boolean down(TextView widget, Spannable buffer) {
297         return false;
298     }
299 
300     /**
301      * Performs a page-up movement action.
302      * Moves the cursor or scrolls up by one page.
303      *
304      * @param widget The text view.
305      * @param buffer The text buffer.
306      * @return True if the event was handled.
307      */
pageUp(TextView widget, Spannable buffer)308     protected boolean pageUp(TextView widget, Spannable buffer) {
309         return false;
310     }
311 
312     /**
313      * Performs a page-down movement action.
314      * Moves the cursor or scrolls down by one page.
315      *
316      * @param widget The text view.
317      * @param buffer The text buffer.
318      * @return True if the event was handled.
319      */
pageDown(TextView widget, Spannable buffer)320     protected boolean pageDown(TextView widget, Spannable buffer) {
321         return false;
322     }
323 
324     /**
325      * Performs a top movement action.
326      * Moves the cursor or scrolls to the top of the buffer.
327      *
328      * @param widget The text view.
329      * @param buffer The text buffer.
330      * @return True if the event was handled.
331      */
top(TextView widget, Spannable buffer)332     protected boolean top(TextView widget, Spannable buffer) {
333         return false;
334     }
335 
336     /**
337      * Performs a bottom movement action.
338      * Moves the cursor or scrolls to the bottom of the buffer.
339      *
340      * @param widget The text view.
341      * @param buffer The text buffer.
342      * @return True if the event was handled.
343      */
bottom(TextView widget, Spannable buffer)344     protected boolean bottom(TextView widget, Spannable buffer) {
345         return false;
346     }
347 
348     /**
349      * Performs a line-start movement action.
350      * Moves the cursor or scrolls to the start of the line.
351      *
352      * @param widget The text view.
353      * @param buffer The text buffer.
354      * @return True if the event was handled.
355      */
lineStart(TextView widget, Spannable buffer)356     protected boolean lineStart(TextView widget, Spannable buffer) {
357         return false;
358     }
359 
360     /**
361      * Performs a line-end movement action.
362      * Moves the cursor or scrolls to the end of the line.
363      *
364      * @param widget The text view.
365      * @param buffer The text buffer.
366      * @return True if the event was handled.
367      */
lineEnd(TextView widget, Spannable buffer)368     protected boolean lineEnd(TextView widget, Spannable buffer) {
369         return false;
370     }
371 
372     /** {@hide} */
leftWord(TextView widget, Spannable buffer)373     protected boolean leftWord(TextView widget, Spannable buffer) {
374         return false;
375     }
376 
377     /** {@hide} */
rightWord(TextView widget, Spannable buffer)378     protected boolean rightWord(TextView widget, Spannable buffer) {
379         return false;
380     }
381 
382     /**
383      * Performs a home movement action.
384      * Moves the cursor or scrolls to the start of the line or to the top of the
385      * document depending on whether the insertion point is being moved or
386      * the document is being scrolled.
387      *
388      * @param widget The text view.
389      * @param buffer The text buffer.
390      * @return True if the event was handled.
391      */
home(TextView widget, Spannable buffer)392     protected boolean home(TextView widget, Spannable buffer) {
393         return false;
394     }
395 
396     /**
397      * Performs an end movement action.
398      * Moves the cursor or scrolls to the start of the line or to the top of the
399      * document depending on whether the insertion point is being moved or
400      * the document is being scrolled.
401      *
402      * @param widget The text view.
403      * @param buffer The text buffer.
404      * @return True if the event was handled.
405      */
end(TextView widget, Spannable buffer)406     protected boolean end(TextView widget, Spannable buffer) {
407         return false;
408     }
409 
410     /**
411      * Performs a previous paragraph movement action.
412      *
413      * @param widget the text view
414      * @param buffer the text buffer
415      * @return true if the event was handled, otherwise false.
416      */
previousParagraph(@onNull TextView widget, @NonNull Spannable buffer)417     public boolean previousParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
418         return false;
419     }
420 
421     /**
422      * Performs a next paragraph movement action.
423      *
424      * @param widget the text view
425      * @param buffer the text buffer
426      * @return true if the event was handled, otherwise false.
427      */
nextParagraph(@onNull TextView widget, @NonNull Spannable buffer)428     public boolean nextParagraph(@NonNull TextView widget, @NonNull Spannable buffer) {
429         return false;
430     }
431 
getTopLine(TextView widget)432     private int getTopLine(TextView widget) {
433         return widget.getLayout().getLineForVertical(widget.getScrollY());
434     }
435 
getBottomLine(TextView widget)436     private int getBottomLine(TextView widget) {
437         return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget));
438     }
439 
getInnerWidth(TextView widget)440     private int getInnerWidth(TextView widget) {
441         return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight();
442     }
443 
getInnerHeight(TextView widget)444     private int getInnerHeight(TextView widget) {
445         return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom();
446     }
447 
getCharacterWidth(TextView widget)448     private int getCharacterWidth(TextView widget) {
449         return (int) Math.ceil(widget.getPaint().getFontSpacing());
450     }
451 
getScrollBoundsLeft(TextView widget)452     private int getScrollBoundsLeft(TextView widget) {
453         final Layout layout = widget.getLayout();
454         final int topLine = getTopLine(widget);
455         final int bottomLine = getBottomLine(widget);
456         if (topLine > bottomLine) {
457             return 0;
458         }
459         int left = Integer.MAX_VALUE;
460         for (int line = topLine; line <= bottomLine; line++) {
461             final int lineLeft = (int) Math.floor(layout.getLineLeft(line));
462             if (lineLeft < left) {
463                 left = lineLeft;
464             }
465         }
466         return left;
467     }
468 
getScrollBoundsRight(TextView widget)469     private int getScrollBoundsRight(TextView widget) {
470         final Layout layout = widget.getLayout();
471         final int topLine = getTopLine(widget);
472         final int bottomLine = getBottomLine(widget);
473         if (topLine > bottomLine) {
474             return 0;
475         }
476         int right = Integer.MIN_VALUE;
477         for (int line = topLine; line <= bottomLine; line++) {
478             final int lineRight = (int) Math.ceil(layout.getLineRight(line));
479             if (lineRight > right) {
480                 right = lineRight;
481             }
482         }
483         return right;
484     }
485 
486     /**
487      * Performs a scroll left action.
488      * Scrolls left by the specified number of characters.
489      *
490      * @param widget The text view.
491      * @param buffer The text buffer.
492      * @param amount The number of characters to scroll by.  Must be at least 1.
493      * @return True if the event was handled.
494      * @hide
495      */
scrollLeft(TextView widget, Spannable buffer, int amount)496     protected boolean scrollLeft(TextView widget, Spannable buffer, int amount) {
497         final int minScrollX = getScrollBoundsLeft(widget);
498         int scrollX = widget.getScrollX();
499         if (scrollX > minScrollX) {
500             scrollX = Math.max(scrollX - getCharacterWidth(widget) * amount, minScrollX);
501             widget.scrollTo(scrollX, widget.getScrollY());
502             return true;
503         }
504         return false;
505     }
506 
507     /**
508      * Performs a scroll right action.
509      * Scrolls right by the specified number of characters.
510      *
511      * @param widget The text view.
512      * @param buffer The text buffer.
513      * @param amount The number of characters to scroll by.  Must be at least 1.
514      * @return True if the event was handled.
515      * @hide
516      */
scrollRight(TextView widget, Spannable buffer, int amount)517     protected boolean scrollRight(TextView widget, Spannable buffer, int amount) {
518         final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
519         int scrollX = widget.getScrollX();
520         if (scrollX < maxScrollX) {
521             scrollX = Math.min(scrollX + getCharacterWidth(widget) * amount, maxScrollX);
522             widget.scrollTo(scrollX, widget.getScrollY());
523             return true;
524         }
525         return false;
526     }
527 
528     /**
529      * Performs a scroll up action.
530      * Scrolls up by the specified number of lines.
531      *
532      * @param widget The text view.
533      * @param buffer The text buffer.
534      * @param amount The number of lines to scroll by.  Must be at least 1.
535      * @return True if the event was handled.
536      * @hide
537      */
scrollUp(TextView widget, Spannable buffer, int amount)538     protected boolean scrollUp(TextView widget, Spannable buffer, int amount) {
539         final Layout layout = widget.getLayout();
540         final int top = widget.getScrollY();
541         int topLine = layout.getLineForVertical(top);
542         if (layout.getLineTop(topLine) == top) {
543             // If the top line is partially visible, bring it all the way
544             // into view; otherwise, bring the previous line into view.
545             topLine -= 1;
546         }
547         if (topLine >= 0) {
548             topLine = Math.max(topLine - amount + 1, 0);
549             Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
550             return true;
551         }
552         return false;
553     }
554 
555     /**
556      * Performs a scroll down action.
557      * Scrolls down by the specified number of lines.
558      *
559      * @param widget The text view.
560      * @param buffer The text buffer.
561      * @param amount The number of lines to scroll by.  Must be at least 1.
562      * @return True if the event was handled.
563      * @hide
564      */
scrollDown(TextView widget, Spannable buffer, int amount)565     protected boolean scrollDown(TextView widget, Spannable buffer, int amount) {
566         final Layout layout = widget.getLayout();
567         final int innerHeight = getInnerHeight(widget);
568         final int bottom = widget.getScrollY() + innerHeight;
569         int bottomLine = layout.getLineForVertical(bottom);
570         if (layout.getLineTop(bottomLine + 1) < bottom + 1) {
571             // Less than a pixel of this line is out of view,
572             // so we must have tried to make it entirely in view
573             // and now want the next line to be in view instead.
574             bottomLine += 1;
575         }
576         final int limit = layout.getLineCount() - 1;
577         if (bottomLine <= limit) {
578             bottomLine = Math.min(bottomLine + amount - 1, limit);
579             Touch.scrollTo(widget, layout, widget.getScrollX(),
580                     layout.getLineTop(bottomLine + 1) - innerHeight);
581             return true;
582         }
583         return false;
584     }
585 
586     /**
587      * Performs a scroll page up action.
588      * Scrolls up by one page.
589      *
590      * @param widget The text view.
591      * @param buffer The text buffer.
592      * @return True if the event was handled.
593      * @hide
594      */
scrollPageUp(TextView widget, Spannable buffer)595     protected boolean scrollPageUp(TextView widget, Spannable buffer) {
596         final Layout layout = widget.getLayout();
597         final int top = widget.getScrollY() - getInnerHeight(widget);
598         int topLine = layout.getLineForVertical(top);
599         if (topLine >= 0) {
600             Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine));
601             return true;
602         }
603         return false;
604     }
605 
606     /**
607      * Performs a scroll page up action.
608      * Scrolls down by one page.
609      *
610      * @param widget The text view.
611      * @param buffer The text buffer.
612      * @return True if the event was handled.
613      * @hide
614      */
scrollPageDown(TextView widget, Spannable buffer)615     protected boolean scrollPageDown(TextView widget, Spannable buffer) {
616         final Layout layout = widget.getLayout();
617         final int innerHeight = getInnerHeight(widget);
618         final int bottom = widget.getScrollY() + innerHeight + innerHeight;
619         int bottomLine = layout.getLineForVertical(bottom);
620         if (bottomLine <= layout.getLineCount() - 1) {
621             Touch.scrollTo(widget, layout, widget.getScrollX(),
622                     layout.getLineTop(bottomLine + 1) - innerHeight);
623             return true;
624         }
625         return false;
626     }
627 
628     /**
629      * Performs a scroll to top action.
630      * Scrolls to the top of the document.
631      *
632      * @param widget The text view.
633      * @param buffer The text buffer.
634      * @return True if the event was handled.
635      * @hide
636      */
scrollTop(TextView widget, Spannable buffer)637     protected boolean scrollTop(TextView widget, Spannable buffer) {
638         final Layout layout = widget.getLayout();
639         if (getTopLine(widget) >= 0) {
640             Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0));
641             return true;
642         }
643         return false;
644     }
645 
646     /**
647      * Performs a scroll to bottom action.
648      * Scrolls to the bottom of the document.
649      *
650      * @param widget The text view.
651      * @param buffer The text buffer.
652      * @return True if the event was handled.
653      * @hide
654      */
scrollBottom(TextView widget, Spannable buffer)655     protected boolean scrollBottom(TextView widget, Spannable buffer) {
656         final Layout layout = widget.getLayout();
657         final int lineCount = layout.getLineCount();
658         if (getBottomLine(widget) <= lineCount - 1) {
659             Touch.scrollTo(widget, layout, widget.getScrollX(),
660                     layout.getLineTop(lineCount) - getInnerHeight(widget));
661             return true;
662         }
663         return false;
664     }
665 
666     /**
667      * Performs a scroll to line start action.
668      * Scrolls to the start of the line.
669      *
670      * @param widget The text view.
671      * @param buffer The text buffer.
672      * @return True if the event was handled.
673      * @hide
674      */
scrollLineStart(TextView widget, Spannable buffer)675     protected boolean scrollLineStart(TextView widget, Spannable buffer) {
676         final int minScrollX = getScrollBoundsLeft(widget);
677         int scrollX = widget.getScrollX();
678         if (scrollX > minScrollX) {
679             widget.scrollTo(minScrollX, widget.getScrollY());
680             return true;
681         }
682         return false;
683     }
684 
685     /**
686      * Performs a scroll to line end action.
687      * Scrolls to the end of the line.
688      *
689      * @param widget The text view.
690      * @param buffer The text buffer.
691      * @return True if the event was handled.
692      * @hide
693      */
scrollLineEnd(TextView widget, Spannable buffer)694     protected boolean scrollLineEnd(TextView widget, Spannable buffer) {
695         final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget);
696         int scrollX = widget.getScrollX();
697         if (scrollX < maxScrollX) {
698             widget.scrollTo(maxScrollX, widget.getScrollY());
699             return true;
700         }
701         return false;
702     }
703 }
704