org.openintents.notepad.NoteEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.openintents.notepad.NoteEditor.java

Source

/* 
 * Copyright (C) 2008-2010 OpenIntents.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Original copyright:
 * Based on the Android SDK sample application NotePad.
 * Copyright (C) 2007 Google Inc.
 * Licensed under the Apache License, Version 2.0.
 */

package org.openintents.notepad;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.Editable;
import android.text.Layout;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.ArrowKeyMovementMethod;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import org.openintents.intents.CryptoIntents;
import org.openintents.intents.NotepadIntents;
import org.openintents.notepad.NotePad.Notes;
import org.openintents.notepad.activity.SaveFileActivity;
import org.openintents.notepad.crypto.EncryptActivity;
import org.openintents.notepad.dialog.DeleteConfirmationDialog;
import org.openintents.notepad.dialog.ThemeDialog;
import org.openintents.notepad.dialog.ThemeDialog.ThemeDialogListener;
import org.openintents.notepad.filename.DialogHostingActivity;
import org.openintents.notepad.intents.NotepadInternalIntents;
import org.openintents.notepad.noteslist.NotesList;
import org.openintents.notepad.theme.ThemeAttributes;
import org.openintents.notepad.theme.ThemeNotepad;
import org.openintents.notepad.theme.ThemeUtils;
import org.openintents.notepad.util.ExtractTitle;
import org.openintents.notepad.util.FileUriUtils;
import org.openintents.notepad.util.SendNote;
import org.openintents.notepad.wrappers.WrapActionBar;
import org.openintents.util.MenuIntentOptionsWithIcons;
import org.openintents.util.UpperCaseTransformationMethod;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Arrays;
import java.util.List;

/**
 * A generic activity for editing a note in a database. This can be used either
 * to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
 * {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}
 * .
 */
public class NoteEditor extends Activity implements ThemeDialogListener {
    private static final String TAG = "NoteEditor";
    private static final boolean DEBUG = false;

    /**
     * Standard projection for the interesting columns of a normal note.
     */
    private static final String[] PROJECTION = new String[] { Notes._ID, // 0
            Notes.NOTE, // 1
            Notes.TAGS, // 2
            Notes.ENCRYPTED, // 3
            Notes.THEME, // 4
            Notes.SELECTION_START, // 5
            Notes.SELECTION_END, // 6
            Notes.SCROLL_POSITION, // 7
    };
    /**
     * The index of the note column
     */
    private static final int COLUMN_INDEX_ID = 0;
    private static final int COLUMN_INDEX_NOTE = 1;
    private static final int COLUMN_INDEX_TAGS = 2;
    private static final int COLUMN_INDEX_ENCRYPTED = 3;
    private static final int COLUMN_INDEX_THEME = 4;
    private static final int COLUMN_INDEX_SELECTION_START = 5;
    private static final int COLUMN_INDEX_SELECTION_END = 6;
    private static final int COLUMN_INDEX_SCROLL_POSITION = 7;

    // This is our state data that is stored when freezing.
    private static final String BUNDLE_ORIGINAL_CONTENT = "original_content";
    private static final String BUNDLE_UNDO_REVERT = "undo_revert";
    private static final String BUNDLE_STATE = "state";
    private static final String BUNDLE_URI = "uri";
    private static final String BUNDLE_SELECTION_START = "selection_start";
    private static final String BUNDLE_SELECTION_STOP = "selection_stop";
    // private static final String BUNDLE_FILENAME = "filename";
    private static final String BUNDLE_FILE_CONTENT = "file_content";
    private static final String BUNDLE_APPLY_TEXT = "apply_text";
    private static final String BUNDLE_APPLY_TEXT_BEFORE = "apply_text_before";
    private static final String BUNDLE_APPLY_TEXT_AFTER = "apply_text_after";

    // Identifiers for our menu items.
    private static final int MENU_REVERT = Menu.FIRST;
    private static final int MENU_DISCARD = Menu.FIRST + 1;
    private static final int MENU_DELETE = Menu.FIRST + 2;
    private static final int MENU_ENCRYPT = Menu.FIRST + 3;
    private static final int MENU_UNENCRYPT = Menu.FIRST + 4;
    private static final int MENU_IMPORT = Menu.FIRST + 5;
    private static final int MENU_SAVE = Menu.FIRST + 6;
    private static final int MENU_SAVE_AS = Menu.FIRST + 7;
    private static final int MENU_THEME = Menu.FIRST + 8;
    private static final int MENU_SETTINGS = Menu.FIRST + 9;
    private static final int MENU_SEND = Menu.FIRST + 10;
    private static final int MENU_WORD_COUNT = Menu.FIRST + 11;
    private static final int MENU_SEARCH = Menu.FIRST + 12;

    // private static final int REQUEST_CODE_ENCRYPT = 1;
    private static final int REQUEST_CODE_DECRYPT = 2;
    private static final int REQUEST_CODE_TEXT_SELECTION_ALTERNATIVE = 3;
    private static final int REQUEST_CODE_SAVE_AS = 4;
    private static final int REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE = 5;
    private static final int REQUEST_CODE_PERMISSION_WRITE_EXTERNAL_STORAGE = 6;

    // The different distinct states the activity can be run in.
    private static final int STATE_EDIT = 0;
    private static final int STATE_INSERT = 1;
    private static final int STATE_EDIT_NOTE_FROM_SDCARD = 2;
    private static final int STATE_EDIT_EXTERNAL_NOTE = 3;

    private static final int DIALOG_UNSAVED_CHANGES = 1;
    private static final int DIALOG_THEME = 2;
    private static final int DIALOG_DELETE = 3;

    private static final int GROUP_ID_TEXT_SELECTION_ALTERNATIVE = 1234; // some
    // number
    // that
    // must
    // not
    // collide
    // with
    // others
    /**
     * Lines mode: 0..no line. 2..show lines only where there is text (padding
     * width). 3..show lines only where there is text (full width). 4..show
     * lines for whole page (padding width). 5..show lines for whole page (full
     * width).
     */
    private static int mLinesMode;
    private static int mLinesColor;
    /**
     * static string for hack. Only used for configuration changes.
     */
    private static String sDecryptedText = null;
    private static int sSelectionStart = 0;
    private static int sSelectionStop = 0;
    private static boolean mActionBarAvailable;

    static {
        try {
            WrapActionBar.checkAvailable();
            mActionBarAvailable = true;
        } catch (Throwable t) {
            mActionBarAvailable = false;
        }
    }

    private String mTextTypeface;
    private float mTextSize;
    private boolean mTextUpperCaseFont;
    private int mTextColor;
    private int mBackgroundPadding;
    Typeface mCurrentTypeface = null;
    private int mState;
    private boolean mNoteOnly = false;
    private Uri mUri;
    private Cursor mCursor;
    private EditText mText;

