Home | History | Annotate | Download | only in activity
      1 /*
      2  * Copyright (C) 2017 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.googlecode.android_scripting.activity;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.content.DialogInterface;
     23 import android.content.DialogInterface.OnClickListener;
     24 import android.content.Intent;
     25 import android.content.SharedPreferences;
     26 import android.content.SharedPreferences.Editor;
     27 import android.media.AudioManager;
     28 import android.os.Bundle;
     29 import android.preference.PreferenceManager;
     30 import android.text.Editable;
     31 import android.text.InputFilter;
     32 import android.text.InputType;
     33 import android.text.Selection;
     34 import android.text.Spanned;
     35 import android.text.TextWatcher;
     36 import android.text.style.UnderlineSpan;
     37 import android.view.KeyEvent;
     38 import android.view.Menu;
     39 import android.view.MenuItem;
     40 import android.view.View;
     41 import android.widget.CheckBox;
     42 import android.widget.EditText;
     43 import android.widget.Toast;
     44 
     45 import com.googlecode.android_scripting.BaseApplication;
     46 import com.googlecode.android_scripting.Constants;
     47 import com.googlecode.android_scripting.FileUtils;
     48 import com.googlecode.android_scripting.Log;
     49 import com.googlecode.android_scripting.R;
     50 import com.googlecode.android_scripting.ScriptStorageAdapter;
     51 import com.googlecode.android_scripting.interpreter.Interpreter;
     52 import com.googlecode.android_scripting.interpreter.InterpreterConfiguration;
     53 
     54 import java.io.File;
     55 import java.io.IOException;
     56 import java.util.ArrayList;
     57 import java.util.Arrays;
     58 import java.util.List;
     59 import java.util.Vector;
     60 import java.util.regex.Matcher;
     61 import java.util.regex.Pattern;
     62 
     63 /**
     64  * A text editor for scripts.
     65  *
     66  */
     67 public class ScriptEditor extends Activity implements OnClickListener {
     68   private static final int DIALOG_FIND_REPLACE = 2;
     69   private static final int DIALOG_LINE = 1;
     70   private EditText mNameText;
     71   private EditText mContentText;
     72   private boolean mScheduleMoveLeft;
     73   private String mLastSavedContent;
     74   private SharedPreferences mPreferences;
     75   private InterpreterConfiguration mConfiguration;
     76   private ContentTextWatcher mWatcher;
     77   private EditHistory mHistory;
     78   private File mScript;
     79   private EditText mLineNo;
     80 
     81   private boolean mIsUndoOrRedo = false;
     82   private boolean mEnableAutoClose;
     83   private boolean mAutoIndent;
     84 
     85   private EditText mSearchFind;
     86   private EditText mSearchReplace;
     87   private CheckBox mSearchCase;
     88   private CheckBox mSearchWord;
     89   private CheckBox mSearchAll;
     90   private CheckBox mSearchStart;
     91 
     92   private static enum MenuId {
     93     SAVE, SAVE_AND_RUN, PREFERENCES, API_BROWSER, HELP, SHARE, GOTO, SEARCH;
     94     public int getId() {
     95       return ordinal() + Menu.FIRST;
     96     }
     97   }
     98 
     99   private static enum RequestCode {
    100     RPC_HELP
    101   }
    102 
    103   private int readIntPref(String key, int defaultValue, int maxValue) {
    104     int val;
    105     try {
    106       val = Integer.parseInt(mPreferences.getString(key, Integer.toString(defaultValue)));
    107     } catch (NumberFormatException e) {
    108       val = defaultValue;
    109     }
    110     val = Math.max(0, Math.min(val, maxValue));
    111     return val;
    112   }
    113 
    114   @Override
    115   protected void onCreate(Bundle savedInstanceState) {
    116     super.onCreate(savedInstanceState);
    117     setContentView(R.layout.script_editor);
    118     mNameText = (EditText) findViewById(R.id.script_editor_title);
    119     mContentText = (EditText) findViewById(R.id.script_editor_body);
    120     mHistory = new EditHistory();
    121     mWatcher = new ContentTextWatcher(mHistory);
    122     mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
    123     updatePreferences();
    124 
    125     mScript = new File(getIntent().getStringExtra(Constants.EXTRA_SCRIPT_PATH));
    126     mNameText.setText(mScript.getName());
    127     mNameText.setSelected(true);
    128     // NOTE: This appears to be the only way to get Android to put the cursor to the beginning of
    129     // the EditText field.
    130     mNameText.setSelection(1);
    131     mNameText.extendSelection(0);
    132     mNameText.setSelection(0);
    133     mLastSavedContent = getIntent().getStringExtra(Constants.EXTRA_SCRIPT_CONTENT);
    134     if (mLastSavedContent == null) {
    135       try {
    136         mLastSavedContent = FileUtils.readToString(mScript);
    137       } catch (IOException e) {
    138         Log.e("Failed to read script.", e);
    139         mLastSavedContent = "";
    140       } finally {
    141       }
    142     }
    143 
    144     mContentText.setText(mLastSavedContent);
    145     InputFilter[] oldFilters = mContentText.getFilters();
    146     List<InputFilter> filters = new ArrayList<InputFilter>(oldFilters.length + 1);
    147     filters.addAll(Arrays.asList(oldFilters));
    148     filters.add(new ContentInputFilter());
    149     mContentText.setFilters(filters.toArray(oldFilters));
    150     mContentText.addTextChangedListener(mWatcher);
    151     mConfiguration = ((BaseApplication) getApplication()).getInterpreterConfiguration();
    152     // Disables volume key beep.
    153     setVolumeControlStream(AudioManager.STREAM_MUSIC);
    154     mLineNo = new EditText(this);
    155     mLineNo.setInputType(InputType.TYPE_CLASS_NUMBER);
    156     int lastLocation = mPreferences.getInt("lasteditpos." + mScript, -1);
    157     if (lastLocation >= 0) {
    158       mContentText.requestFocus();
    159       mContentText.setSelection(lastLocation);
    160     }
    161   }
    162 
    163   @Override
    164   protected void onResume() {
    165     super.onResume();
    166     updatePreferences();
    167   }
    168 
    169   private void updatePreferences() {
    170     mContentText.setTextSize(readIntPref("editor_fontsize", 10, 30));
    171     mEnableAutoClose = mPreferences.getBoolean("enableAutoClose", true);
    172     mAutoIndent = mPreferences.getBoolean("editor_auto_indent", false);
    173     mContentText.setHorizontallyScrolling(mPreferences.getBoolean("editor_no_wrap", false));
    174   }
    175 
    176   @Override
    177   public boolean onCreateOptionsMenu(Menu menu) {
    178     super.onCreateOptionsMenu(menu);
    179     menu.add(0, MenuId.SAVE.getId(), 0, "Save & Exit").setIcon(android.R.drawable.ic_menu_save);
    180     menu.add(0, MenuId.SAVE_AND_RUN.getId(), 0, "Save & Run").setIcon(
    181         android.R.drawable.ic_media_play);
    182     menu.add(0, MenuId.PREFERENCES.getId(), 0, "Preferences").setIcon(
    183         android.R.drawable.ic_menu_preferences);
    184     menu.add(0, MenuId.API_BROWSER.getId(), 0, "API Browser").setIcon(
    185         android.R.drawable.ic_menu_info_details);
    186     menu.add(0, MenuId.SHARE.getId(), 0, "Share").setIcon(android.R.drawable.ic_menu_share);
    187     menu.add(0, MenuId.GOTO.getId(), 0, "GoTo").setIcon(android.R.drawable.ic_menu_directions);
    188     menu.add(0, MenuId.SEARCH.getId(), 0, "Find").setIcon(android.R.drawable.ic_menu_search);
    189     return true;
    190   }
    191 
    192   @Override
    193   public boolean onOptionsItemSelected(MenuItem item) {
    194     if (item.getItemId() == MenuId.SAVE.getId()) {
    195       save();
    196       finish();
    197     } else if (item.getItemId() == MenuId.SAVE_AND_RUN.getId()) {
    198       save();
    199       Interpreter interpreter =
    200           mConfiguration.getInterpreterForScript(mNameText.getText().toString());
    201       if (interpreter != null) { // We may be editing an unknown type.
    202         Intent intent = new Intent(this, ScriptingLayerService.class);
    203         intent.setAction(Constants.ACTION_LAUNCH_FOREGROUND_SCRIPT);
    204         intent.putExtra(Constants.EXTRA_SCRIPT_PATH, mScript.getAbsolutePath());
    205         startService(intent);
    206       } else {
    207         // TODO(damonkohler): Should remove menu option.
    208         Toast.makeText(this, "Can't run this type.", Toast.LENGTH_SHORT).show();
    209       }
    210       finish();
    211     } else if (item.getItemId() == MenuId.PREFERENCES.getId()) {
    212       startActivity(new Intent(this, Preferences.class));
    213     } else if (item.getItemId() == MenuId.API_BROWSER.getId()) {
    214       Intent intent = new Intent(this, ApiBrowser.class);
    215       intent.putExtra(Constants.EXTRA_SCRIPT_PATH, mNameText.getText().toString());
    216       intent.putExtra(Constants.EXTRA_INTERPRETER_NAME,
    217           mConfiguration.getInterpreterForScript(mNameText.getText().toString()).getName());
    218       intent.putExtra(Constants.EXTRA_SCRIPT_TEXT, mContentText.getText().toString());
    219       startActivityForResult(intent, RequestCode.RPC_HELP.ordinal());
    220     } else if (item.getItemId() == MenuId.SHARE.getId()) {
    221       Intent intent = new Intent(Intent.ACTION_SEND);
    222       intent.putExtra(Intent.EXTRA_TEXT, mContentText.getText().toString());
    223       intent.putExtra(Intent.EXTRA_SUBJECT, "Share " + mNameText.getText().toString());
    224       intent.setType("text/plain");
    225       startActivity(Intent.createChooser(intent, "Send Script to:"));
    226     } else if (item.getItemId() == MenuId.GOTO.getId()) {
    227       showDialog(DIALOG_LINE);
    228     } else if (item.getItemId() == MenuId.SEARCH.getId()) {
    229       showDialog(DIALOG_FIND_REPLACE);
    230     }
    231     return super.onOptionsItemSelected(item);
    232   }
    233 
    234   @Override
    235   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    236     super.onActivityResult(requestCode, resultCode, data);
    237     RequestCode request = RequestCode.values()[requestCode];
    238 
    239     if (resultCode == RESULT_OK) {
    240       switch (request) {
    241       case RPC_HELP:
    242         String rpcText = data.getStringExtra(Constants.EXTRA_RPC_HELP_TEXT);
    243         insertContent(rpcText);
    244         break;
    245       default:
    246         break;
    247       }
    248     } else {
    249       switch (request) {
    250       case RPC_HELP:
    251         break;
    252       default:
    253         break;
    254       }
    255     }
    256   }
    257 
    258   private void save() {
    259     int start = mContentText.getSelectionStart();
    260     mLastSavedContent = mContentText.getText().toString();
    261     mScript = new File(mScript.getParent(), mNameText.getText().toString());
    262     ScriptStorageAdapter.writeScript(mScript, mLastSavedContent);
    263     Toast.makeText(this, "Saved " + mNameText.getText().toString(), Toast.LENGTH_SHORT).show();
    264     Editor e = mPreferences.edit();
    265     e.putInt("lasteditpos." + mScript, start);
    266     e.commit();
    267   }
    268 
    269   private void insertContent(String text) {
    270     int selectionStart = Math.min(mContentText.getSelectionStart(), mContentText.getSelectionEnd());
    271     int selectionEnd = Math.max(mContentText.getSelectionStart(), mContentText.getSelectionEnd());
    272     mContentText.getEditableText().replace(selectionStart, selectionEnd, text);
    273   }
    274 
    275   @Override
    276   public boolean onKeyDown(int keyCode, KeyEvent event) {
    277     if (keyCode == KeyEvent.KEYCODE_BACK && hasContentChanged()) {
    278       AlertDialog.Builder alert = new AlertDialog.Builder(this);
    279       setVolumeControlStream(AudioManager.STREAM_MUSIC);
    280       alert.setCancelable(false);
    281       alert.setTitle("Confirm exit");
    282       alert.setMessage("Would you like to save?");
    283       alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
    284         public void onClick(DialogInterface dialog, int whichButton) {
    285           save();
    286           finish();
    287         }
    288       });
    289       alert.setNegativeButton("No", new DialogInterface.OnClickListener() {
    290         public void onClick(DialogInterface dialog, int whichButton) {
    291           finish();
    292         }
    293       });
    294       alert.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
    295         public void onClick(DialogInterface dialog, int whichButton) {
    296         }
    297       });
    298       alert.show();
    299       return true;
    300     } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
    301       redo();
    302       return true;
    303     } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
    304       undo();
    305       return true;
    306     } else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
    307       showDialog(DIALOG_FIND_REPLACE);
    308       return true;
    309     } else {
    310       return super.onKeyDown(keyCode, event);
    311     }
    312   }
    313 
    314   @Override
    315   protected Dialog onCreateDialog(int id, Bundle args) {
    316     AlertDialog.Builder b = new AlertDialog.Builder(this);
    317     if (id == DIALOG_LINE) {
    318       b.setTitle("Goto Line");
    319       b.setView(mLineNo);
    320       b.setPositiveButton("Ok", new OnClickListener() {
    321 
    322         @Override
    323         public void onClick(DialogInterface dialog, int which) {
    324           gotoLine(Integer.parseInt(mLineNo.getText().toString()));
    325         }
    326       });
    327       b.setNegativeButton("Cancel", null);
    328       return b.create();
    329     } else if (id == DIALOG_FIND_REPLACE) {
    330       View v = getLayoutInflater().inflate(R.layout.findreplace, null);
    331       mSearchFind = (EditText) v.findViewById(R.id.searchFind);
    332       mSearchReplace = (EditText) v.findViewById(R.id.searchReplace);
    333       mSearchAll = (CheckBox) v.findViewById(R.id.searchAll);
    334       mSearchCase = (CheckBox) v.findViewById(R.id.searchCase);
    335       mSearchStart = (CheckBox) v.findViewById(R.id.searchStart);
    336       mSearchWord = (CheckBox) v.findViewById(R.id.searchWord);
    337       b.setTitle("Search and Replace");
    338       b.setView(v);
    339       b.setPositiveButton("Find", this);
    340       b.setNeutralButton("Next", this);
    341       b.setNegativeButton("Replace", this);
    342       return b.create();
    343     }
    344 
    345     return super.onCreateDialog(id, args);
    346   }
    347 
    348   @Override
    349   protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
    350     if (id == DIALOG_LINE) {
    351       mLineNo.setText(String.valueOf(getLineNo()));
    352     } else if (id == DIALOG_FIND_REPLACE) {
    353       mSearchStart.setChecked(false);
    354     }
    355     super.onPrepareDialog(id, dialog, args);
    356   }
    357 
    358   protected int getLineNo() {
    359     int pos = mContentText.getSelectionStart();
    360     String text = mContentText.getText().toString();
    361     int i = 0;
    362     int n = 1;
    363     while (i < pos) {
    364       int j = text.indexOf("\n", i);
    365       if (j < 0) {
    366         break;
    367       }
    368       i = j + 1;
    369       if (i < pos) {
    370         n += 1;
    371       }
    372     }
    373     return n;
    374   }
    375 
    376   protected void gotoLine(int line) {
    377     String text = mContentText.getText().toString();
    378     if (text.length() < 1) {
    379       return;
    380     }
    381     int i = 0;
    382     int n = 1;
    383     while (i < text.length() && n < line) {
    384       int j = text.indexOf("\n", i);
    385       if (j < 0) {
    386         break;
    387       }
    388       i = j + 1;
    389       n += 1;
    390     }
    391     mContentText.setSelection(Math.min(text.length() - 1, i));
    392   }
    393 
    394   @Override
    395   protected void onUserLeaveHint() {
    396     if (hasContentChanged()) {
    397       save();
    398     }
    399   }
    400 
    401   private boolean hasContentChanged() {
    402     return !mLastSavedContent.equals(mContentText.getText().toString());
    403   }
    404 
    405   private final class ContentInputFilter implements InputFilter {
    406     @Override
    407     public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart,
    408         int dend) {
    409       if (end - start == 1) {
    410         Interpreter ip = mConfiguration.getInterpreterForScript(mNameText.getText().toString());
    411         String auto = null;
    412         if (ip != null && mEnableAutoClose) {
    413           auto = ip.getLanguage().autoClose(source.charAt(start));
    414         }
    415         // Auto indent code?
    416         if (auto == null && source.charAt(start) == '\n' && mAutoIndent) {
    417           int i = dstart - 1;
    418           int spaces = 0;
    419           while ((i >= 0) && dest.charAt(i) != '\n') {
    420             i -= 1; // Find start of line.
    421           }
    422           i += 1;
    423           while (i < dest.length() && dest.charAt(i++) == ' ') {
    424             spaces += 1;
    425           }
    426           if (spaces > 0) {
    427             return String.format("\n%" + spaces + "s", " ");
    428           }
    429         }
    430         if (auto != null) {
    431           mScheduleMoveLeft = true;
    432           return auto;
    433         }
    434       }
    435       return null;
    436     }
    437   }
    438 
    439   private final class ContentTextWatcher implements TextWatcher {
    440     private final EditHistory mmEditHistory;
    441     private CharSequence mmBeforeChange;
    442     private CharSequence mmAfterChange;
    443 
    444     private ContentTextWatcher(EditHistory history) {
    445       mmEditHistory = history;
    446     }
    447 
    448     @Override
    449     public void onTextChanged(CharSequence s, int start, int before, int count) {
    450       if (!mIsUndoOrRedo) {
    451         mmAfterChange = s.subSequence(start, start + count);
    452         mmEditHistory.add(new EditItem(start, mmBeforeChange, mmAfterChange));
    453       }
    454     }
    455 
    456     @Override
    457     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    458       if (!mIsUndoOrRedo) {
    459         mmBeforeChange = s.subSequence(start, start + count);
    460       }
    461     }
    462 
    463     @Override
    464     public void afterTextChanged(Editable s) {
    465       if (mScheduleMoveLeft) {
    466         mScheduleMoveLeft = false;
    467         Selection.moveLeft(mContentText.getText(), mContentText.getLayout());
    468       }
    469     }
    470   }
    471 
    472   /**
    473    * Keeps track of all the edit history of a text.
    474    */
    475   private final class EditHistory {
    476     int mmPosition = 0;
    477     private final Vector<EditItem> mmHistory = new Vector<EditItem>();
    478 
    479     /**
    480      * Adds a new edit operation to the history at the current position. If executed after a call to
    481      * getPrevious() removes all the future history (elements with positions >= current history
    482      * position).
    483      *
    484      */
    485     private void add(EditItem item) {
    486       mmHistory.setSize(mmPosition);
    487       mmHistory.add(item);
    488       mmPosition++;
    489     }
    490 
    491     /**
    492      * Traverses the history backward by one position, returns and item at that position.
    493      */
    494     private EditItem getPrevious() {
    495       if (mmPosition == 0) {
    496         return null;
    497       }
    498       mmPosition--;
    499       return mmHistory.get(mmPosition);
    500     }
    501 
    502     /**
    503      * Traverses the history forward by one position, returns and item at that position.
    504      */
    505     private EditItem getNext() {
    506       if (mmPosition == mmHistory.size()) {
    507         return null;
    508       }
    509       EditItem item = mmHistory.get(mmPosition);
    510       mmPosition++;
    511       return item;
    512     }
    513   }
    514 
    515   /**
    516    * Represents a single edit operation.
    517    */
    518   private final class EditItem {
    519     private final int mmIndex;
    520     private final CharSequence mmBefore;
    521     private final CharSequence mmAfter;
    522 
    523     /**
    524      * Constructs EditItem of a modification that was applied at position start and replaced
    525      * CharSequence before with CharSequence after.
    526      */
    527     public EditItem(int start, CharSequence before, CharSequence after) {
    528       mmIndex = start;
    529       mmBefore = before;
    530       mmAfter = after;
    531     }
    532   }
    533 
    534   private void undo() {
    535     EditItem edit = mHistory.getPrevious();
    536     if (edit == null) {
    537       return;
    538     }
    539     Editable text = mContentText.getText();
    540     int start = edit.mmIndex;
    541     int end = start + (edit.mmAfter != null ? edit.mmAfter.length() : 0);
    542     mIsUndoOrRedo = true;
    543     text.replace(start, end, edit.mmBefore);
    544     mIsUndoOrRedo = false;
    545     // This will get rid of underlines inserted when editor tries to come up with a suggestion.
    546     for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) {
    547       text.removeSpan(o);
    548     }
    549     Selection.setSelection(text, edit.mmBefore == null ? start : (start + edit.mmBefore.length()));
    550   }
    551 
    552   private void redo() {
    553     EditItem edit = mHistory.getNext();
    554     if (edit == null) {
    555       return;
    556     }
    557     Editable text = mContentText.getText();
    558     int start = edit.mmIndex;
    559     int end = start + (edit.mmBefore != null ? edit.mmBefore.length() : 0);
    560     mIsUndoOrRedo = true;
    561     text.replace(start, end, edit.mmAfter);
    562     mIsUndoOrRedo = false;
    563     for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) {
    564       text.removeSpan(o);
    565     }
    566     Selection.setSelection(text, edit.mmAfter == null ? start : (start + edit.mmAfter.length()));
    567   }
    568 
    569   @Override
    570   public void onClick(DialogInterface dialog, int which) {
    571     int start = mContentText.getSelectionStart();
    572     int end = mContentText.getSelectionEnd();
    573     String original = mContentText.getText().toString();
    574     if (start == end || which != AlertDialog.BUTTON_NEGATIVE) {
    575       end = original.length();
    576     }
    577     if (which == AlertDialog.BUTTON_NEUTRAL) {
    578       start += 1;
    579     }
    580     if (mSearchStart.isChecked()) {
    581       start = 0;
    582       end = original.length();
    583     }
    584     String findText = mSearchFind.getText().toString();
    585     String replaceText = mSearchReplace.getText().toString();
    586     String search = Pattern.quote(findText);
    587     int flags = 0;
    588     if (!mSearchCase.isChecked()) {
    589       flags |= Pattern.CASE_INSENSITIVE;
    590     }
    591     if (mSearchWord.isChecked()) {
    592       search = "\\b" + search + "\\b";
    593     }
    594     Pattern p = Pattern.compile(search, flags);
    595     Matcher m = p.matcher(original);
    596     m.region(start, end);
    597     if (!m.find()) {
    598       Toast.makeText(this, "Search not found.", Toast.LENGTH_SHORT).show();
    599       return;
    600     }
    601     int foundpos = m.start();
    602     if (which != AlertDialog.BUTTON_NEGATIVE) { // Find
    603       mContentText.setSelection(foundpos, foundpos + findText.length());
    604     } else { // Replace
    605       String s;
    606       // Seems to be a bug in the android 2.2 implementation of replace... regions not returning
    607       // whole string.
    608       m = p.matcher(original.substring(start, end));
    609       String replace = Matcher.quoteReplacement(replaceText);
    610       if (mSearchAll.isChecked()) {
    611         s = m.replaceAll(replace);
    612       } else {
    613         s = m.replaceFirst(replace);
    614       }
    615       mContentText.setText(original.substring(0, start) + s + original.substring(end));
    616       mContentText.setSelection(foundpos, foundpos + replaceText.length());
    617     }
    618     mContentText.requestFocus();
    619   }
    620 }
    621