Hi, I intended to look at the lifecycle management in Tomdroid 0.2 (or actually the trunk). It all seems fine now. Except that some the power of the ContentProvider is not used currently (as is indicated with TODOs in the code).
The appended patch (or you can merge the branch on launchpad) contains various changes: 1) Remove 'close' menu item, as it is not necessary with Android, use the 'back' key instead. 2) Substitute OI About as about dialog. In eclipse you need to have a project with the OI About sourcecode open now. If this is a problem, we can just copy the few constants that are shared this way into Tomdroid. 3) Use a Service to fill the ContentProvider in onCreate() (but threaded) and let Android fill the list from the ContentProvider itself. Notes are not removed at this point. Also, some other thread mechanisms can likely be removed. 4) Slight UI changes. At least I think this puts Tomdroid more in line with other Android software. It all seems to work, but is not fully tested. Also I moved many pieces of code around, without making changes to the many remarks and TODOs in the code, so some refactoring and sifting through is probably in order. Let me know if you have any questions. I hope a new version can eventually be released with these changes. Regards, -- pjv
# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: [email protected] # target_branch: file:///Data/Code%20Repository/tomdroid/tomdroid/ # testament_sha1: 22f84c05d97fe028c9ceb2f72ca06470989f9557 # timestamp: 2009-06-27 23:10:55 +0200 # base_revision_id: [email protected]\ # gui128fmsj3ddb3d # # Begin patch === modified file '.classpath' --- .classpath 2009-06-21 15:58:01 +0000 +++ .classpath 2009-06-27 21:04:05 +0000 @@ -4,5 +4,6 @@ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="lib" path="lib/joda-time/joda-time-1.6.jar"/> <classpathentry kind="src" path="gen"/> + <classpathentry combineaccessrules="false" kind="src" path="/OI About"/> <classpathentry kind="output" path="bin"/> </classpath> === modified file '.project' --- .project 2008-11-19 07:01:14 +0000 +++ .project 2009-06-27 21:04:05 +0000 @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <projectDescription> - <name>tomdroid</name> + <name>tomdroid-fix-Android-lifecycle</name> <comment></comment> <projects> </projects> === modified file 'AndroidManifest.xml' --- AndroidManifest.xml 2009-06-24 19:50:22 +0000 +++ AndroidManifest.xml 2009-06-27 21:04:05 +0000 @@ -6,7 +6,27 @@ <uses-sdk android:minSdkVersion="1" /> <application android:icon="@drawable/icon" android:label="@string/app_name"> - + <meta-data android:name="org.openintents.metadata.COMMENTS" + android:value="@string/about_comments" /> + <meta-data android:name="org.openintents.metadata.COPYRIGHT" + android:value="@string/about_copyright" /> + <meta-data android:name="org.openintents.metadata.AUTHORS" + android:resource="@array/about_authors" /> + <meta-data android:name="org.openintents.metadata.DOCUMENTERS" + android:resource="@array/about_documenters" /> + <meta-data android:name="org.openintents.metadata.TRANSLATORS" + android:resource="@string/about_translators" /> + <meta-data android:name="org.openintents.metadata.ARTISTS" + android:resource="@array/about_artists" /> + <meta-data android:name="org.openintents.metadata.WEBSITE_LABEL" + android:value="@string/about_website_label" /> + <meta-data android:name="org.openintents.metadata.WEBSITE_URL" + android:value="@string/about_website_url" /> + <meta-data android:name="org.openintents.metadata.EMAIL" + android:value="@string/about_email" /> + <meta-data android:name="org.openintents.metadata.LICENSE" + android:resource="@raw/license_short" /> + <activity android:label="@string/app_name" android:name=".ui.Tomdroid" android:launchMode="singleTop" @@ -31,6 +51,8 @@ <provider android:name="NoteProvider" android:authorities="org.tomdroid.notes" /> + + <service android:name="NoteCollectingService"></service> </application> <uses-permission android:name="android.permission.INTERNET" /> </manifest> \ No newline at end of file === modified file 'res/layout/main_list_item.xml' --- res/layout/main_list_item.xml 2009-06-23 02:51:17 +0000 +++ res/layout/main_list_item.xml 2009-06-27 21:04:05 +0000 @@ -21,9 +21,10 @@ You should have received a copy of the GNU General Public License along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. --> -<TextView android:id="@+id/note_title" xmlns:android="http://schemas.android.com/apk/res/android" +<TextView android:id="@android:id/text1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:textSize="24dp" - android:padding="10dip" - /> + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAppearance="?android:attr/textAppearanceLarge" + android:gravity="center_vertical" + android:paddingLeft="5dip"/> === modified file 'res/menu/main.xml' --- res/menu/main.xml 2009-04-06 01:07:32 +0000 +++ res/menu/main.xml 2009-06-27 21:04:05 +0000 @@ -28,11 +28,11 @@ android:title="@string/menuLoadWebNote" android:id="@+id/menuLoadWebNote" /> - <item + <!-- <item android:icon="@drawable/icon_close" android:title="@string/menuClose" android:id="@+id/menuClose" - /> + /> --> <item android:icon="@drawable/icon_about" android:title="@string/menuAbout" === added directory 'res/raw' === added file 'res/raw/license_short' --- res/raw/license_short 1970-01-01 00:00:00 +0000 +++ res/raw/license_short 2009-06-27 21:04:05 +0000 @@ -0,0 +1,12 @@ +Tomdroid is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Tomdroid is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. \ No newline at end of file === added file 'res/values/arrays.xml' --- res/values/arrays.xml 1970-01-01 00:00:00 +0000 +++ res/values/arrays.xml 2009-06-27 21:04:05 +0000 @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string-array name="about_authors"> + <item>Olivier Bilodeau [email protected]</item> + <item>pjv https://launchpad.net/~pjv</item> + <item>This app reuses several system / typical components.</item> + </string-array> + + <string-array name="about_documenters"> + <item>Olivier Bilodeau [email protected]</item> + <item>pjv https://launchpad.net/~pjv</item> + </string-array> + + <string-array name="about_translators"> + <item>Olivier Bilodeau [email protected]</item> + </string-array> + + <string-array name="about_artists"> + <item>Olivier Bilodeau [email protected]</item> + </string-array> +</resources> \ No newline at end of file === modified file 'res/values/strings.xml' --- res/values/strings.xml 2009-06-21 22:50:37 +0000 +++ res/values/strings.xml 2009-06-27 21:04:05 +0000 @@ -23,9 +23,7 @@ --> <resources> <string name="app_name">Tomdroid</string> - <string name="app_desc">Tomboy compatible wikiwiki note-taking application that can\'t take notes yet.</string> - <string name="author">Olivier Bilodeau</string> - + <!-- main.xml --> <string name="strListEmptyWaiting">Please wait while the notes load..</string> <string name="strListEmptyNoNotes"> @@ -46,12 +44,20 @@ \n\nIf you would like to contribute, help at all levels would be appreciated. Hop on board on launchpad.net/tomdroid and contact us!\n </string> - <string name="strAbout"> - %1$s - \n\nAuthor: %2$s - \nVersion: %3$s - \nLicensed under the GPLv3 - </string> + + <!-- About --> + + <string name="about_comments">Tomboy compatible wikiwiki note-taking application that can\'t take notes yet.</string> + <string name="about_copyright">Copyright © 2009 Olivier Bilodeau</string><!-- Add your name here, separated with comma's, if you helped out --> + <string name="about_website_label">Project page</string> + <string name="about_website_url">http://www.launchpad.net/tomdroid/</string> + <string name="about_email">[email protected]</string> + <string name="about_feedback">[email protected]</string> + <string name="about_translators">translator-credits</string> + <string name="about_backup">To view information about this app you need to install OI About. See http://www.openintents.org/ or the Market.</string> + <string name="link_about_dialog">market://search?q=pname:org.openintents.about</string> + <string name="market_backup">You do not seem to have the Android Market app installed. Ignoring.</string> + <!-- load_web_note_dialog.xml --> <string name="strLoadFromWebTitle">Note from the Web</string> === modified file 'src/org/tomdroid/Note.java' --- src/org/tomdroid/Note.java 2009-06-21 20:25:16 +0000 +++ src/org/tomdroid/Note.java 2009-06-27 21:04:05 +0000 @@ -25,6 +25,7 @@ import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; +import org.tomdroid.ui.Tomdroid; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -45,7 +46,7 @@ public static final String[] PROJECTION = { Note.ID, Note.TITLE, Note.FILE, Note.MODIFIED_DATE }; // Logging info - private static final String TAG = "Note"; + private static final String TAG = Tomdroid.TAG;//"Note"; // Notes constants // TODO this is a weird yellow that was usable for the android emulator, I must confirm this for real usage === added file 'src/org/tomdroid/NoteCollectingService.java' --- src/org/tomdroid/NoteCollectingService.java 1970-01-01 00:00:00 +0000 +++ src/org/tomdroid/NoteCollectingService.java 2009-06-27 21:04:05 +0000 @@ -0,0 +1,183 @@ +package org.tomdroid; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStreamReader; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.tomdroid.ui.Tomdroid; +import org.tomdroid.xml.NoteHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import android.app.Service; +import android.content.ContentValues; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.IBinder; +import android.util.Log; + +public class NoteCollectingService extends Service { + private File notesRoot; + + // domain elements + private NoteCollection noteCollection; + + // Logging info + private static final String TAG = Tomdroid.TAG;//"NoteCollectingService"; + + /* (non-Javadoc) + * @see android.app.Service#onStart(android.content.Intent, int) + */ + @Override + public void onStart(Intent intent, int startId) { + // TODO Auto-generated method stub + super.onStart(intent, startId); + + noteCollection = NoteCollection.getInstance(); + notesRoot = new File(Tomdroid.NOTES_PATH); + try { + if (!notesRoot.exists()) { + throw new FileNotFoundException("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH); + }//TODO: FNFE not catched anymore + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + new Thread(new Runnable() { + public void run() { + readAndParseNotes(); + stopSelf(); + } + }).start(); + } + + + public void readAndParseNotes() { + File[] fileList = notesRoot.listFiles(new NotesFilter()); + + // If there are no notes, warn the UI through an empty message + if (fileList.length == 0) { + //TODO: also remove notes from ContentProvider + } + + for (File file : fileList) { + + // give a filename to a thread and ask to parse it when nothing's left to do its over + parseFile(file); + } + } + + /** + * Simple filename filter that grabs files ending with .note + * TODO move into its own static class in a util package + */ + class NotesFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + return (name.endsWith(".note")); + } + } + + + public void parseFile(File file) { + Note note = new Note(); + + note.setFileName(file.getAbsolutePath()); + + try { + // Parsing + // XML + // Get a SAXParser from the SAXPArserFactory + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser sp = spf.newSAXParser(); + + // Get the XMLReader of the SAXParser we created + XMLReader xr = sp.getXMLReader(); + + // Create a new ContentHandler, send it this note to fill and apply it to the XML-Reader + NoteHandler xmlHandler = new NoteHandler(note); + xr.setContentHandler(xmlHandler); + + + // Create the proper input source based on if its a local note or a web note + FileInputStream fin = new FileInputStream(file); + BufferedReader in = new BufferedReader(new InputStreamReader(fin), 8192); + InputSource is = new InputSource(in); + + if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "parsing note"); + xr.parse(is); + + // TODO wrap and throw a new exception here + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + synchronized (noteCollection) { + noteCollection.addNote(note); + } + + // notify UI that we are done here and send result + updateNoteListWith(note.getTitle()); + } + + private void updateNoteListWith(String noteTitle) { + + // get the note instance we will work with that instead from now on + Note note = NoteCollection.getInstance().findNoteFromTitle(noteTitle); + + // verify if the note is already in the content provider + + // TODO I could see a problem where someone delete a note and recreate one with the same title. + // It would been seen as not new although it is (it will have a new filename) + // TODO make the query prettier (use querybuilder) + Uri notes = Tomdroid.CONTENT_URI; + String[] whereArgs = new String[1]; + whereArgs[0] = noteTitle; + Cursor managedCursor = getContentResolver().query( notes, + NoteProvider.PROJECTION, + Note.TITLE + "= ?", + whereArgs, + Note.TITLE + " ASC"); + if (managedCursor.getCount() == 0) { + + // This note is not in the database yet we need to insert it + if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"A new note has been detected (not yet in db)"); + + // This add the note to the content Provider + // TODO PoC code that should be removed in next iteration's refactoring (no notecollection, everything should come from the provider I guess?) + ContentValues values = new ContentValues(); + values.put(Note.TITLE, note.getTitle()); + values.put(Note.FILE, note.getFileName()); + Uri uri = getContentResolver().insert(Tomdroid.CONTENT_URI, values); + // now that we inserted the note put its ID in the note itself + note.setDbId(Integer.parseInt(uri.getLastPathSegment())); + + if (Tomdroid.LOGGING_ENABLED) Log.v(TAG,"Note inserted in content provider. ID: "+uri+" TITLE:"+noteTitle+" ID:"+note.getDbId()); + } else { + + // find out the note's id and put it in the note + if (managedCursor.moveToFirst()) { + int idColumn = managedCursor.getColumnIndex(Note.ID); + note.setDbId(managedCursor.getInt(idColumn)); + } + } + } + + + @Override + public IBinder onBind(Intent intent) { + throw new RuntimeException("onBind not supported for NoteCollectingService"); + } +} === modified file 'src/org/tomdroid/NoteCollection.java' --- src/org/tomdroid/NoteCollection.java 2009-06-21 22:20:10 +0000 +++ src/org/tomdroid/NoteCollection.java 2009-06-27 21:04:05 +0000 @@ -22,17 +22,13 @@ */ package org.tomdroid; -import java.io.File; -import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import org.tomdroid.ui.Tomdroid; -import org.tomdroid.util.AsyncNoteLoaderAndParser; -import android.os.Handler; import android.util.Log; // TODO Transform the NoteCollection into a Provider (see .../android-sdk-linux_x86-1.0_r1/docs/devel/data/contentproviders.html#creatingacontentprovider) @@ -44,7 +40,7 @@ private List<Note> notes = new ArrayList<Note>(); // Logging info - private static final String TAG = "NoteCollection"; + private static final String TAG = Tomdroid.TAG;//"NoteCollection"; public List<Note> getNotes() { return notes; @@ -76,6 +72,18 @@ return null; } + public synchronized Note findNoteFromDbId(long id) { + if (Tomdroid.LOGGING_ENABLED) Log.d(TAG,"searching for note with db id: "+id); + Iterator<Note> i = notes.iterator(); + while(i.hasNext()) { + Note curNote = i.next(); + if (curNote.getDbId()==id) { + return curNote; + } + } + return null; + } + // TODO there is most likely a better way to do this public Note findNoteFromFilename(String filename) { if (Tomdroid.LOGGING_ENABLED) Log.d(TAG,"searching for note with filename: "+filename); @@ -88,18 +96,6 @@ } return null; } - - // TODO also throw a empty exception that we will catch in tomdroid and display the empty notelist msg - public void loadNotes(Handler hndl) throws FileNotFoundException { - File notesRoot = new File(Tomdroid.NOTES_PATH); - - if (!notesRoot.exists()) { - throw new FileNotFoundException("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH); - } - - AsyncNoteLoaderAndParser asyncLoader = new AsyncNoteLoaderAndParser(notesRoot, this, hndl); - asyncLoader.readAndParseNotes(); - } /** * Builds a regular expression pattern that will match any of the note title currently in the collection. === modified file 'src/org/tomdroid/NoteProvider.java' --- src/org/tomdroid/NoteProvider.java 2009-04-06 22:30:41 +0000 +++ src/org/tomdroid/NoteProvider.java 2009-06-27 21:04:05 +0000 @@ -67,20 +67,25 @@ // -- private static final String DATABASE_NAME = "tomdroid-notes.db"; private static final String DB_TABLE_NOTES = "notes"; - private static final int DB_VERSION = 1; + private static final int DB_VERSION = 2; // TODO once properly implemented, sort by: KEY_MODIFIED_DATE + " DESC" - private static final String DEFAULT_SORT_ORDER = Note.ID; + public static final String DEFAULT_SORT_ORDER = Note.ID; private static HashMap<String, String> notesProjectionMap; private static final int NOTES = 1; private static final int NOTE_ID = 2; private static final int NOTE_TITLE = 3; + + public static final String[] PROJECTION = new String[] { + Note.ID, + Note.TITLE, + }; private static final UriMatcher uriMatcher; // Logging info - private static final String TAG = "NoteProvider"; + private static final String TAG = Tomdroid.TAG;//"NoteProvider"; /** * This class helps open, create, and upgrade the database file. === modified file 'src/org/tomdroid/ui/Tomdroid.java' --- src/org/tomdroid/ui/Tomdroid.java 2009-06-24 19:50:22 +0000 +++ src/org/tomdroid/ui/Tomdroid.java 2009-06-27 21:04:05 +0000 @@ -22,24 +22,24 @@ */ package org.tomdroid.ui; -import java.io.FileNotFoundException; +import java.io.File; +import org.openintents.intents.AboutIntents; import org.tomdroid.Note; +import org.tomdroid.NoteCollectingService; import org.tomdroid.NoteCollection; +import org.tomdroid.NoteProvider; import org.tomdroid.R; import android.app.AlertDialog; import android.app.ListActivity; -import android.content.ContentValues; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnClickListener; -import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -47,7 +47,9 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; +import android.widget.SimpleCursorAdapter; import android.widget.TextView; +import android.widget.Toast; public class Tomdroid extends ListActivity { @@ -56,16 +58,15 @@ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.tomdroid.note"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.tomdroid.note"; - public static final String PROJECT_HOMEPAGE = "http://www.launchpad.net/tomdroid/"; - + // config parameters // TODO hardcoded for now public static final String NOTES_PATH = "/sdcard/tomdroid/"; // Logging should be disabled for release builds - public static final boolean LOGGING_ENABLED = false; + public static final boolean LOGGING_ENABLED = true; // Logging info - private static final String TAG = "Tomdroid"; + public static final String TAG = "Tomdroid"; // data keys public static final String RESULT_URL_TO_LOAD = "urlToLoad"; @@ -78,7 +79,6 @@ private NoteCollection localNotes; // UI to data model glue - private ArrayAdapter<String> notesListAdapter; private TextView listEmptyView; // Bundle keys for saving state @@ -111,9 +111,23 @@ .show(); } - // listAdapter that binds the UI to the notes names - notesListAdapter = new ArrayAdapter<String>(this, R.layout.main_list_item); - setListAdapter(notesListAdapter); + // If no data was given in the intent (because we were started + // as a MAIN activity), then use our default content provider. + Intent intent = getIntent(); + if (intent.getData() == null) { + intent.setData(CONTENT_URI); + } + + + // Perform a managed query. The Activity will handle closing and requerying the cursor + // when needed. + Cursor cursor = managedQuery(getIntent().getData(), NoteProvider.PROJECTION, null, null, + NoteProvider.DEFAULT_SORT_ORDER); + + // Used to map notes entries from the database to views + SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.main_list_item, cursor, + new String[] { Note.TITLE }, new int[] { android.R.id.text1 }); + setListAdapter(adapter); // set the view shown when the list is empty listEmptyView = (TextView)findViewById(R.id.list_empty); @@ -121,24 +135,19 @@ // start loading local notes if (LOGGING_ENABLED) Log.v(TAG, "Loading local notes"); - + File notesRoot = new File(Tomdroid.NOTES_PATH); + if (!notesRoot.exists()) { + new AlertDialog.Builder(this) + .setMessage("Tomdroid notes folder doesn't exist. It is configured to be at: "+Tomdroid.NOTES_PATH) + .setTitle("Error") + .setNeutralButton("Ok", new OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + }}) + .show(); + } + startService(new Intent(this, NoteCollectingService.class)); localNotes = NoteCollection.getInstance(); - try { - localNotes.loadNotes(handler); - } catch (FileNotFoundException e) { - //TODO put strings in ressource - listEmptyView.setText(R.string.strListEmptyNoNotes); - new AlertDialog.Builder(this) - .setMessage(e.getMessage()) - .setTitle("Error") - .setNeutralButton("Ok", new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - }}) - .show(); - e.printStackTrace(); - } - } @Override @@ -156,12 +165,12 @@ case R.id.menuLoadWebNote: showLoadWebNoteDialog(); return true; - +/* case R.id.menuClose: // closing everything then closing itself finishActivity(ACTIVITY_VIEW); finish(); - return true; + return true;*/ case R.id.menuAbout: showAboutDialog(); @@ -180,41 +189,29 @@ outState.putBoolean(WARNING_SHOWN, true); } } - - private void showAboutDialog() { + + /** + * Show an about dialog for this application. + */ + protected void showAboutDialog() { + Intent intent = new Intent(AboutIntents.ACTION_SHOW_ABOUT_DIALOG); - // grab version info - String ver; - try { - ver = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; - } catch (NameNotFoundException e) { - e.printStackTrace(); - ver = "Not found!"; + // Start about activity. Needs to be "forResult" with requestCode>=0 + // so that the package name is passed properly. + // + // The details are obtained from the Manifest through + // default tags and metadata. + try{ + startActivityForResult(intent, 0); + }catch(ActivityNotFoundException e){ + try{ + //FlurryAgent.onError("LCA:showAboutDialog1", getString(R.string.about_backup), "Toast"); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.link_about_dialog)))); + }catch(ActivityNotFoundException e2){ + //FlurryAgent.onError("LCA:showAboutDialog2", getString(R.string.about_backup), "Toast"); + Toast.makeText(this, getString(R.string.market_backup), Toast.LENGTH_LONG).show(); + } } - - // format the string - String aboutDialogFormat = getString(R.string.strAbout); - String aboutDialogStr = String.format(aboutDialogFormat, - getString(R.string.app_desc), // App description - getString(R.string.author), // Author name - ver // Version - ); - - // build and show the dialog - new AlertDialog.Builder(this) - .setMessage(aboutDialogStr) - .setTitle("About Tomdroid") - .setIcon(R.drawable.icon) - .setNegativeButton("Project page", new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Tomdroid.PROJECT_HOMEPAGE))); - dialog.dismiss(); - }}) - .setPositiveButton("Ok", new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - }}) - .show(); } @Override @@ -229,31 +226,12 @@ } } } - - private Handler handler = new Handler() { - - @Override - public void handleMessage(Message msg) { - - // thread is done fetching a note and parsing went well - if (msg.what == Note.NOTE_RECEIVED_AND_VALID) { - - // update the note list with this newly parsed note - updateNoteListWith(msg.getData().getString(Note.TITLE)); - - } else if (msg.what == Note.NO_NOTES) { - - // if there are no notes, say so in the list_empty message - listEmptyView.setText(R.string.strListEmptyNoNotes); - } - } - }; @Override protected void onListItemClick(ListView l, View v, int position, long id) { // get the clicked note - Note n = localNotes.findNoteFromTitle(notesListAdapter.getItem(position)); + Note n = localNotes.findNoteFromDbId(id); Intent i = new Intent(Tomdroid.this, ViewNote.class); i.putExtra(Note.FILE, n.getFileName()); @@ -273,54 +251,4 @@ i.putExtra(Note.URL, url); startActivity(i); } - - private void updateNoteListWith(String noteTitle) { - - // add note to the note list - notesListAdapter.add(noteTitle); - - // get the note instance we will work with that instead from now on - Note note = localNotes.findNoteFromTitle(noteTitle); - - // verify if the note is already in the content provider - String[] projection = new String[] { - Note.ID, - Note.TITLE, - }; - - // TODO I could see a problem where someone delete a note and recreate one with the same title. - // It would been seen as not new although it is (it will have a new filename) - // TODO make the query prettier (use querybuilder) - Uri notes = Tomdroid.CONTENT_URI; - String[] whereArgs = new String[1]; - whereArgs[0] = noteTitle; - Cursor managedCursor = managedQuery( notes, - projection, - Note.TITLE + "= ?", - whereArgs, - Note.TITLE + " ASC"); - if (managedCursor.getCount() == 0) { - - // This note is not in the database yet we need to insert it - if (LOGGING_ENABLED) Log.v(TAG,"A new note has been detected (not yet in db)"); - - // This add the note to the content Provider - // TODO PoC code that should be removed in next iteration's refactoring (no notecollection, everything should come from the provider I guess?) - ContentValues values = new ContentValues(); - values.put(Note.TITLE, note.getTitle()); - values.put(Note.FILE, note.getFileName()); - Uri uri = getContentResolver().insert(CONTENT_URI, values); - // now that we inserted the note put its ID in the note itself - note.setDbId(Integer.parseInt(uri.getLastPathSegment())); - - if (LOGGING_ENABLED) Log.v(TAG,"Note inserted in content provider. ID: "+uri+" TITLE:"+noteTitle+" ID:"+note.getDbId()); - } else { - - // find out the note's id and put it in the note - if (managedCursor.moveToFirst()) { - int idColumn = managedCursor.getColumnIndex(Note.ID); - note.setDbId(managedCursor.getInt(idColumn)); - } - } - } } === modified file 'src/org/tomdroid/ui/ViewNote.java' --- src/org/tomdroid/ui/ViewNote.java 2009-06-21 20:25:16 +0000 +++ src/org/tomdroid/ui/ViewNote.java 2009-06-27 21:04:05 +0000 @@ -60,7 +60,7 @@ private Note note; // Logging info - private static final String TAG = "ViewNote"; + private static final String TAG = Tomdroid.TAG;//"ViewNote"; // TODO extract methods in here @Override === removed file 'src/org/tomdroid/util/AsyncNoteLoaderAndParser.java' --- src/org/tomdroid/util/AsyncNoteLoaderAndParser.java 2009-06-21 21:28:14 +0000 +++ src/org/tomdroid/util/AsyncNoteLoaderAndParser.java 1970-01-01 00:00:00 +0000 @@ -1,163 +0,0 @@ -/* - * Tomdroid - * Tomboy on Android - * http://www.launchpad.net/tomdroid - * - * Copyright 2009 Olivier Bilodeau <[email protected]> - * - * This file is part of Tomdroid. - * - * Tomdroid is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Tomdroid is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tomdroid. If not, see <http://www.gnu.org/licenses/>. - */ -package org.tomdroid.util; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import org.tomdroid.Note; -import org.tomdroid.NoteCollection; -import org.tomdroid.ui.Tomdroid; -import org.tomdroid.xml.NoteHandler; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; - -public class AsyncNoteLoaderAndParser { - private final ExecutorService pool; - private final static int poolSize = 1; - private File path; - private NoteCollection noteCollection; - private Handler parentHandler; - - // logging related - private final static String TAG = "AsyncNoteLoaderAndParser"; - - public AsyncNoteLoaderAndParser(File path, NoteCollection nc, Handler hndl) { - this.path = path; - pool = Executors.newFixedThreadPool(poolSize); - noteCollection = nc; - parentHandler = hndl; - } - - public void readAndParseNotes() { - File[] fileList = path.listFiles(new NotesFilter()); - - // If there are no notes, warn the UI through an empty message - if (fileList.length == 0) { - parentHandler.sendEmptyMessage(Note.NO_NOTES); - } - - for (File file : fileList) { - - // give a filename to a thread and ask to parse it when nothing's left to do its over - pool.execute(new Worker(file)); - } - } - - /** - * Simple filename filter that grabs files ending with .note - * TODO move into its own static class in a util package - */ - class NotesFilter implements FilenameFilter { - public boolean accept(File dir, String name) { - return (name.endsWith(".note")); - } - } - - /** - * The worker spawns a new note, parse the file its being given by the executor and add it to the NoteCollection - */ - class Worker implements Runnable { - - // the note to be loaded and parsed - private Note note = new Note(); - private File file; - - public Worker(File f) { - file = f; - } - - public void run() { - - note.setFileName(file.getAbsolutePath()); - - try { - // Parsing - // XML - // Get a SAXParser from the SAXPArserFactory - SAXParserFactory spf = SAXParserFactory.newInstance(); - SAXParser sp = spf.newSAXParser(); - - // Get the XMLReader of the SAXParser we created - XMLReader xr = sp.getXMLReader(); - - // Create a new ContentHandler, send it this note to fill and apply it to the XML-Reader - NoteHandler xmlHandler = new NoteHandler(note); - xr.setContentHandler(xmlHandler); - - - // Create the proper input source based on if its a local note or a web note - FileInputStream fin = new FileInputStream(file); - BufferedReader in = new BufferedReader(new InputStreamReader(fin), 8192); - InputSource is = new InputSource(in); - - if (Tomdroid.LOGGING_ENABLED) Log.v(TAG, "parsing note"); - xr.parse(is); - - // TODO wrap and throw a new exception here - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - synchronized (noteCollection) { - noteCollection.addNote(note); - } - - // notify UI that we are done here and send result - warnHandler(); - } - - private void warnHandler() { - - // notify the main UI that we are done here (sending an ok along with the note's title) - Message msg = Message.obtain(); - Bundle bundle = new Bundle(); - bundle.putString(Note.TITLE, note.getTitle()); - msg.setData(bundle); - msg.what = Note.NOTE_RECEIVED_AND_VALID; - - parentHandler.sendMessage(msg); - } - - } -} === modified file 'src/org/tomdroid/util/NoteBuilder.java' --- src/org/tomdroid/util/NoteBuilder.java 2009-06-21 20:25:16 +0000 +++ src/org/tomdroid/util/NoteBuilder.java 2009-06-27 21:04:05 +0000 @@ -60,7 +60,7 @@ // the object being built private Note note = new Note(); - private final String TAG = "NoteBuilder"; + private final String TAG = Tomdroid.TAG;//"NoteBuilder"; // thread related private Thread runner; # Begin bundle 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
_______________________________________________ Mailing list: https://launchpad.net/~tomdroid-dev Post to : [email protected] Unsubscribe : https://launchpad.net/~tomdroid-dev More help : https://help.launchpad.net/ListHelp