    // private String mTags;
    private String mOriginalContent;
    private String mUndoRevert;
    private int mSelectionStart;
    private int mSelectionStop;
    // If the following are not null, the result of
    // a text change (TEXT_SELECTION_ALTERNATIVE) still needs to be applied.
    private String mApplyText;
    private String mApplyTextBefore;
    private String mApplyTextAfter;
    // Whether this note is stored in encrypted format
    private long mEncrypted;
    private String mDecryptedText;
    private String mFileContent;
    private String mTheme;
    /**
     * Which features are supported (which columns are available in the
     * database)? Everything is supported by default.
     */
    private boolean hasNoteColumn = true;
    private boolean hasTagsColumn = true;
    private boolean hasEncryptionColumn = true;
    private boolean hasThemeColumn = true;
    private boolean hasSelection_startColumn = true;
    private boolean hasSelection_endColumn = true;
    // TODO use this flag to make the note read-only
    private boolean mReadOnly;
    private TextWatcher mTextWatcherSdCard = new TextWatcher() {
        public void afterTextChanged(Editable s) {
            // if (DEBUG) Log.d(TAG, "after");
            mFileContent = s.toString();
            updateTitleSdCard();
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // if (DEBUG) Log.d(TAG, "before");
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // if (DEBUG) Log.d(TAG, "on");
        }

    };
    private TextWatcher mTextWatcherCharCount = new TextWatcher() {
        public void afterTextChanged(Editable s) {
            updateCharCount();
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }
    };
    private BroadcastReceiver mUpdateReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            intent.getStringExtra(DialogHostingActivity.EXTRA_FILENAME);
            updateTitleSdCard();
        }
    };

    public static void deleteStaticDecryptedText() {
        if (DEBUG) {
            Log.d(TAG, "deleting decrypted text: " + sDecryptedText);
        }
        sDecryptedText = null;
    }

    /**
     * Set theme for all lists.
     *
     * @param context
     * @param theme
     */
    public static void setThemeForAll(Context context, String theme) {
        ContentValues values = new ContentValues();
        values.put(Notes.THEME, theme);
        context.getContentResolver().update(Notes.CONTENT_URI, values, null, null);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (DEBUG) {
            Log.d(TAG, "onCreate()");
        }

        if (getIntent().getAction().equals(Intent.ACTION_CREATE_SHORTCUT)) {
            createShortcut();
            return;
        }

        if (savedInstanceState == null) {
            // sDecryptedText has no use for brand new activities
            sDecryptedText = null;
        }

        // Usually, sDecryptedText == null.
        mDecryptedText = sDecryptedText;
        if (sDecryptedText != null) {
            // we use the text right now,
            // so don't encrypt the text anymore.
            EncryptActivity.cancelEncrypt();

            if (EncryptActivity.getPendingEncryptActivities() == 0) {
                if (DEBUG) {
                    Log.d(TAG, "sDecryptedText = null");
                }
                // no more encrypt activies will be called
                sDecryptedText = null;
            }
        }

        mSelectionStart = 0;
        mSelectionStop = 0;

        // If an instance of this activity had previously stopped, we can
        // get the original text it started with.
        if (savedInstanceState != null) {
            mOriginalContent = savedInstanceState.getString(BUNDLE_ORIGINAL_CONTENT);
            mUndoRevert = savedInstanceState.getString(BUNDLE_UNDO_REVERT);
            mState = savedInstanceState.getInt(BUNDLE_STATE);
            String uriString = savedInstanceState.getString(BUNDLE_URI);
            if (uriString != null) {
                mUri = Uri.parse(uriString);
            }

            mSelectionStart = savedInstanceState.getInt(BUNDLE_SELECTION_START);
            mSelectionStop = savedInstanceState.getInt(BUNDLE_SELECTION_STOP);
            mFileContent = savedInstanceState.getString(BUNDLE_FILE_CONTENT);
            if (mApplyText == null && mApplyTextBefore == null && mApplyTextAfter == null) {
                // Only read values if they had not been set by
                // onActivityResult() yet:
                mApplyText = savedInstanceState.getString(BUNDLE_APPLY_TEXT);
                mApplyTextBefore = savedInstanceState.getString(BUNDLE_APPLY_TEXT_BEFORE);
                mApplyTextAfter = savedInstanceState.getString(BUNDLE_APPLY_TEXT_AFTER);
            }
        } else {
            // Do some setup based on the action being performed.
            final Intent intent = getIntent();
            final String action = intent.getAction();

            if (Intent.ACTION_EDIT.equals(action) || Intent.ACTION_VIEW.equals(action)) {
                // Requested to edit: set that state, and the data being edited.
                mState = STATE_EDIT;
                mUri = intent.getData();

                if (mUri != null && mUri.getScheme().equals("file")) {
                    mState = STATE_EDIT_NOTE_FROM_SDCARD;
                    // Load the file into a new note.

                    if (ContextCompat.checkSelfPermission(this,
                            Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        mFileContent = readFile(FileUriUtils.getFile(mUri));
                    } else {
                        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                                Manifest.permission.READ_EXTERNAL_STORAGE)) {

                        } else {
                            ActivityCompat.requestPermissions(this,
                                    new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
                                    REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE);
                            mFileContent = getString(R.string.request_permissions);
                        }
                    }
                } else if (mUri != null && !mUri.getAuthority().equals(NotePad.AUTHORITY)) {
                    // Note a notepad note. Treat slightly differently.
                    // (E.g. a note from OI Shopping List)
                    mState = STATE_EDIT_EXTERNAL_NOTE;
                }

            } else if (Intent.ACTION_INSERT.equals(action) || Intent.ACTION_SEND.equals(action)) {

                // Use theme of most recently modified note:
                ContentValues values = new ContentValues(1);
                String theme = getMostRecentlyUsedTheme();
                values.put(Notes.THEME, theme);

                String tags = intent.getStringExtra(NotepadInternalIntents.EXTRA_TAGS);
                values.put(Notes.TAGS, tags);

                if (mText != null) {
                    values.put(Notes.SELECTION_START, mText.getSelectionStart());
                    values.put(Notes.SELECTION_END, mText.getSelectionEnd());
                }

                // Requested to insert: set that state, and create a new entry
                // in the container.
                mState = STATE_INSERT;
                /*
                 * intent.setAction(Intent.ACTION_EDIT); intent.setData(mUri);
                * setIntent(intent);
                */

                if (Intent.ACTION_SEND.equals(action)) {
                    values.put(Notes.NOTE, getIntent().getStringExtra(Intent.EXTRA_TEXT));
                    mUri = getContentResolver().insert(Notes.CONTENT_URI, values);
                } else {
                    mUri = getContentResolver().insert(intent.getData(), values);
                }

                // If we were unable to create a new note, then just finish
                // this activity. A RESULT_CANCELED will be sent back to the
                // original activity if they requested a result.
                if (mUri == null) {
                    Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
                    finish();
                    return;
                }

                // The new entry was created, so assume all will end well and
                // set the result to be returned.
                // setResult(RESULT_OK, (new
                // Intent()).setAction(mUri.toString()));
                setResult(RESULT_OK, intent);

            } else {
                // Whoops, unknown action! Bail.
                Log.e(TAG, "Unknown action, exiting");
                finish();
                return;
            }
        }

        // setup actionbar
        if (mActionBarAvailable) {
            requestWindowFeature(Window.FEATURE_ACTION_BAR);
            WrapActionBar bar = new WrapActionBar(this);
            bar.setDisplayHomeAsUpEnabled(true);
            // force to show the actionbar on version 14+
            if (Integer.valueOf(android.os.Build.VERSION.SDK) >= 14) {
                bar.setHomeButtonEnabled(true);
            }
        } else {
            requestWindowFeature(Window.FEATURE_RIGHT_ICON);
        }

        // Set the layout for this activity. You can find it in
        // res/layout/note_editor.xml
        setContentView(R.layout.note_editor);

        // The text view for our note, identified by its ID in the XML file.
        mText = (EditText) findViewById(R.id.note);

        if (mState == STATE_EDIT_NOTE_FROM_SDCARD) {
            // We add a text watcher, so that the title can be updated
            // to indicate a small "*" if modified.
            mText.addTextChangedListener(mTextWatcherSdCard);
        }

        if (mState != STATE_EDIT_NOTE_FROM_SDCARD) {

            // Check if we load a note from notepad or from some external module
            if (mState == STATE_EDIT_EXTERNAL_NOTE) {
                // Get all the columns as we don't know which columns are
                // supported.
                mCursor = managedQuery(mUri, null, null, null, null);

                // Now check which columns are available
                List<String> columnNames = Arrays.asList(mCursor.getColumnNames());

                if (!columnNames.contains(Notes.NOTE)) {
                    hasNoteColumn = false;
                }
                if (!columnNames.contains(Notes.TAGS)) {
                    hasTagsColumn = false;
                }
                if (!columnNames.contains(Notes.ENCRYPTED)) {
                    hasEncryptionColumn = false;
                }
                if (!columnNames.contains(Notes.THEME)) {
                    hasThemeColumn = false;
                }
                if (!columnNames.contains(Notes.SELECTION_START)) {
                    hasSelection_startColumn = false;
                }
                if (!columnNames.contains(Notes.SELECTION_END)) {
                    hasSelection_endColumn = false;
                }
            } else {
                // Get the note!
                mCursor = managedQuery(mUri, PROJECTION, null, null, null);

                // It's not an external note, so all the columns are available
                // in the database
            }
        } else {
            mCursor = null;
        }

        mText.addTextChangedListener(mTextWatcherCharCount);

        initSearchPanel();
    }

    /**
     * Return intent data when invoked with
     * action=android.intent.action.CREATE_SHORTCUT
     */
    private void createShortcut() {
        Intent intent = new Intent(Intent.ACTION_INSERT, Notes.CONTENT_URI, getApplicationContext(),
                NoteEditor.class);

        Intent result = new Intent();
        result.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
        result.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
                ShortcutIconResource.fromContext(getApplicationContext(), R.drawable.ic_launcher_notepad));
        result.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.new_note));

        setResult(RESULT_OK, result);

        finish();
    }

    /**
     * Returns most recently used theme, or null.
     *
     * @return
     */
    private String getMostRecentlyUsedTheme() {
        String theme = null;
        Cursor c = getContentResolver().query(Notes.CONTENT_URI, new String[] { Notes.THEME }, null, null,
                Notes.MODIFIED_DATE + " DESC");
        if (c != null && c.moveToFirst()) {
            theme = c.getString(0);
        }
        c.close();
        return theme;
    }

    public String readFile(File file) {

        FileInputStream fis;
        String result;

        try {
            fis = new FileInputStream(file);
            result = readFile(fis);
            // dispose all the resources after using them.
            fis.close();

        } catch (FileNotFoundException e) {
            Log.e(TAG, "File not found", e);
            Toast.makeText(this, R.string.file_not_found, Toast.LENGTH_SHORT).show();
            return null;
        } catch (IOException e) {
            Log.e(TAG, "File not found", e);
            Toast.makeText(this, R.string.error_reading_file, Toast.LENGTH_SHORT).show();
            return null;
        }

        return result;
    }

    private String readFile(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();

        try {
            Reader in = new InputStreamReader(inputStream, "UTF-8");

            char[] buffer = new char[40960];
            int len;
            while ((len = in.read(buffer)) != -1) {
                sb.append(buffer, 0, len);
            }

        } catch (IOException e) {
            Log.e(TAG, "File not found", e);
            Toast.makeText(this, R.string.error_reading_file, Toast.LENGTH_SHORT).show();
            return null;
        }

        return sb.toString();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (DEBUG) {
            Log.d(TAG, "onResume");
        }

        if (DEBUG) {
            Log.d(TAG, "mDecrypted: " + mDecryptedText);
        }

        // Set auto-link on or off, based on the current setting.
        int autoLink = PreferenceActivity.getAutoLinkFromPreference(this);

        mText.setAutoLinkMask(autoLink);

        mEncrypted = 0;

        if (mState == STATE_EDIT || (mState == STATE_INSERT) || mState == STATE_EDIT_EXTERNAL_NOTE) {
            getNoteFromContentProvider();
        } else if (mState == STATE_EDIT_NOTE_FROM_SDCARD) {
            getNoteFromFile();
        }

        if (mEncrypted == 0 || mDecryptedText != null) {
            applyInsertText();
        }

        // Make sure that we don't use the link movement method.
        // Instead, we need a blend between the arrow key movement (for regular
        // navigation) and
        // the link movement (so the user can click on links).
        mText.setMovementMethod(new ArrowKeyMovementMethod() {
            public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
                // This block is copied and pasted from LinkMovementMethod's
                // onTouchEvent (without the part that actually changes the
                // selection).
                int action = event.getAction();

                if (action == MotionEvent.ACTION_UP) {
                    int x = (int) event.getX();
                    int y = (int) event.getY();

                    x -= widget.getTotalPaddingLeft();
                    y -= widget.getTotalPaddingTop();

                    x += widget.getScrollX();
                    y += widget.getScrollY();

                    Layout layout = widget.getLayout();
                    int line = layout.getLineForVertical(y);
                    int off = layout.getOffsetForHorizontal(line, x);

                    ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

                    if (link.length != 0) {
                        link[0].onClick(widget);
                        return true;
                    }
                }

                return super.onTouchEvent(widget, buffer, event);
            }
        });

        setTheme(loadTheme());

        startupSearch();
    }

    // Handle search command from notes editor menu
    private void menuSearch() {
        LinearLayout searchLayout = (LinearLayout) findViewById(R.id.search);
        searchLayout.setVisibility(View.VISIBLE);
        EditText edt = (EditText) findViewById(R.id.edtSearchWord);
        edt.requestFocus();
    }

    // if editor was invoked from a global search, take over the search word and show the search panel
    private void startupSearch() {
        String searchString = getIntent().getStringExtra("SEARCH_STRING");
        if (searchString != null && !searchString.equals("")) {
            LinearLayout searchLayout = (LinearLayout) findViewById(R.id.search);
            searchLayout.setVisibility(View.VISIBLE);
            EditText edt = (EditText) findViewById(R.id.edtSearchWord);
            edt.setText(searchString);
            mText.setSelection(0, 0);
            searchForward();
        }
    }

    // Initialize the search panel
    private void initSearchPanel() {
        ImageButton mBtnForward = (ImageButton) findViewById(R.id.btnForward);
        mBtnForward.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                searchForward();
            }
        });
        ImageButton mBtnBackward = (ImageButton) findViewById(R.id.btnBackward);
        mBtnBackward.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                searchBackward();
            }
        });
        ImageButton btnClose = (ImageButton) findViewById(R.id.btnClose);
        btnClose.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                LinearLayout searchLayout = (LinearLayout) findViewById(R.id.search);
                searchLayout.setVisibility(View.GONE);
            }
        });
    }

    // Search forward from current selection
    private void searchForward() {
        EditText edt = (EditText) findViewById(R.id.edtSearchWord);
        String search = edt.getText().toString();
        if (search != null && !search.equals("")) {
            String text = mText.getText().toString();
            if (text != null && !text.equals("")) {
                int start = text.toUpperCase().indexOf(search.toUpperCase(), mText.getSelectionEnd());
                if (start != -1) {
                    mText.requestFocus();
                    mText.setSelection(start, start + search.length());
                } else {
                    mText.setSelection(0); // not found, set cursor at beginning of text
                    Toast toast = Toast.makeText(getApplicationContext(), getText(R.string.toast_wrap_bottom),
                            Toast.LENGTH_SHORT);
                    toast.show();
                }
            }
        }
    }

    // Search backwards from current selection
    private void searchBackward() {
        EditText edt = (EditText) findViewById(R.id.edtSearchWord);
        String search = edt.getText().toString();
        if (search != null && !search.equals("")) {
            String text = mText.getText().toString();
            if (text != null && !text.equals("")) {
                int start = text.toUpperCase().lastIndexOf(search.toUpperCase(), mText.getSelectionStart() - 1);
                if (start != -1) {
                    mText.requestFocus();
                    mText.setSelection(start, start + search.length());
                } else {
                    mText.setSelection(text.length()); // not found, set cursor at end of text
                    Toast toast = Toast.makeText(getApplicationContext(), getText(R.string.toast_wrap_top),
                            Toast.LENGTH_SHORT);
                    toast.show();
                }
            }
        }
    }

    private void getNoteFromContentProvider() {
        // If we didn't have any trouble retrieving the data, it is now
        // time to get at the stuff.
        if (mCursor != null && mCursor.requery() && mCursor.moveToFirst()) {

            // Modify our overall title depending on the mode we are running in.
            if (mState == STATE_EDIT || mState == STATE_EDIT_EXTERNAL_NOTE) {
                setTitle(getText(R.string.title_edit));
            } else if (mState == STATE_INSERT) {
                setTitle(getText(R.string.title_create));
            }

            // This always has to be available
            long id = mCursor.getLong(mCursor.getColumnIndex(Notes._ID));
            String note;

            if (mState == STATE_EDIT_EXTERNAL_NOTE) {
                // Check if the other columns are available

                // Note
                if (hasNoteColumn) {
                    note = mCursor.getString(mCursor.getColumnIndex(Notes.NOTE));
                } else {
                    note = "";
                }

                // Encrypted
                mEncrypted = isNoteUnencrypted() ? 0 : 1;

                // Theme
                if (hasThemeColumn) {
                    mTheme = mCursor.getString(mCursor.getColumnIndex(Notes.THEME));
                } else {
                    note = "";
                }

                // Selection start
                if (hasSelection_startColumn) {
                    mSelectionStart = mCursor.getInt(mCursor.getColumnIndex(Notes.SELECTION_START));
                } else {
                    mSelectionStart = 0;
                }

                // Selection end
                if (hasSelection_endColumn) {
                    mSelectionStop = mCursor.getInt(mCursor.getColumnIndex(Notes.SELECTION_END));
                } else {
                    mSelectionStop = 0;
                }
            } else {
                // We know for sure all the columns are available
                note = mCursor.getString(COLUMN_INDEX_NOTE);
                mEncrypted = mCursor.getLong(COLUMN_INDEX_ENCRYPTED);
                mTheme = mCursor.getString(COLUMN_INDEX_THEME);
                mSelectionStart = mCursor.getInt(COLUMN_INDEX_SELECTION_START);
                mSelectionStop = mCursor.getInt(COLUMN_INDEX_SELECTION_END);
            }

            if (mEncrypted == 0) {
                // Not encrypted

                // This is a little tricky: we may be resumed after previously
                // being
                // paused/stopped. We want to put the new text in the text view,
                // but leave the user where they were (retain the cursor
                // position
                // etc). This version of setText does that for us.
                if (!note.equals(mText.getText().toString())) {
                    mText.setTextKeepState(note);
                    // keep state does not work, so we have to do it manually:
                    mText.setSelection(mSelectionStart, mSelectionStop);
                }
            } else {
                if (mDecryptedText != null) {
                    // Text had already been decrypted, use that:
                    if (DEBUG) {
                        Log.d(TAG, "set decrypted text as mText: " + mDecryptedText);
                    }
                    mText.setTextKeepState(mDecryptedText);
                    // keep state does not work, so we have to do it manually:
                    mText.setSelection(mSelectionStart, mSelectionStop);

                    if (!mActionBarAvailable) {
                        setFeatureDrawableResource(Window.FEATURE_RIGHT_ICON, android.R.drawable.ic_lock_idle_lock);
                    }
                } else {
                    // Decrypt note
                    if (DEBUG) {
                        Log.d(TAG, "Decrypt note: " + note);
                    }

                    // Overwrite mText because it may contain unencrypted note
                    // from savedInstanceState.
                    // mText.setText(R.string.encrypted);

                    Intent i = new Intent();
                    i.setAction(CryptoIntents.ACTION_DECRYPT);
                    i.putExtra(CryptoIntents.EXTRA_TEXT, note);
                    i.putExtra(PrivateNotePadIntents.EXTRA_ID, id);

                    try {
                        if (checkCallingOrSelfPermission(
                                CryptoIntents.PERMISSION_SAFE_ACCESS_INTENTS) == PackageManager.PERMISSION_GRANTED) {
                            startActivityForResult(i, REQUEST_CODE_DECRYPT);
                        } else {
                            Toast.makeText(this, R.string.decryption_failed_due_to_permissions, Toast.LENGTH_SHORT)
                                    .show();
                        }
                    } catch (ActivityNotFoundException e) {
                        Toast.makeText(this, R.string.decryption_failed, Toast.LENGTH_SHORT).show();
                        Log.e(TAG, "failed to invoke decrypt");
                    }
                }
            }

            // If we hadn't previously retrieved the original text, do so
            // now. This allows the user to revert their changes.
            if (mOriginalContent == null) {
                mOriginalContent = note;
            }

        } else {
            setTitle(getText(R.string.error_title));
            mText.setText(getText(R.string.error_message));
        }
    }

    private void getNoteFromFile() {
        if (DEBUG) {
            Log.d(TAG, "file: " + mFileContent);
        }
        if (mFileContent == null) {
            // TODO
            mFileContent = "error";
            mReadOnly = true;
        }

        mText.setTextKeepState(mFileContent);

        // keep state does not work, so we have to do it manually:
        try {
            mText.setSelection(mSelectionStart, mSelectionStop);
        } catch (IndexOutOfBoundsException e) {
            // Then let's not adjust the selection.
        }

        // If we hadn't previously retrieved the original text, do so
        // now. This allows the user to revert their changes.
        if (mOriginalContent == null) {
            mOriginalContent = mFileContent;
        }

        updateTitleSdCard();
    }

    private void updateTitleSdCard() {
        String modified = "";
        if (mOriginalContent != null && !mOriginalContent.equals(mFileContent)) {
            modified = "* ";
        }
        String filename = FileUriUtils.getFilename(mUri);
        setTitle(modified + filename);
        // setTitle(getString(R.string.title_edit_file, modified + filename));
        // setFeatureDrawableResource(Window.FEATURE_RIGHT_ICON,
        // android.R.drawable.ic_menu_save);
    }

    //
    // method related to pause
    //

    private void updateCharCount() {
        boolean charCountVisible = false;
        String currentTitle = getTitle().toString();
        if (currentTitle.startsWith("[")) {
            charCountVisible = true;
        }
        if (PreferenceActivity.getCharCountEnabledFromPrefs(this)) {
            if (charCountVisible) {
                setTitle("[" + mText.length() + "]" + currentTitle.substring(currentTitle.indexOf(" ")));
            } else {
                setTitle("[" + mText.length() + "] " + currentTitle);
            }
        } else {
            if (charCountVisible) {
                setTitle(currentTitle.substring(currentTitle.indexOf(" ")));
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (DEBUG) {
            Log.d(TAG, "onSaveInstanceState");
        }
        // if (DEBUG) Log.d(TAG, "file content: " + mFileContent);

        // Save away the original text, so we still have it if the activity
        // needs to be killed while paused.
        mSelectionStart = mText.getSelectionStart();
        mSelectionStop = mText.getSelectionEnd();
        mFileContent = mText.getText().toString();

        if (DEBUG) {
            Log.d(TAG, "Selection " + mSelectionStart + " - " + mSelectionStop + " for text : " + mFileContent);
        }

        outState.putString(BUNDLE_ORIGINAL_CONTENT, mOriginalContent);
        outState.putString(BUNDLE_UNDO_REVERT, mUndoRevert);
        outState.putInt(BUNDLE_STATE, mState);
        if (mUri != null) {
            outState.putString(BUNDLE_URI, mUri.toString());
        }
        outState.putInt(BUNDLE_SELECTION_START, mSelectionStart);
        outState.putInt(BUNDLE_SELECTION_STOP, mSelectionStop);
        outState.putString(BUNDLE_FILE_CONTENT, mFileContent);
        outState.putString(BUNDLE_APPLY_TEXT, mApplyText);
        outState.putString(BUNDLE_APPLY_TEXT_BEFORE, mApplyTextBefore);
        outState.putString(BUNDLE_APPLY_TEXT_AFTER, mApplyTextAfter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (DEBUG) {
            Log.d(TAG, "onPause");
        }

        mText.setAutoLinkMask(0);

        // The user is going somewhere else, so make sure their current
        // changes are safely saved away in the provider. We don't need
        // to do this if only editing.
        if (mCursor != null) {

            mCursor.moveToFirst();

            if (isNoteUnencrypted()) {
                String text = mText.getText().toString();
                int length = text.length();

                // If this activity is finished, and there is no text, then we
                // do something a little special: simply delete the note entry.
                // Note that we do this both for editing and inserting... it
                // would be reasonable to only do it when inserting.
                if (isFinishing() && (length == 0) && !mNoteOnly) {
                    setResult(RESULT_CANCELED);
                    deleteNote();

                    // Get out updates into the provider.
                } else {
                    ContentValues values = new ContentValues();

                    // This stuff is only done when working with a full-fledged
                    // note.
                    if (!mNoteOnly) {
                        String oldText = "";
                        Cursor cursor = getContentResolver().query(mUri, new String[] { "note" }, null, null, null);
                        if (cursor.moveToFirst()) {
                            oldText = cursor.getString(0);
                        }
                        if (!oldText.equals(text)) {
                            // Bump the modification time to now.
                            values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
                        }

                        String title;
                        if (!PreferenceActivity.getMarqueeFromPrefs(this)) {
                            title = ExtractTitle.extractTitle(text);
                        } else {
                            title = text;
                        }
                        values.put(Notes.TITLE, title);
                    }

                    // Write our text back into the provider.
                    if (hasNoteColumn) {
                        values.put(Notes.NOTE, text);
                    }
                    if (hasThemeColumn) {
                        values.put(Notes.THEME, mTheme);
                    }
                    if (hasSelection_startColumn) {
                        values.put(Notes.SELECTION_START, mText.getSelectionStart());
                    }
                    if (hasSelection_endColumn) {
                        values.put(Notes.SELECTION_END, mText.getSelectionEnd());
                    }

                    // Commit all of our changes to persistent storage. When the
                    // update completes
                    // the content provider will notify the cursor of the
                    // change, which will
                    // cause the UI to be updated.
                    getContentResolver().update(mUri, values, null, null);
                }
            } else {
                // encrypted note: First encrypt and store encrypted note:

                // Save current theme:
                ContentValues values = new ContentValues();

                if (hasThemeColumn) {
                    values.put(Notes.THEME, mTheme);
                }

                getContentResolver().update(mUri, values, null, null);

                if (mDecryptedText != null) {
                    // Decrypted had been decrypted.
                    // We take the current version from 'text' and encrypt it.

                    encryptNote(false);

                    // Remove displayed note.
                    // mText.setText(R.string.encrypted);
                }
            }
        }
    }

    /**
     * Encrypt the current note.
     *
     * @param encryptTags
     */
    private void encryptNote(boolean encryptTags) {
        String text = mText.getText().toString();
        String title;
        if (!PreferenceActivity.getMarqueeFromPrefs(this)) {
            title = ExtractTitle.extractTitle(text);
        } else {
            title = text;
        }
        String tags = getTags();
        // Log.i(TAG, "encrypt tags: " + tags);

        boolean isNoteEncrypted = !isNoteUnencrypted();

        if (!encryptTags) {
            tags = null;
        }

        if (DEBUG) {
            Log.d(TAG, "encrypt note: " + text);
        }

        if (EncryptActivity.getPendingEncryptActivities() == 0) {
            Intent i = new Intent(this, EncryptActivity.class);
            i.putExtra(PrivateNotePadIntents.EXTRA_ACTION, CryptoIntents.ACTION_ENCRYPT);
            i.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, EncryptActivity.getCryptoStringArray(text, title, tags));
            i.putExtra(PrivateNotePadIntents.EXTRA_URI, mUri.toString());
            if (text.equals(mOriginalContent) && isNoteEncrypted) {
                // No need to encrypt, content was not modified.
                i.putExtra(PrivateNotePadIntents.EXTRA_CONTENT_UNCHANGED, true);
            }
            startActivity(i);

            // Remove knowledge of the decrypted note.
            // If encryption fails because one has been locked out, (another)
            // user
            // should not be able to see note again from cache.
            if (DEBUG) {
                Log.d(TAG, "using static decrypted text: " + text);
            }
            sDecryptedText = text;
            if (isNoteEncrypted) {
                // Already encrypted
                mDecryptedText = null;
                mText.setText(R.string.encrypted);
            } else {
                // not yet encrypted, but we want to encrypt.
                // Leave mText until note is really encrypted
                // (in case password is not entered or OI Safw not installed)
            }
            EncryptActivity.confirmEncryptActivityCalled();
        } else {
            // encrypt already called
            if (DEBUG) {
                Log.d(TAG, "encrypt already called");
            }

        }

    }

    /**
     * Unencrypt the current note.
     */
    private void unencryptNote() {
        String text = mText.getText().toString();
        String title = ExtractTitle.extractTitle(text);
        String tags = getTags();
        // Log.i(TAG, "unencrypt tags: " + tags);

        ContentValues values = new ContentValues();
        values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
        values.put(Notes.TITLE, title);
        values.put(Notes.NOTE, text);
        values.put(Notes.ENCRYPTED, 0);

        getContentResolver().update(mUri, values, null, null);
        mCursor.requery();

        if (!mActionBarAvailable) {
            setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
        }

        // Small trick: Tags have not been converted properly yet. Let's do it
        // now:
        Intent i = new Intent(this, EncryptActivity.class);
        i.putExtra(PrivateNotePadIntents.EXTRA_ACTION, CryptoIntents.ACTION_DECRYPT);
        i.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, EncryptActivity.getCryptoStringArray(null, null, tags));
        i.putExtra(PrivateNotePadIntents.EXTRA_URI, mUri.toString());
        startActivity(i);
    }

    private boolean isNoteUnencrypted() {
        long encrypted = 0;
        if (mCursor != null && mCursor.moveToFirst()) {
            // Check if the column Notes.ENCRYPTED exists
            if (hasEncryptionColumn) {
                encrypted = mCursor.getInt(mCursor.getColumnIndex(Notes.ENCRYPTED));
            } else {
                encrypted = 0;
            }
        }
        return encrypted == 0;
    }

    private String getTags() {
        String tags;

        // Check if there is a tags column in the database
        int index;
        if ((index = mCursor.getColumnIndex(Notes.TAGS)) != -1) {
            tags = mCursor.getString(index);
        } else {
            tags = "";
        }

        if (!TextUtils.isEmpty(tags)) {
            return tags;
        } else {
            return "";
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);

        // Build the menus that are shown when editing.

        menu.add(5, MENU_SEARCH, 0, R.string.menu_search).setShortcut('3', 'f')
                .setIcon(android.R.drawable.ic_menu_search);

        // if (!mOriginalContent.equals(mText.getText().toString())) {

        menu.add(0, MENU_REVERT, 0, R.string.menu_revert).setShortcut('0', 'r')
                .setIcon(android.R.drawable.ic_menu_revert);

        menu.add(1, MENU_ENCRYPT, 0, R.string.menu_encrypt).setShortcut('1', 'e')
                .setIcon(android.R.drawable.ic_lock_lock); // TODO:
        // better
        // icon

        menu.add(1, MENU_UNENCRYPT, 0, R.string.menu_undo_encryption).setShortcut('1', 'e')
                .setIcon(android.R.drawable.ic_lock_lock); // TODO:
        // better
        // icon

        MenuItem item = menu.add(1, MENU_DELETE, 0, R.string.menu_delete);
        item.setIcon(android.R.drawable.ic_menu_delete);

        menu.add(2, MENU_IMPORT, 0, R.string.menu_import).setShortcut('1', 'i')
                .setIcon(android.R.drawable.ic_menu_add);

        menu.add(2, MENU_SAVE, 0, R.string.menu_save).setShortcut('2', 's')
                .setIcon(android.R.drawable.ic_menu_save);

        menu.add(2, MENU_SAVE_AS, 0, R.string.menu_save_as).setShortcut('3', 'w')
                .setIcon(android.R.drawable.ic_menu_save);

        menu.add(3, MENU_THEME, 0, R.string.menu_theme).setIcon(android.R.drawable.ic_menu_manage).setShortcut('4',
                't');

        menu.add(3, MENU_SETTINGS, 0, R.string.settings).setIcon(android.R.drawable.ic_menu_preferences)
                .setShortcut('9', 'p');

        item = menu.add(4, MENU_SEND, 0, R.string.menu_share);
        item.setIcon(android.R.drawable.ic_menu_share);
        if (mActionBarAvailable) {
            WrapActionBar.showIfRoom(item);
        }

        menu.add(5, MENU_WORD_COUNT, 0, R.string.menu_word_count);

        /*
           * if (mState == STATE_EDIT) {
         *
         * menu.add(0, REVERT_ID, 0, R.string.menu_revert) .setShortcut('0',
         * 'r') .setIcon(android.R.drawable.ic_menu_revert);
         *
         * if (!mNoteOnly) { menu.add(1, DELETE_ID, 0, R.string.menu_delete)
         * .setShortcut('1', 'd') .setIcon(android.R.drawable.ic_menu_delete); }
         *
         * // Build the menus that are shown when inserting. } else {
         * menu.add(1, DISCARD_ID, 0, R.string.menu_discard) .setShortcut('0',
         * 'd') .setIcon(android.R.drawable.ic_menu_delete); }
         */

        // If we are working on a full note, then append to the
        // menu items for any other activities that can do stuff with it
        // as well. This does a query on the system for any activities that
        // implement the ALTERNATIVE_ACTION for our data, adding a menu item
        // for each one that is found.
        if (!mNoteOnly) {
            // We use mUri instead of getIntent().getData() in the
            // following line, because mUri may have changed when inserting
            // a new note.
            Intent intent = new Intent(null, mUri);
            intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
            // menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
            // new ComponentName(this, NoteEditor.class), null, intent, 0,
            // null);

            // Workaround to add icons:
            MenuIntentOptionsWithIcons menu2 = new MenuIntentOptionsWithIcons(this, menu);
            menu2.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, new ComponentName(this, NoteEditor.class), null,
                    intent, 0, null);

            // Add menu items for category CATEGORY_TEXT_SELECTION_ALTERNATIVE
            intent = new Intent(); // Don't pass data for this intent
            intent.addCategory(NotepadIntents.CATEGORY_TEXT_SELECTION_ALTERNATIVE);
            intent.setType("text/plain");
            // Workaround to add icons:
            menu2.addIntentOptions(GROUP_ID_TEXT_SELECTION_ALTERNATIVE, 0, 0,
                    new ComponentName(this, NoteEditor.class), null, intent, 0, null);

        }

        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {

        // Show "revert" menu item only if content has changed and we have a cursor (see revertNote())

        // contentChanged used for revert and save menu
        boolean contentChanged = !mOriginalContent.equals(mText.getText().toString());

        boolean isNoteUnencrypted = isNoteUnencrypted();

        // Show comands on the URI only if the note is not encrypted
        menu.setGroupVisible(Menu.CATEGORY_ALTERNATIVE, isNoteUnencrypted);

        if (mState == STATE_EDIT_NOTE_FROM_SDCARD) {
            // Menus for editing from SD card
            menu.setGroupVisible(0, false);
            menu.setGroupVisible(1, false);
            menu.setGroupVisible(2, true);

            menu.findItem(MENU_SAVE).setEnabled(contentChanged);

        } else if (mState == STATE_EDIT_EXTERNAL_NOTE) {
            // Menus for external notes, e.g. from OI Shopping List.
            // In this case, don't show encryption/decryption.
            menu.setGroupVisible(0, contentChanged || mUndoRevert != null);
            menu.setGroupVisible(1, true);
            menu.setGroupVisible(2, false);

            menu.findItem(MENU_ENCRYPT).setVisible(false);
            menu.findItem(MENU_UNENCRYPT).setVisible(false);
        } else {
            // Menus for internal notes
            menu.setGroupVisible(0, contentChanged || mUndoRevert != null);
            menu.setGroupVisible(1, true);
            menu.setGroupVisible(2, false);

            menu.findItem(MENU_ENCRYPT).setVisible(isNoteUnencrypted);
            menu.findItem(MENU_UNENCRYPT).setVisible(!isNoteUnencrypted);
        }

        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle all of the possible menu actions.
        switch (item.getItemId()) {
        case android.R.id.home:
            Intent intent = new Intent(this, NotesList.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
            break;
        case MENU_SEARCH:
            menuSearch();
            break;
        case MENU_DELETE:
            deleteNoteWithConfirm();
            break;
        case MENU_DISCARD:
            revertNote();
            break;
        case MENU_REVERT:
            revertNote();
            break;
        case MENU_ENCRYPT:
            encryptNote(true);
            break;
        case MENU_UNENCRYPT:
            unencryptNote();
            break;
        case MENU_IMPORT:
            importNote();
            break;
        case MENU_SAVE:
            saveNoteWithPermissionCheck();
            break;
        case MENU_SAVE_AS:
            saveAsNote();
            break;
        case MENU_THEME:
            setThemeSettings();
            return true;
        case MENU_SETTINGS:
            showNotesListSettings();
            return true;
        case MENU_SEND:
            shareNote();
            return true;
        case MENU_WORD_COUNT:
            showWordCount();
            break;
        }
        if (item.getGroupId() == GROUP_ID_TEXT_SELECTION_ALTERNATIVE) {
            // Process manually:
            // We pass the current selection along with the intent
            startTextSelectionActivity(item.getIntent());

            // Consume event
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void shareNote() {
        String content = mText.getText().toString();
        String title = ExtractTitle.extractTitle(content);
        SendNote.sendNote(this, title, content);
    }

    private void deleteNoteWithConfirm() {
        showDialog(DIALOG_DELETE);
    }

    /**
     * Modifies an activity to pass along the currently selected text.
     *
     * @param intent
     */
    private void startTextSelectionActivity(Intent intent) {
        Intent newIntent = new Intent(intent);

        String text = mText.getText().toString();
        int start = mText.getSelectionStart();
        int end = mText.getSelectionEnd();

        // if (DEBUG) Log.i(TAG, "len: " + text.length() + ", start: " + start +
        // ", end: " + end);
        if (end < start) {
            int swap = end;
            end = start;
            start = swap;
        }

        newIntent.putExtra(NotepadIntents.EXTRA_TEXT, text.substring(start, end));
        newIntent.putExtra(NotepadIntents.EXTRA_TEXT_BEFORE_SELECTION, text.substring(0, start));
        newIntent.putExtra(NotepadIntents.EXTRA_TEXT_AFTER_SELECTION, text.substring(end));

        startActivityForResult(newIntent, REQUEST_CODE_TEXT_SELECTION_ALTERNATIVE);
    }

    /**
     * Reverts to the original text, or undoes revert.
     */
    private final void revertNote() {
        if (mCursor != null) {
            String tmp = mText.getText().toString();
            mText.setAutoLinkMask(0);
            if (!tmp.equals(mOriginalContent)) {
                // revert to original content
                mText.setTextKeepState(mOriginalContent);
                mUndoRevert = tmp;
            } else if (mUndoRevert != null) {
                // revert to original content
                mText.setTextKeepState(mUndoRevert);
                mUndoRevert = null;
            }
            int autolink = PreferenceActivity.getAutoLinkFromPreference(this);
            mText.setAutoLinkMask(autolink);
        }
        // mCursor.requery();
        // setResult(RESULT_CANCELED);
        // finish();
    }

    /**
     * Take care of deleting a note. Simply deletes the entry.
     */
    private final void deleteNote() {
        if (mCursor != null) {
            mCursor.close();
            mCursor = null;
            getContentResolver().delete(mUri, null, null);
            mText.setText("");
        }
    }

    /*
     * private final void discardNote() { //if (mCursor != null) { //
     * mCursor.close(); // mCursor = null; // getContentResolver().delete(mUri,
     * null, null); // mText.setText(""); //} mOriginalContent =
     * mText.getText().toString(); mText.setText(""); }
     */

    private void applyInsertText() {
        if (mApplyTextBefore != null || mApplyText != null || mApplyTextAfter != null) {
            // Need to apply insert text from previous
            // TEXT_SELECTION_ALTERNATIVE

            insertAtPoint(mApplyTextBefore, mApplyText, mApplyTextAfter);

            // Only apply once:
            mApplyTextBefore = null;
            mApplyText = null;
            mApplyTextAfter = null;
        }
    }

    /**
     * Insert textToInsert at current position. Optionally, if textBefore or
     * textAfter are non-null, replace the text before or after the current
     * selection.
     *
     * @author isaac
     * @author Peli
     */
    private void insertAtPoint(String textBefore, String textToInsert, String textAfter) {
        String originalText = mText.getText().toString();
        int startPos = mText.getSelectionStart();
        int endPos = mText.getSelectionEnd();
        if (mDecryptedText != null) {
            // Treat encrypted text:
            originalText = mDecryptedText;
            startPos = mSelectionStart;
            endPos = mSelectionStop;
        }
        int newStartPos = startPos;
        int newEndPos;
        ContentValues values = new ContentValues();

        String newNote;
        StringBuilder sb = new StringBuilder();
        if (textBefore != null) {
            sb.append(textBefore);
            newStartPos = textBefore.length();
        } else {
            sb.append(originalText.substring(0, startPos));
        }
        if (textToInsert != null) {
            sb.append(textToInsert);
            newEndPos = newStartPos + textToInsert.length();
        } else {
            String text = originalText.substring(startPos, endPos);
            sb.append(text);
            newEndPos = newStartPos + text.length();
        }
        if (textAfter != null) {
            sb.append(textAfter);
        } else {
            sb.append(originalText.substring(endPos));
        }
        newNote = sb.toString();

        if (mState == STATE_EDIT_NOTE_FROM_SDCARD) {
            mFileContent = newNote;
            mSelectionStart = newStartPos;
            mSelectionStop = newEndPos;
        } else if (mDecryptedText != null) {
            mDecryptedText = newNote;
        } else {
            // This stuff is only done when working with a full-fledged note.
            if (!mNoteOnly) {
                // Bump the modification time to now.
                values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
                String title;
                if (!PreferenceActivity.getMarqueeFromPrefs(this)) {
                    title = ExtractTitle.extractTitle(newNote);
                } else {
                    title = newNote;
                }
                values.put(Notes.TITLE, title);
            }
            // Write our text back into the provider.
            values.put(Notes.NOTE, newNote);
            // Commit all of our changes to persistent storage. When the update
            // completes
            // the content provider will notify the cursor of the change, which
            // will
            // cause the UI to be updated.
            getContentResolver().update(mUri, values, null, null);
        }

        // ijones: notification doesn't seem to trigger for some reason :(
        mText.setTextKeepState(newNote);
        // Adjust cursor position according to new length:
        mText.setSelection(newStartPos, newEndPos);
    }

    private void importNote() {
        // Load the file into a new note.

        mFileContent = mText.getText().toString();

        Uri newUri;

        // Let's check whether the exactly same note already exists or not:
        Cursor c = getContentResolver().query(Notes.CONTENT_URI, new String[] { Notes._ID }, Notes.NOTE + " = ?",
                new String[] { mFileContent }, null);
        if (c != null && c.moveToFirst()) {
            // Same note exists already:
            long id = c.getLong(0);
            newUri = ContentUris.withAppendedId(Notes.CONTENT_URI, id);
        } else {

            // Add new note
            // Requested to insert: set that state, and create a new entry
            // in the container.
            // mState = STATE_INSERT;
            ContentValues values = new ContentValues();
            values.put(Notes.NOTE, mFileContent);
            values.put(Notes.THEME, mTheme);
            newUri = getContentResolver().insert(Notes.CONTENT_URI, values);

            // If we were unable to create a new note, then just finish
            // this activity. A RESULT_CANCELED will be sent back to the
            // original activity if they requested a result.
            if (newUri == null) {
                Log.e(TAG, "Failed to insert new note.");
                finish();
                return;
            }

            // The new entry was created, so assume all will end well and
            // set the result to be returned.
            // setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
            // setResult(RESULT_OK, intent);
        }

        // Start a new editor:
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_EDIT);
        intent.setData(newUri);
        intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        setIntent(intent);
        startActivity(intent);

        // and finish this editor
        finish();

    }

    private void saveNoteWithPermissionCheck() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            saveNote();
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.READ_EXTERNAL_STORAGE)) {

            } else {
                ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
                        REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE);
            }
        }
    }

    private void saveNote() {
        mFileContent = mText.getText().toString();
        File file = FileUriUtils.getFile(mUri);
        SaveFileActivity.writeToFile(this, file, mFileContent);

        mOriginalContent = mFileContent;
    }

    /**
     * Show the "Save as" dialog.
     */
    private void saveAsNote() {
        mFileContent = mText.getText().toString();

        Intent intent = new Intent();
        intent.setAction(NotepadInternalIntents.ACTION_SAVE_TO_SD_CARD);
        if (mUri != null) {
            intent.setData(mUri);
        }
        intent.putExtra(NotepadInternalIntents.EXTRA_TEXT, mFileContent);

        startActivityForResult(intent, REQUEST_CODE_SAVE_AS);
    }

    void setThemeSettings() {
        showDialog(DIALOG_THEME);
    }

    @Override
    protected Dialog onCreateDialog(int id) {

        switch (id) {
        case DIALOG_UNSAVED_CHANGES:
            return getUnsavedChangesWarningDialog();

        case DIALOG_THEME:
            return new ThemeDialog(this, this);

        case DIALOG_DELETE:
            return new DeleteConfirmationDialog(this, new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface arg0, int arg1) {
                    deleteNote();
                    finish();
                }
            }).create();
        }
        return null;
    }

    public String onLoadTheme() {
        return loadTheme();
    }

    public void onSaveTheme(String theme) {
        saveTheme(theme);
    }

    public void onSetTheme(String theme) {
        setTheme(theme);
    }

    public void onSetThemeForAll(String theme) {
        setThemeForAll(this, theme);
    }

    /**
     * Loads the theme settings for the currently selected theme.
     * <p/>
     * Up to version 1.2.1, only one of 3 hardcoded themes are available. These
     * are stored in 'skin_background' as '1', '2', or '3'.
     * <p/>
     * Starting in 1.2.2, also themes of other packages are allowed.
     *
     * @return
     */
    public String loadTheme() {
        return mTheme;
        /*
         * if (mCursor != null && mCursor.moveToFirst()) { // mCursorListFilter
         * has been set to correct position // by calling getSelectedListId(),
         * // so we can read out further elements: String skinBackground =
         * mCursor .getString(COLUMN_INDEX_THEME);
         *
         * return skinBackground; } else { return null; }
         */
    }

    public void saveTheme(String theme) {
        mTheme = theme;
        /*
         * // Save theme only for content Uris with NotePad authority. // Don't
         * save anything for file:// uri. if (mUri != null &&
         * mUri.getAuthority().equals(NotePad.AUTHORITY)) { ContentValues values
         * = new ContentValues(); values.put(Notes.THEME, theme);
         * getContentResolver().update(mUri, values, null, null); }
         */
    }

    /**
     * Set theme according to Id.
     *
     * @param themeName
     */
    void setTheme(String themeName) {
        int size = PreferenceActivity.getFontSizeFromPrefs(this);

        // New styles:
        boolean themeFound = setRemoteStyle(themeName, size);

        if (!themeFound) {
            // Some error occured, let's use default style:
            setLocalStyle(R.style.Theme_Notepad, size);
        }

        applyTheme();
    }

    private void setLocalStyle(int styleResId, int size) {
        String styleName = getResources().getResourceName(styleResId);

        boolean themefound = setRemoteStyle(styleName, size);

        if (!themefound) {
            // Actually this should never happen.
            Log.e(TAG, "Local theme not found: " + styleName);
        }
    }

    private boolean setRemoteStyle(String styleName, int size) {
        if (TextUtils.isEmpty(styleName)) {
            if (DEBUG) {
                Log.e(TAG, "Empty style name: " + styleName);
            }
            return false;
        }

        PackageManager pm = getPackageManager();

        String packageName = ThemeUtils.getPackageNameFromStyle(styleName);

        if (packageName == null) {
            Log.e(TAG, "Invalid style name: " + styleName);
            return false;
        }

        Context c = null;
        try {
            c = createPackageContext(packageName, 0);
        } catch (NameNotFoundException e) {
            Log.e(TAG, "Package for style not found: " + packageName + ", " + styleName);
            return false;
        }

        Resources res = c.getResources();

        int themeid = res.getIdentifier(styleName, null, null);
        if (DEBUG) {
            Log.d(TAG, "Retrieving theme: " + styleName + ", " + themeid);
        }

        if (themeid == 0) {
            Log.e(TAG, "Theme name not found: " + styleName);
            return false;
        }

        try {
            ThemeAttributes ta = new ThemeAttributes(c, packageName, themeid);

            mTextTypeface = ta.getString(ThemeNotepad.TEXT_TYPEFACE);
            if (DEBUG) {
                Log.d(TAG, "textTypeface: " + mTextTypeface);
            }

            mCurrentTypeface = null;

            // Look for special cases:
            if ("monospace".equals(mTextTypeface)) {
                mCurrentTypeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL);
            } else if ("sans".equals(mTextTypeface)) {
                mCurrentTypeface = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
            } else if ("serif".equals(mTextTypeface)) {
                mCurrentTypeface = Typeface.create(Typeface.SERIF, Typeface.NORMAL);
            } else if (!TextUtils.isEmpty(mTextTypeface)) {

                try {
                    if (DEBUG) {
                        Log.d(TAG, "Reading typeface: package: " + packageName + ", typeface: " + mTextTypeface);
                    }
                    Resources remoteRes = pm.getResourcesForApplication(packageName);
                    mCurrentTypeface = Typeface.createFromAsset(remoteRes.getAssets(), mTextTypeface);
                    if (DEBUG) {
                        Log.d(TAG, "Result: " + mCurrentTypeface);
                    }
                } catch (NameNotFoundException e) {
                    Log.e(TAG, "Package not found for Typeface", e);
                }
            }

            mTextUpperCaseFont = ta.getBoolean(ThemeNotepad.TEXT_UPPER_CASE_FONT, false);

            mTextColor = ta.getColor(ThemeNotepad.TEXT_COLOR, android.R.color.white);

            if (DEBUG) {
                Log.d(TAG, "textColor: " + mTextColor);
            }

            if (size == 0) {
                mTextSize = getTextSizeTiny(ta);
            } else if (size == 1) {
                mTextSize = getTextSizeSmall(ta);
            } else if (size == 2) {
                mTextSize = getTextSizeMedium(ta);
            } else {
                mTextSize = getTextSizeLarge(ta);
            }
            if (DEBUG) {
                Log.d(TAG, "textSize: " + mTextSize);
            }

            if (mText != null) {
                mBackgroundPadding = ta.getDimensionPixelOffset(ThemeNotepad.BACKGROUND_PADDING, -1);
                int backgroundPaddingLeft = ta.getDimensionPixelOffset(ThemeNotepad.BACKGROUND_PADDING_LEFT,
                        mBackgroundPadding);
                int backgroundPaddingTop = ta.getDimensionPixelOffset(ThemeNotepad.BACKGROUND_PADDING_TOP,
                        mBackgroundPadding);
                int backgroundPaddingRight = ta.getDimensionPixelOffset(ThemeNotepad.BACKGROUND_PADDING_RIGHT,
                        mBackgroundPadding);
                int backgroundPaddingBottom = ta.getDimensionPixelOffset(ThemeNotepad.BACKGROUND_PADDING_BOTTOM,
                        mBackgroundPadding);

                if (DEBUG) {
                    Log.d(TAG,
                            "Padding: " + mBackgroundPadding + "; " + backgroundPaddingLeft + "; "
                                    + backgroundPaddingTop + "; " + backgroundPaddingRight + "; "
                                    + backgroundPaddingBottom + "; ");
                }

                try {
                    Resources remoteRes = pm.getResourcesForApplication(packageName);
                    int resid = ta.getResourceId(ThemeNotepad.BACKGROUND, 0);
                    if (resid != 0) {
                        Drawable d = remoteRes.getDrawable(resid);
                        mText.setBackgroundDrawable(d);
                    } else {
                        // remove background
                        mText.setBackgroundResource(0);
                    }
                } catch (NameNotFoundException e) {
                    Log.e(TAG, "Package not found for Theme background.", e);
                } catch (Resources.NotFoundException e) {
                    Log.e(TAG, "Resource not found for Theme background.", e);
                }

                // Apply padding
                if (mBackgroundPadding >= 0 || backgroundPaddingLeft >= 0 || backgroundPaddingTop >= 0
                        || backgroundPaddingRight >= 0 || backgroundPaddingBottom >= 0) {
                    mText.setPadding(backgroundPaddingLeft, backgroundPaddingTop, backgroundPaddingRight,
                            backgroundPaddingBottom);
                } else {
                    // 9-patches do the padding automatically
                    // todo clear padding
                }
            }

            mLinesMode = ta.getInteger(ThemeNotepad.LINE_MODE, 2);
            mLinesColor = ta.getColor(ThemeNotepad.LINE_COLOR, 0xFF000080);

            if (DEBUG) {
                Log.d(TAG, "line color: " + mLinesColor);
            }

            return true;

        } catch (UnsupportedOperationException e) {
            // This exception is thrown e.g. if one attempts
            // to read an integer attribute as dimension.
            Log.e(TAG, "UnsupportedOperationException", e);
            return false;
        } catch (NumberFormatException e) {
            // This exception is thrown e.g. if one attempts
            // to read a string as integer.
            Log.e(TAG, "NumberFormatException", e);
            return false;
        }
    }

    private float getTextSizeTiny(ThemeAttributes ta) {
        float size = ta.getDimensionPixelOffset(ThemeNotepad.TEXT_SIZE_TINY, -1);
        if (size == -1) {
            // Try to obtain from small:
            size = (12f / 18f) * getTextSizeSmall(ta);
        }
        return size;
    }

    private float getTextSizeSmall(ThemeAttributes ta) {
        float size = ta.getDimensionPixelOffset(ThemeNotepad.TEXT_SIZE_SMALL, -1);
        if (size == -1) {
            // Try to obtain from small:
            size = (18f / 23f) * getTextSizeMedium(ta);
        }
        return size;
    }

    private float getTextSizeMedium(ThemeAttributes ta) {
        final float scale = getResources().getDisplayMetrics().scaledDensity;
        return ta.getDimensionPixelOffset(ThemeNotepad.TEXT_SIZE_MEDIUM, (int) (23 * scale + 0.5f));
    }

    private float getTextSizeLarge(ThemeAttributes ta) {
        float size = ta.getDimensionPixelOffset(ThemeNotepad.TEXT_SIZE_LARGE, -1);
        if (size == -1) {
            // Try to obtain from small:
            size = (28f / 23f) * getTextSizeMedium(ta);
        }
        return size;
    }

    private void applyTheme() {
        mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
        mText.setTypeface(mCurrentTypeface);
        mText.setTextColor(mTextColor);

        if (mTextUpperCaseFont) {
            // Turn off autolinkmask, because it is not compatible with
            // transformationmethod.
            mText.setAutoLinkMask(0);

            mText.setTransformationMethod(UpperCaseTransformationMethod.getInstance());
        } else {
            mText.setTransformationMethod(null);

            // Set auto-link on or off, based on the current setting.
            int autoLink = PreferenceActivity.getAutoLinkFromPreference(this);

            mText.setAutoLinkMask(autoLink);
        }

        mText.invalidate();
    }

    private void showNotesListSettings() {
        startActivity(new Intent(this, PreferenceActivity.class));
    }

    private void showWordCount() {
        String text = mText.getText().toString();
        int number_of_words = text.split("\\s+").length;
        if (TextUtils.isEmpty(text)) {
            // if text is empty, number_of_words is set to 1,
            // so in this case we set it manually
            number_of_words = 0;
        }
        AlertDialog.Builder wordCountAlert = new AlertDialog.Builder(this);
        wordCountAlert.setMessage(
                getResources().getQuantityString(R.plurals.word_count, number_of_words, number_of_words));
        wordCountAlert.setTitle(R.string.menu_word_count);
        wordCountAlert.setPositiveButton(R.string.ok, null);
        wordCountAlert.setCancelable(false);
        wordCountAlert.create().show();
    }

    Dialog getUnsavedChangesWarningDialog() {
        return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(R.string.warning_unsaved_changes_title)
                .setMessage(R.string.warning_unsaved_changes_message)
                .setPositiveButton(R.string.button_save, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        saveNoteWithPermissionCheck();
                        finish();
                    }
                }).setNeutralButton(R.string.button_dont_save, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // Don't save
                        finish();
                    }
                }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        // Cancel
                    }
                }).create();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (mState == STATE_EDIT_NOTE_FROM_SDCARD) {
                mFileContent = mText.getText().toString();
                if (!mFileContent.equals(mOriginalContent)) {
                    // Show a dialog
                    showDialog(DIALOG_UNSAVED_CHANGES);
                    return true;
                }
            }
        }

        return super.onKeyDown(keyCode, event);
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (DEBUG) {
            Log.d(TAG, "onActivityResult: Received requestCode " + requestCode + ", resultCode " + resultCode);
        }
        switch (requestCode) {
        case REQUEST_CODE_DECRYPT:
            if (resultCode == RESULT_OK && data != null) {
                String decryptedText = data.getStringExtra(CryptoIntents.EXTRA_TEXT);
                long id = data.getLongExtra(PrivateNotePadIntents.EXTRA_ID, -1);

                // TODO: Check that id corresponds to current intent.

                if (id == -1) {
                    Log.e(TAG, "Wrong extra id");
                    Toast.makeText(this, "Decrypted information incomplete", Toast.LENGTH_SHORT).show();

                    finish();
                    return;
                }

                if (DEBUG) {
                    Log.d(TAG, "decrypted text received: " + decryptedText);
                }
                mDecryptedText = decryptedText;
                mOriginalContent = decryptedText;

            } else {
                Toast.makeText(this, R.string.decryption_failed, Toast.LENGTH_SHORT).show();
                Log.e(TAG, "decryption failed");

                finish();
            }
            break;
        case REQUEST_CODE_TEXT_SELECTION_ALTERNATIVE:
            if (resultCode == RESULT_OK && data != null) {
                // Insert result at current cursor position:
                mApplyText = data.getStringExtra(NotepadIntents.EXTRA_TEXT);
                mApplyTextBefore = data.getStringExtra(NotepadIntents.EXTRA_TEXT_BEFORE_SELECTION);
                mApplyTextAfter = data.getStringExtra(NotepadIntents.EXTRA_TEXT_AFTER_SELECTION);

                // Text is actually inserted in onResume() - see
                // applyInsertText()
            }
            break;
        case REQUEST_CODE_SAVE_AS:
            if (resultCode == RESULT_OK && data != null) {
                // Set the new file name
                mUri = data.getData();
                if (DEBUG) {
                    Log.d(TAG, "original: " + mOriginalContent + ", file: " + mFileContent);
                }
                mOriginalContent = mFileContent;

                updateTitleSdCard();
            }
            break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
        case REQUEST_CODE_PERMISSION_READ_EXTERNAL_STORAGE: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                mFileContent = readFile(FileUriUtils.getFile(mUri));
                mOriginalContent = mFileContent;
            } else {
                mFileContent = null;
            }
            getNoteFromFile();
            return;
        }

        case REQUEST_CODE_PERMISSION_WRITE_EXTERNAL_STORAGE: {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                saveNote();
            }
            return;
        }

        }
    }

    /**
     * A custom EditText that draws lines between each line of text that is
     * displayed.
     */
    public static class LinedEditText extends EditText {
        private Rect mRect;
        private Paint mPaint;

        // we need this constructor for LayoutInflater
        public LinedEditText(Context context, AttributeSet attrs) {
            super(context, attrs);

            mRect = new Rect();
            mPaint = new Paint();
            mPaint.setStyle(Paint.Style.STROKE);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            boolean fullWidth = (mLinesMode & 1) == 1;
            boolean textlines = (mLinesMode & 2) == 2;
            boolean pagelines = (mLinesMode & 4) == 4;
            if (textlines || pagelines) {
                mPaint.setColor(mLinesColor);

                int count = getLineCount();
                Rect r = mRect;
                Paint paint = mPaint;

                int height = getHeight();
                int line_height = getLineHeight();
                int page_size = height / line_height + 1;

                int baseline = 0;
                int left = 0;
                int right = 0;
                for (int i = 0; i < count; i++) {
                    baseline = getLineBounds(i, r);
                    left = r.left;
                    right = r.right;
                    if (fullWidth) {
                        left = getLeft();
                        right = getRight();
                    }
                    canvas.drawLine(left, baseline + 1, right, baseline + 1, paint);
                }
                if (pagelines) {
                    // Fill the rest of the page with lines
                    for (int i = count; i < page_size; i++) {
                        baseline += line_height;
                        canvas.drawLine(left, baseline + 1, right, baseline + 1, paint);
                    }
                }
            }

            super.onDraw(canvas);
        }
    }
}