• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.example.android.notepad;
18 
19 import android.app.Activity;
20 import android.content.ComponentName;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.database.Cursor;
26 import android.graphics.Canvas;
27 import android.graphics.Paint;
28 import android.graphics.Rect;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.widget.EditText;
37 import android.widget.Toast;
38 
39 import com.example.android.notepad.NotePad.NoteColumns;
40 
41 /**
42  * A generic activity for editing a note in a database.  This can be used
43  * either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
44  * {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
45  */
46 public class NoteEditor extends Activity {
47     private static final String TAG = "NoteEditor";
48 
49     /**
50      * Standard projection for the interesting columns of a normal note.
51      */
52     private static final String[] PROJECTION = new String[] {
53         NoteColumns._ID, // 0
54         NoteColumns.NOTE, // 1
55         NoteColumns.TITLE, // 2
56     };
57     /** The index of the note column */
58     private static final int COLUMN_INDEX_NOTE = 1;
59     /** The index of the title column */
60     private static final int COLUMN_INDEX_TITLE = 2;
61 
62     // This is our state data that is stored when freezing.
63     private static final String ORIGINAL_CONTENT = "origContent";
64 
65     // The different distinct states the activity can be run in.
66     private static final int STATE_EDIT = 0;
67     private static final int STATE_INSERT = 1;
68 
69     private int mState;
70     private Uri mUri;
71     private Cursor mCursor;
72     private EditText mText;
73     private String mOriginalContent;
74 
75     /**
76      * A custom EditText that draws lines between each line of text that is displayed.
77      */
78     public static class LinedEditText extends EditText {
79         private Rect mRect;
80         private Paint mPaint;
81 
82         // we need this constructor for LayoutInflater
LinedEditText(Context context, AttributeSet attrs)83         public LinedEditText(Context context, AttributeSet attrs) {
84             super(context, attrs);
85 
86             mRect = new Rect();
87             mPaint = new Paint();
88             mPaint.setStyle(Paint.Style.STROKE);
89             mPaint.setColor(0x800000FF);
90         }
91 
92         @Override
onDraw(Canvas canvas)93         protected void onDraw(Canvas canvas) {
94             int count = getLineCount();
95             Rect r = mRect;
96             Paint paint = mPaint;
97 
98             for (int i = 0; i < count; i++) {
99                 int baseline = getLineBounds(i, r);
100 
101                 canvas.drawLine(r.left, baseline + 1, r.right, baseline + 1, paint);
102             }
103 
104             super.onDraw(canvas);
105         }
106     }
107 
108     @Override
onCreate(Bundle savedInstanceState)109     protected void onCreate(Bundle savedInstanceState) {
110         super.onCreate(savedInstanceState);
111 
112         final Intent intent = getIntent();
113 
114         // Do some setup based on the action being performed.
115         final String action = intent.getAction();
116         if (Intent.ACTION_EDIT.equals(action)) {
117             // Requested to edit: set that state, and the data being edited.
118             mState = STATE_EDIT;
119             mUri = intent.getData();
120         } else if (Intent.ACTION_INSERT.equals(action)) {
121             // Requested to insert: set that state, and create a new entry
122             // in the container.
123             mState = STATE_INSERT;
124             mUri = getContentResolver().insert(intent.getData(), null);
125 
126             // If we were unable to create a new note, then just finish
127             // this activity.  A RESULT_CANCELED will be sent back to the
128             // original activity if they requested a result.
129             if (mUri == null) {
130                 Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
131                 finish();
132                 return;
133             }
134 
135             // The new entry was created, so assume all will end well and
136             // set the result to be returned.
137             setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
138 
139         } else {
140             // Whoops, unknown action!  Bail.
141             Log.e(TAG, "Unknown action, exiting");
142             finish();
143             return;
144         }
145 
146         // Set the layout for this activity.  You can find it in res/layout/note_editor.xml
147         setContentView(R.layout.note_editor);
148 
149         // The text view for our note, identified by its ID in the XML file.
150         mText = (EditText) findViewById(R.id.note);
151 
152         // Get the note!
153         mCursor = managedQuery(mUri, PROJECTION, null, null, null);
154 
155         // If an instance of this activity had previously stopped, we can
156         // get the original text it started with.
157         if (savedInstanceState != null) {
158             mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
159         }
160     }
161 
162     @Override
onResume()163     protected void onResume() {
164         super.onResume();
165         // If we didn't have any trouble retrieving the data, it is now
166         // time to get at the stuff.
167         if (mCursor != null) {
168             // Requery in case something changed while paused (such as the title)
169             mCursor.requery();
170             // Make sure we are at the one and only row in the cursor.
171             mCursor.moveToFirst();
172 
173             // Modify our overall title depending on the mode we are running in.
174             if (mState == STATE_EDIT) {
175                 // Set the title of the Activity to include the note title
176                 String title = mCursor.getString(COLUMN_INDEX_TITLE);
177                 Resources res = getResources();
178                 String text = String.format(res.getString(R.string.title_edit), title);
179                 setTitle(text);
180             } else if (mState == STATE_INSERT) {
181                 setTitle(getText(R.string.title_create));
182             }
183 
184             // This is a little tricky: we may be resumed after previously being
185             // paused/stopped.  We want to put the new text in the text view,
186             // but leave the user where they were (retain the cursor position
187             // etc).  This version of setText does that for us.
188             String note = mCursor.getString(COLUMN_INDEX_NOTE);
189             mText.setTextKeepState(note);
190 
191             // If we hadn't previously retrieved the original text, do so
192             // now.  This allows the user to revert their changes.
193             if (mOriginalContent == null) {
194                 mOriginalContent = note;
195             }
196 
197         } else {
198             setTitle(getText(R.string.error_title));
199             mText.setText(getText(R.string.error_message));
200         }
201     }
202 
203     @Override
onSaveInstanceState(Bundle outState)204     protected void onSaveInstanceState(Bundle outState) {
205         // Save away the original text, so we still have it if the activity
206         // needs to be killed while paused.
207         outState.putString(ORIGINAL_CONTENT, mOriginalContent);
208     }
209 
210     @Override
onPause()211     protected void onPause() {
212         super.onPause();
213         // The user is going somewhere, so make sure changes are saved
214 
215         String text = mText.getText().toString();
216         int length = text.length();
217 
218         // If this activity is finished, and there is no text, then we
219         // simply delete the note entry.
220         // Note that we do this both for editing and inserting...  it
221         // would be reasonable to only do it when inserting.
222         if (isFinishing() && (length == 0) && mCursor != null) {
223             setResult(RESULT_CANCELED);
224             deleteNote();
225         } else {
226             saveNote();
227         }
228     }
229 
230     @Override
onCreateOptionsMenu(Menu menu)231     public boolean onCreateOptionsMenu(Menu menu) {
232         // Inflate menu from XML resource
233         MenuInflater inflater = getMenuInflater();
234         inflater.inflate(R.menu.editor_options_menu, menu);
235 
236         // Append to the
237         // menu items for any other activities that can do stuff with it
238         // as well.  This does a query on the system for any activities that
239         // implement the ALTERNATIVE_ACTION for our data, adding a menu item
240         // for each one that is found.
241         Intent intent = new Intent(null, getIntent().getData());
242         intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
243         menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
244                 new ComponentName(this, NoteEditor.class), null, intent, 0, null);
245 
246         return super.onCreateOptionsMenu(menu);
247     }
248 
249 
250 
251     @Override
onPrepareOptionsMenu(Menu menu)252     public boolean onPrepareOptionsMenu(Menu menu) {
253         if (mState == STATE_EDIT) {
254             menu.setGroupVisible(R.id.menu_group_edit, true);
255             menu.setGroupVisible(R.id.menu_group_insert, false);
256 
257             // Check if note has changed and enable/disable the revert option
258             String savedNote = mCursor.getString(COLUMN_INDEX_NOTE);
259             String currentNote = mText.getText().toString();
260             if (savedNote.equals(currentNote)) {
261                 menu.findItem(R.id.menu_revert).setEnabled(false);
262             } else {
263                 menu.findItem(R.id.menu_revert).setEnabled(true);
264             }
265         } else {
266             menu.setGroupVisible(R.id.menu_group_edit, false);
267             menu.setGroupVisible(R.id.menu_group_insert, true);
268         }
269         return super.onPrepareOptionsMenu(menu);
270     }
271 
272     @Override
onOptionsItemSelected(MenuItem item)273     public boolean onOptionsItemSelected(MenuItem item) {
274         // Handle all of the possible menu actions.
275         switch (item.getItemId()) {
276         case R.id.menu_save:
277             saveNote();
278             finish();
279             break;
280         case R.id.menu_delete:
281             deleteNote();
282             finish();
283             break;
284         case R.id.menu_revert:
285         case R.id.menu_discard:
286             cancelNote();
287             break;
288         }
289         return super.onOptionsItemSelected(item);
290 
291     }
292 
saveNote()293     private final void saveNote() {
294         // Make sure their current
295         // changes are safely saved away in the provider.  We don't need
296         // to do this if only editing.
297         if (mCursor != null) {
298             // Get out updates into the provider.
299             ContentValues values = new ContentValues();
300 
301             // Bump the modification time to now.
302             values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
303 
304             String text = mText.getText().toString();
305             int length = text.length();
306             // If we are creating a new note, then we want to also create
307             // an initial title for it.
308             if (mState == STATE_INSERT) {
309                 if (length == 0) {
310                     Toast.makeText(this, R.string.nothing_to_save, Toast.LENGTH_SHORT).show();
311                     return;
312                 }
313                 String title = text.substring(0, Math.min(30, length));
314                 if (length > 30) {
315                     int lastSpace = title.lastIndexOf(' ');
316                     if (lastSpace > 0) {
317                         title = title.substring(0, lastSpace);
318                     }
319                 }
320                 values.put(NoteColumns.TITLE, title);
321             }
322 
323             // Write our text back into the provider.
324             values.put(NoteColumns.NOTE, text);
325 
326             // Commit all of our changes to persistent storage. When the update completes
327             // the content provider will notify the cursor of the change, which will
328             // cause the UI to be updated.
329             try {
330                 getContentResolver().update(mUri, values, null, null);
331             } catch (NullPointerException e) {
332                 Log.e(TAG, e.getMessage());
333             }
334 
335         }
336     }
337 
338     /**
339      * Take care of canceling work on a note.  Deletes the note if we
340      * had created it, otherwise reverts to the original text.
341      */
cancelNote()342     private final void cancelNote() {
343         if (mCursor != null) {
344             if (mState == STATE_EDIT) {
345                 // Put the original note text back into the database
346                 mCursor.close();
347                 mCursor = null;
348                 ContentValues values = new ContentValues();
349                 values.put(NoteColumns.NOTE, mOriginalContent);
350                 getContentResolver().update(mUri, values, null, null);
351             } else if (mState == STATE_INSERT) {
352                 // We inserted an empty note, make sure to delete it
353                 deleteNote();
354             }
355         }
356         setResult(RESULT_CANCELED);
357         finish();
358     }
359 
360     /**
361      * Take care of deleting a note.  Simply deletes the entry.
362      */
deleteNote()363     private final void deleteNote() {
364         if (mCursor != null) {
365             mCursor.close();
366             mCursor = null;
367             getContentResolver().delete(mUri, null, null);
368             mText.setText("");
369         }
370     }
371 }
372