• 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 com.example.android.notepad.NotePad.Notes;
20 
21 import android.app.Activity;
22 import android.content.ComponentName;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.graphics.Canvas;
28 import android.graphics.Paint;
29 import android.graphics.Rect;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.view.Menu;
35 import android.view.MenuItem;
36 import android.widget.EditText;
37 
38 /**
39  * A generic activity for editing a note in a database.  This can be used
40  * either to simply view a note {@link Intent#ACTION_VIEW}, view and edit a note
41  * {@link Intent#ACTION_EDIT}, or create a new note {@link Intent#ACTION_INSERT}.
42  */
43 public class NoteEditor extends Activity {
44     private static final String TAG = "Notes";
45 
46     /**
47      * Standard projection for the interesting columns of a normal note.
48      */
49     private static final String[] PROJECTION = new String[] {
50             Notes._ID, // 0
51             Notes.NOTE, // 1
52     };
53     /** The index of the note column */
54     private static final int COLUMN_INDEX_NOTE = 1;
55 
56     // This is our state data that is stored when freezing.
57     private static final String ORIGINAL_CONTENT = "origContent";
58 
59     // Identifiers for our menu items.
60     private static final int REVERT_ID = Menu.FIRST;
61     private static final int DISCARD_ID = Menu.FIRST + 1;
62     private static final int DELETE_ID = Menu.FIRST + 2;
63 
64     // The different distinct states the activity can be run in.
65     private static final int STATE_EDIT = 0;
66     private static final int STATE_INSERT = 1;
67 
68     private int mState;
69     private boolean mNoteOnly = false;
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 
116         final String action = intent.getAction();
117         if (Intent.ACTION_EDIT.equals(action)) {
118             // Requested to edit: set that state, and the data being edited.
119             mState = STATE_EDIT;
120             mUri = intent.getData();
121         } else if (Intent.ACTION_INSERT.equals(action)) {
122             // Requested to insert: set that state, and create a new entry
123             // in the container.
124             mState = STATE_INSERT;
125             mUri = getContentResolver().insert(intent.getData(), null);
126 
127             // If we were unable to create a new note, then just finish
128             // this activity.  A RESULT_CANCELED will be sent back to the
129             // original activity if they requested a result.
130             if (mUri == null) {
131                 Log.e(TAG, "Failed to insert new note into " + getIntent().getData());
132                 finish();
133                 return;
134             }
135 
136             // The new entry was created, so assume all will end well and
137             // set the result to be returned.
138             setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
139 
140         } else {
141             // Whoops, unknown action!  Bail.
142             Log.e(TAG, "Unknown action, exiting");
143             finish();
144             return;
145         }
146 
147         // Set the layout for this activity.  You can find it in res/layout/note_editor.xml
148         setContentView(R.layout.note_editor);
149 
150         // The text view for our note, identified by its ID in the XML file.
151         mText = (EditText) findViewById(R.id.note);
152 
153         // Get the note!
154         mCursor = managedQuery(mUri, PROJECTION, null, null, null);
155 
156         // If an instance of this activity had previously stopped, we can
157         // get the original text it started with.
158         if (savedInstanceState != null) {
159             mOriginalContent = savedInstanceState.getString(ORIGINAL_CONTENT);
160         }
161     }
162 
163     @Override
onResume()164     protected void onResume() {
165         super.onResume();
166 
167         // If we didn't have any trouble retrieving the data, it is now
168         // time to get at the stuff.
169         if (mCursor != null) {
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                 setTitle(getText(R.string.title_edit));
176             } else if (mState == STATE_INSERT) {
177                 setTitle(getText(R.string.title_create));
178             }
179 
180             // This is a little tricky: we may be resumed after previously being
181             // paused/stopped.  We want to put the new text in the text view,
182             // but leave the user where they were (retain the cursor position
183             // etc).  This version of setText does that for us.
184             String note = mCursor.getString(COLUMN_INDEX_NOTE);
185             mText.setTextKeepState(note);
186 
187             // If we hadn't previously retrieved the original text, do so
188             // now.  This allows the user to revert their changes.
189             if (mOriginalContent == null) {
190                 mOriginalContent = note;
191             }
192 
193         } else {
194             setTitle(getText(R.string.error_title));
195             mText.setText(getText(R.string.error_message));
196         }
197     }
198 
199     @Override
onSaveInstanceState(Bundle outState)200     protected void onSaveInstanceState(Bundle outState) {
201         // Save away the original text, so we still have it if the activity
202         // needs to be killed while paused.
203         outState.putString(ORIGINAL_CONTENT, mOriginalContent);
204     }
205 
206     @Override
onPause()207     protected void onPause() {
208         super.onPause();
209 
210         // The user is going somewhere else, so make sure their current
211         // changes are safely saved away in the provider.  We don't need
212         // to do this if only editing.
213         if (mCursor != null) {
214             String text = mText.getText().toString();
215             int length = text.length();
216 
217             // If this activity is finished, and there is no text, then we
218             // do something a little special: simply delete the note entry.
219             // Note that we do this both for editing and inserting...  it
220             // would be reasonable to only do it when inserting.
221             if (isFinishing() && (length == 0) && !mNoteOnly) {
222                 setResult(RESULT_CANCELED);
223                 deleteNote();
224 
225             // Get out updates into the provider.
226             } else {
227                 ContentValues values = new ContentValues();
228 
229                 // This stuff is only done when working with a full-fledged note.
230                 if (!mNoteOnly) {
231                     // Bump the modification time to now.
232                     values.put(Notes.MODIFIED_DATE, System.currentTimeMillis());
233 
234                     // If we are creating a new note, then we want to also create
235                     // an initial title for it.
236                     if (mState == STATE_INSERT) {
237                         String title = text.substring(0, Math.min(30, length));
238                         if (length > 30) {
239                             int lastSpace = title.lastIndexOf(' ');
240                             if (lastSpace > 0) {
241                                 title = title.substring(0, lastSpace);
242                             }
243                         }
244                         values.put(Notes.TITLE, title);
245                     }
246                 }
247 
248                 // Write our text back into the provider.
249                 values.put(Notes.NOTE, text);
250 
251                 // Commit all of our changes to persistent storage. When the update completes
252                 // the content provider will notify the cursor of the change, which will
253                 // cause the UI to be updated.
254                 getContentResolver().update(mUri, values, null, null);
255             }
256         }
257     }
258 
259     @Override
onCreateOptionsMenu(Menu menu)260     public boolean onCreateOptionsMenu(Menu menu) {
261         super.onCreateOptionsMenu(menu);
262 
263         // Build the menus that are shown when editing.
264         if (mState == STATE_EDIT) {
265             menu.add(0, REVERT_ID, 0, R.string.menu_revert)
266                     .setShortcut('0', 'r')
267                     .setIcon(android.R.drawable.ic_menu_revert);
268             if (!mNoteOnly) {
269                 menu.add(0, DELETE_ID, 0, R.string.menu_delete)
270                         .setShortcut('1', 'd')
271                         .setIcon(android.R.drawable.ic_menu_delete);
272             }
273 
274         // Build the menus that are shown when inserting.
275         } else {
276             menu.add(0, DISCARD_ID, 0, R.string.menu_discard)
277                     .setShortcut('0', 'd')
278                     .setIcon(android.R.drawable.ic_menu_delete);
279         }
280 
281         // If we are working on a full note, then append to the
282         // menu items for any other activities that can do stuff with it
283         // as well.  This does a query on the system for any activities that
284         // implement the ALTERNATIVE_ACTION for our data, adding a menu item
285         // for each one that is found.
286         if (!mNoteOnly) {
287             Intent intent = new Intent(null, getIntent().getData());
288             intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
289             menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
290                     new ComponentName(this, NoteEditor.class), null, intent, 0, null);
291         }
292 
293         return true;
294     }
295 
296     @Override
onOptionsItemSelected(MenuItem item)297     public boolean onOptionsItemSelected(MenuItem item) {
298         // Handle all of the possible menu actions.
299         switch (item.getItemId()) {
300         case DELETE_ID:
301             deleteNote();
302             finish();
303             break;
304         case DISCARD_ID:
305             cancelNote();
306             break;
307         case REVERT_ID:
308             cancelNote();
309             break;
310         }
311         return super.onOptionsItemSelected(item);
312     }
313 
314     /**
315      * Take care of canceling work on a note.  Deletes the note if we
316      * had created it, otherwise reverts to the original text.
317      */
cancelNote()318     private final void cancelNote() {
319         if (mCursor != null) {
320             if (mState == STATE_EDIT) {
321                 // Put the original note text back into the database
322                 mCursor.close();
323                 mCursor = null;
324                 ContentValues values = new ContentValues();
325                 values.put(Notes.NOTE, mOriginalContent);
326                 getContentResolver().update(mUri, values, null, null);
327             } else if (mState == STATE_INSERT) {
328                 // We inserted an empty note, make sure to delete it
329                 deleteNote();
330             }
331         }
332         setResult(RESULT_CANCELED);
333         finish();
334     }
335 
336     /**
337      * Take care of deleting a note.  Simply deletes the entry.
338      */
deleteNote()339     private final void deleteNote() {
340         if (mCursor != null) {
341             mCursor.close();
342             mCursor = null;
343             getContentResolver().delete(mUri, null, null);
344             mText.setText("");
345         }
346     }
347 }
348