batch = Lists.newArrayList();
+
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ if (type == START_TAG
+ && StoryEntry.StoryTags.STORY.equals(parser.getName())) {
+ final StoryEntry entry = StoryEntry.fromParser(parser);
+
+ final String storyId = entry.get(Stories.STORY_ID);
+ // final String projectId = entry.get(StoryTags.PROJECT_ID);
+
+ final Uri storyUri = Stories.buildStoryUri(storyId);
+ final long localUpdated = ParserUtils.queryItemUpdated(
+ storyUri, resolver);
+
+ // TODO: Determine if story needs to be updated
+
+ if (localUpdated > 0) {
+ Log.i(TAG, "Updating story with id= " + storyId);
+ batch.add(ContentProviderOperation.newDelete(storyUri)
+ .build());
+ } else {
+ Log.i(TAG, "Inserting story with id= " + storyId);
+ }
+
+ final ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newInsert(Stories.CONTENT_URI);
+
+ builder.withValue(SyncColumns.UPDATED, entry
+ .get(SyncColumns.UPDATED));
+
+ for (String f : entry.keySet()) {
+ builder.withValue(f, entry.get(f));
+ }
+
+ batch.add(builder.build());
+ }
+ }
+ //TODO: delete removed stories from local store
+ return batch;
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/io/.svn/text-base/SaveStoryHandler.java.svn-base b/src/com/loganlinn/pivotaltrackie/io/.svn/text-base/SaveStoryHandler.java.svn-base
new file mode 100644
index 0000000..ba20ac3
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/.svn/text-base/SaveStoryHandler.java.svn-base
@@ -0,0 +1,5 @@
+package com.loganlinn.pivotaltrackie.io;
+
+public class SaveStoryHandler {
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/io/.svn/text-base/XmlHandler.java.svn-base b/src/com/loganlinn/pivotaltrackie/io/.svn/text-base/XmlHandler.java.svn-base
new file mode 100644
index 0000000..3a6aae5
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/.svn/text-base/XmlHandler.java.svn-base
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.
+ */
+package com.loganlinn.pivotaltrackie.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.OperationApplicationException;
+import android.net.Uri;
+import android.os.RemoteException;
+
+/**
+ * Abstract class that handles reading and parsing an {@link XmlPullParser} into
+ * a set of {@link ContentProviderOperation}. It catches recoverable network
+ * exceptions and rethrows them as {@link HandlerException}. Any local
+ * {@link ContentProvider} exceptions are considered unrecoverable.
+ *
+ * This class is only designed to handle simple one-way synchronization.
+ */
+public abstract class XmlHandler {
+ private final String mAuthority;
+ private Uri mContentUri; //story or project uri
+ public XmlHandler(String authority) {
+ mAuthority = authority;
+ }
+
+ /**
+ * Parse the given {@link XmlPullParser}, turning into a series of
+ * {@link ContentProviderOperation} that are immediately applied using the
+ * given {@link ContentResolver}.
+ */
+ public void parseAndApply(XmlPullParser parser, ContentResolver resolver)
+ throws HandlerException {
+ try {
+ final ArrayList batch = parse(parser, resolver);
+ resolver.applyBatch(mAuthority, batch);
+
+ } catch (HandlerException e) {
+ throw e;
+ } catch (XmlPullParserException e) {
+ throw new HandlerException("Problem parsing XML response", e);
+ } catch (IOException e) {
+ throw new HandlerException("Problem reading response", e);
+ } catch (RemoteException e) {
+ // Failed binder transactions aren't recoverable
+ throw new RuntimeException("Problem applying batch operation", e);
+ } catch (OperationApplicationException e) {
+ // Failures like constraint violation aren't recoverable
+ // TODO: write unit tests to exercise full provider
+ // TODO: consider catching version checking asserts here, and then
+ // wrapping around to retry parsing again.
+ throw new RuntimeException("Problem applying batch operation", e);
+ }
+ }
+
+ /**
+ * Parse the given {@link XmlPullParser}, returning a set of
+ * {@link ContentProviderOperation} that will bring the
+ * {@link ContentProvider} into sync with the parsed data.
+ */
+ public abstract ArrayList parse(XmlPullParser parser,
+ ContentResolver resolver) throws XmlPullParserException, IOException;
+
+ /**
+ * General {@link IOException} that indicates a problem occured while
+ * parsing or applying an {@link XmlPullParser}.
+ */
+ public static class HandlerException extends IOException {
+ public HandlerException(String message) {
+ super(message);
+ }
+
+ public HandlerException(String message, Throwable cause) {
+ super(message);
+ initCause(cause);
+ }
+
+ @Override
+ public String toString() {
+ if (getCause() != null) {
+ return getLocalizedMessage() + ": " + getCause();
+ } else {
+ return getLocalizedMessage();
+ }
+ }
+ }
+
+ public void setContentUri(Uri contentUri) {
+ mContentUri = contentUri;
+
+ }
+
+ protected Uri getContentUri(){
+ return mContentUri;
+ }
+}
\ No newline at end of file
diff --git a/src/com/loganlinn/pivotaltrackie/io/LocalExecutor.java b/src/com/loganlinn/pivotaltrackie/io/LocalExecutor.java
new file mode 100644
index 0000000..b00edd6
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/LocalExecutor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.
+ */
+package com.loganlinn.pivotaltrackie.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+
+import com.loganlinn.pivotaltrackie.io.XmlHandler.HandlerException;
+import com.loganlinn.pivotaltrackie.util.ParserUtils;
+
+/**
+ * Opens a local {@link Resources#getXml(int)} and passes the resulting
+ * {@link XmlPullParser} to the given {@link XmlHandler}.
+ */
+public class LocalExecutor {
+ private Resources mRes;
+ private ContentResolver mResolver;
+
+ public LocalExecutor(Resources res, ContentResolver resolver) {
+ mRes = res;
+ mResolver = resolver;
+ }
+
+ public void execute(Context context, String assetName, XmlHandler handler)
+ throws HandlerException {
+ try {
+ final InputStream input = context.getAssets().open(assetName);
+ final XmlPullParser parser = ParserUtils.newPullParser(input);
+ handler.parseAndApply(parser, mResolver);
+ } catch (HandlerException e) {
+ throw e;
+ } catch (XmlPullParserException e) {
+ throw new HandlerException("Problem parsing local asset: " + assetName, e);
+ } catch (IOException e) {
+ throw new HandlerException("Problem parsing local asset: " + assetName, e);
+ }
+ }
+
+ public void execute(int resId, XmlHandler handler) throws HandlerException {
+ final XmlResourceParser parser = mRes.getXml(resId);
+ try {
+ handler.parseAndApply(parser, mResolver);
+ } finally {
+ parser.close();
+ }
+ }
+}
+
diff --git a/src/com/loganlinn/pivotaltrackie/io/RemoteExecutor.java b/src/com/loganlinn/pivotaltrackie/io/RemoteExecutor.java
new file mode 100644
index 0000000..03ee325
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/RemoteExecutor.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.
+ */
+package com.loganlinn.pivotaltrackie.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.util.Log;
+
+import com.loganlinn.pivotaltracker.PivotalTracker;
+import com.loganlinn.pivotaltrackie.io.XmlHandler.HandlerException;
+import com.loganlinn.pivotaltrackie.util.ParserUtils;
+
+/**
+ * Executes an {@link HttpUriRequest} and passes the result as an
+ * {@link XmlPullParser} to the given {@link XmlHandler}.
+ */
+public class RemoteExecutor {
+ private static final String TAG = "RemoteExecutor";
+
+ private final HttpClient mHttpClient;
+ private final ContentResolver mResolver;
+
+ public RemoteExecutor(HttpClient httpClient, ContentResolver resolver) {
+ mHttpClient = httpClient;
+ mResolver = resolver;
+ }
+
+ /**
+ * Execute a {@link HttpGet} request, passing a valid response through
+ * {@link XmlHandler#parseAndApply(XmlPullParser, ContentResolver)}.
+ * @throws ResponseException
+ */
+ public void executeGet(String url, XmlHandler handler)
+ throws HandlerException {
+
+ execute(PivotalTracker.getGetRequest(url), handler); // get a
+ // HttpRequest
+ // object with
+ // the token in
+ // header
+ }
+
+ public void executePut(String url, XmlHandler handler)
+ throws HandlerException {
+
+ execute(PivotalTracker.getPutRequest(url), handler);
+ }
+
+ public void executePost(String url, XmlHandler handler)
+ throws HandlerException {
+
+ execute(PivotalTracker.getPostRequest(url), handler);
+ }
+
+ /**
+ * Execute this {@link HttpUriRequest}, passing a valid response through
+ * {@link XmlHandler#parseAndApply(XmlPullParser, ContentResolver)}.
+ * @throws ResponseException
+ */
+ public void execute(HttpUriRequest request, XmlHandler handler)
+ throws HandlerException {
+ Log.i(TAG, "Executing Remote "+request.getMethod()+" Request: " + request.getURI());
+
+ try {
+ // Execute the HttpRequest
+ final HttpResponse resp = mHttpClient.execute(request);
+ final int status = resp.getStatusLine().getStatusCode();
+ Log.i(TAG, "Request Response: " + status);
+
+ final boolean notFound = (status != HttpStatus.SC_NOT_FOUND);
+
+ if (status != HttpStatus.SC_OK && !notFound) {
+ throw new HandlerException("Unexpected server response "
+ + resp.getStatusLine() + " for "
+ + request.getRequestLine());
+ }
+
+
+
+ if(notFound){
+ final InputStream input = resp.getEntity().getContent();
+ // Parse the input stream using the handler
+ try {
+
+ final XmlPullParser parser = ParserUtils.newPullParser(input);
+ handler.parseAndApply(parser, mResolver);
+
+ } catch (XmlPullParserException e) {
+ throw new HandlerException("Malformed response for "
+ + request.getRequestLine(), e);
+ } finally {
+ if (input != null)
+ input.close();
+ }
+ }else if(handler.getContentUri() != null){
+ Log.i(TAG, "Deleting "+handler.getContentUri().toString());
+ mResolver.delete(handler.getContentUri(), null, null);
+ }
+ } catch (HandlerException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new HandlerException("Problem reading remote response for "
+ + request.getRequestLine(), e);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/loganlinn/pivotaltrackie/io/RemoteIterationsHandler.java b/src/com/loganlinn/pivotaltrackie/io/RemoteIterationsHandler.java
new file mode 100644
index 0000000..c56ed47
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/RemoteIterationsHandler.java
@@ -0,0 +1,31 @@
+package com.loganlinn.pivotaltrackie.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import com.loganlinn.pivotaltrackie.provider.ProjectContract;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+
+public class RemoteIterationsHandler extends XmlHandler {
+ private static final String TAG = "RemoteIterationsHandler";
+
+ private RemoteExecutor mExecutor;
+
+ public RemoteIterationsHandler(RemoteExecutor executor) {
+ super(ProjectContract.CONTENT_AUTHORITY);
+ mExecutor = executor;
+ }
+ @Override
+ public ArrayList parse(XmlPullParser parser,
+ ContentResolver resolver) throws XmlPullParserException,
+ IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/io/RemoteProjectsHandler.java b/src/com/loganlinn/pivotaltrackie/io/RemoteProjectsHandler.java
new file mode 100644
index 0000000..672f367
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/RemoteProjectsHandler.java
@@ -0,0 +1,102 @@
+package com.loganlinn.pivotaltrackie.io;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.util.Log;
+
+import com.loganlinn.pivotaltracker.ProjectEntry;
+import com.loganlinn.pivotaltracker.ProjectEntry.ProjectTags;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Projects;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.SyncColumns;
+import com.loganlinn.pivotaltrackie.util.Lists;
+import com.loganlinn.pivotaltrackie.util.ParserUtils;
+
+public class RemoteProjectsHandler extends XmlHandler {
+ private static final String TAG = "RemoteProjectsHandler";
+
+ private RemoteExecutor mExecutor;
+
+ public RemoteProjectsHandler(RemoteExecutor executor) {
+ super(ProjectContract.CONTENT_AUTHORITY);
+ mExecutor = executor;
+ }
+
+ @Override
+ public ArrayList parse(XmlPullParser parser,
+ ContentResolver resolver) throws XmlPullParserException,
+ IOException {
+ final ArrayList batch = Lists.newArrayList();
+
+ int type;
+
+ while ((type = parser.next()) != END_DOCUMENT) {
+ if (type == START_TAG
+ && ProjectEntry.ProjectTags.PROJECT
+ .equals(parser.getName())) {
+ final ProjectEntry entry = ProjectEntry.fromParser(parser);
+ Log.d(TAG, "found project " + entry.toString());
+ final String projectId = entry.get(ProjectTags.ID);
+ final Uri projectUri = Projects.buildProjectUri(projectId);
+
+ final long localUpdated = ParserUtils.queryItemUpdated(projectUri, resolver);
+ final long serverUpdated = entry.getUpdated();
+ Log.i(TAG, "found localUpdated=" + localUpdated + ", server=" + serverUpdated);
+ //if (localUpdated >= serverUpdated) continue;
+ Log.i(TAG, "Updating project with id= "+projectId);
+ batch.add(ContentProviderOperation.newDelete(projectUri).build());
+
+ final ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(Projects.CONTENT_URI);
+ builder.withValue(SyncColumns.UPDATED, serverUpdated);
+ builder.withValue(Projects.PROJECT_ID, entry.get(ProjectTags.ID));
+ builder.withValue(Projects.NAME, entry.get(ProjectTags.NAME));
+ builder.withValue(Projects.ITERATION_LENGTH, entry.get(ProjectTags.ITERATION_LENGTH));
+ builder.withValue(Projects.WEEK_START_DAY, entry.get(ProjectTags.WEEK_START_DAY));
+ builder.withValue(Projects.POINT_SCALE, entry.get(ProjectTags.POINT_SCALE));
+ builder.withValue(Projects.ACCOUNT, entry.get(ProjectTags.ACCOUNT));
+ builder.withValue(Projects.VELOCITY_SCHEME, entry.get(ProjectTags.VELOCITY_SCHEME));
+ builder.withValue(Projects.CURRENT_VELOCITY, entry.get(ProjectTags.CURRENT_VELOCITY));
+ builder.withValue(Projects.INITIAL_VELOCITY, entry.get(ProjectTags.INITIAL_VELOCITY));
+ builder.withValue(Projects.NUMBER_OF_DONE_ITERATIONS_TO_SHOW, entry.get(ProjectTags.NUMBER_OF_DONE_ITERATIONS_TO_SHOW));
+ builder.withValue(Projects.ALLOW_ATTACHMENTS, entry.get(ProjectTags.ALLOW_ATTACHMENTS));
+ builder.withValue(Projects.PUBLIC, entry.get(ProjectTags.USE_HTTPS));
+ builder.withValue(Projects.USE_HTTPS, entry.get(ProjectTags.USE_HTTPS));
+ builder.withValue(Projects.BUGS_AND_CHORES_ARE_ESTIMATABLE, entry.get(ProjectTags.BUGS_AND_CHORES_ARE_ESTIMATABLE));
+ builder.withValue(Projects.COMMIT_MODE, entry.get(ProjectTags.COMMIT_MODE));
+ builder.withValue(Projects.LAST_ACTIVITY_AT, entry.get(ProjectTags.LAST_ACTIVITY_AT));
+ builder.withValue(Projects.LABELS, entry.get(ProjectTags.LABELS));
+ batch.add(builder.build());
+ }
+ }
+
+ return batch;
+ }
+
+// private ContentValues queryProjectDetails(Uri uri, ContentResolver resolver){
+// final ContentValues values = new ContentValues();
+// final Cursor cursor = resolver.query(uri, ProjectsQuery.PROJECTION, null, null, null);
+// try {
+// if (cursor.moveToFirst()) {
+// values.put(SyncColumns.UPDATED, cursor.getLong(Projects.UPDATED));
+// values.put(Vendors.STARRED, cursor.getInt(VendorsQuery.STARRED));
+// } else {
+// values.put(SyncColumns.UPDATED, ScheduleContract.UPDATED_NEVER);
+// }
+// } finally {
+// cursor.close();
+// }
+// return values;
+// }
+
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/io/RemoteStoriesHandler.java b/src/com/loganlinn/pivotaltrackie/io/RemoteStoriesHandler.java
new file mode 100644
index 0000000..1fe073f
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/RemoteStoriesHandler.java
@@ -0,0 +1,81 @@
+package com.loganlinn.pivotaltrackie.io;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.util.Log;
+
+import com.loganlinn.pivotaltracker.StoryEntry;
+import com.loganlinn.pivotaltracker.StoryEntry.StoryTags;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Stories;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.SyncColumns;
+import com.loganlinn.pivotaltrackie.util.Lists;
+import com.loganlinn.pivotaltrackie.util.ParserUtils;
+
+public class RemoteStoriesHandler extends XmlHandler {
+ private static final String TAG = "RemoteStoriesHandler";
+
+ private RemoteExecutor mExecutor;
+ public Uri mContentUri = null;
+
+ public RemoteStoriesHandler(RemoteExecutor executor) {
+ super(ProjectContract.CONTENT_AUTHORITY);
+ mExecutor = executor;
+ }
+
+ @Override
+ public ArrayList parse(XmlPullParser parser,
+ ContentResolver resolver) throws XmlPullParserException,
+ IOException {
+ final ArrayList batch = Lists.newArrayList();
+
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ if (type == START_TAG
+ && StoryEntry.StoryTags.STORY.equals(parser.getName())) {
+ final StoryEntry entry = StoryEntry.fromParser(parser);
+
+ final String storyId = entry.get(Stories.STORY_ID);
+ // final String projectId = entry.get(StoryTags.PROJECT_ID);
+
+ final Uri storyUri = Stories.buildStoryUri(storyId);
+ final long localUpdated = ParserUtils.queryItemUpdated(
+ storyUri, resolver);
+
+ // TODO: Determine if story needs to be updated
+
+ if (localUpdated > 0) {
+ Log.i(TAG, "Updating story with id= " + storyId);
+ batch.add(ContentProviderOperation.newDelete(storyUri)
+ .build());
+ } else {
+ Log.i(TAG, "Inserting story with id= " + storyId);
+ }
+
+ final ContentProviderOperation.Builder builder = ContentProviderOperation
+ .newInsert(Stories.CONTENT_URI);
+
+ builder.withValue(SyncColumns.UPDATED, entry
+ .get(SyncColumns.UPDATED));
+
+ for (String f : entry.keySet()) {
+ builder.withValue(f, entry.get(f));
+ }
+
+ batch.add(builder.build());
+ }
+ }
+ //TODO: delete removed stories from local store
+ return batch;
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/io/SaveStoryHandler.java b/src/com/loganlinn/pivotaltrackie/io/SaveStoryHandler.java
new file mode 100644
index 0000000..ba20ac3
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/SaveStoryHandler.java
@@ -0,0 +1,5 @@
+package com.loganlinn.pivotaltrackie.io;
+
+public class SaveStoryHandler {
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/io/XmlHandler.java b/src/com/loganlinn/pivotaltrackie/io/XmlHandler.java
new file mode 100644
index 0000000..3a6aae5
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/io/XmlHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * 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.
+ */
+package com.loganlinn.pivotaltrackie.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.OperationApplicationException;
+import android.net.Uri;
+import android.os.RemoteException;
+
+/**
+ * Abstract class that handles reading and parsing an {@link XmlPullParser} into
+ * a set of {@link ContentProviderOperation}. It catches recoverable network
+ * exceptions and rethrows them as {@link HandlerException}. Any local
+ * {@link ContentProvider} exceptions are considered unrecoverable.
+ *
+ * This class is only designed to handle simple one-way synchronization.
+ */
+public abstract class XmlHandler {
+ private final String mAuthority;
+ private Uri mContentUri; //story or project uri
+ public XmlHandler(String authority) {
+ mAuthority = authority;
+ }
+
+ /**
+ * Parse the given {@link XmlPullParser}, turning into a series of
+ * {@link ContentProviderOperation} that are immediately applied using the
+ * given {@link ContentResolver}.
+ */
+ public void parseAndApply(XmlPullParser parser, ContentResolver resolver)
+ throws HandlerException {
+ try {
+ final ArrayList batch = parse(parser, resolver);
+ resolver.applyBatch(mAuthority, batch);
+
+ } catch (HandlerException e) {
+ throw e;
+ } catch (XmlPullParserException e) {
+ throw new HandlerException("Problem parsing XML response", e);
+ } catch (IOException e) {
+ throw new HandlerException("Problem reading response", e);
+ } catch (RemoteException e) {
+ // Failed binder transactions aren't recoverable
+ throw new RuntimeException("Problem applying batch operation", e);
+ } catch (OperationApplicationException e) {
+ // Failures like constraint violation aren't recoverable
+ // TODO: write unit tests to exercise full provider
+ // TODO: consider catching version checking asserts here, and then
+ // wrapping around to retry parsing again.
+ throw new RuntimeException("Problem applying batch operation", e);
+ }
+ }
+
+ /**
+ * Parse the given {@link XmlPullParser}, returning a set of
+ * {@link ContentProviderOperation} that will bring the
+ * {@link ContentProvider} into sync with the parsed data.
+ */
+ public abstract ArrayList parse(XmlPullParser parser,
+ ContentResolver resolver) throws XmlPullParserException, IOException;
+
+ /**
+ * General {@link IOException} that indicates a problem occured while
+ * parsing or applying an {@link XmlPullParser}.
+ */
+ public static class HandlerException extends IOException {
+ public HandlerException(String message) {
+ super(message);
+ }
+
+ public HandlerException(String message, Throwable cause) {
+ super(message);
+ initCause(cause);
+ }
+
+ @Override
+ public String toString() {
+ if (getCause() != null) {
+ return getLocalizedMessage() + ": " + getCause();
+ } else {
+ return getLocalizedMessage();
+ }
+ }
+ }
+
+ public void setContentUri(Uri contentUri) {
+ mContentUri = contentUri;
+
+ }
+
+ protected Uri getContentUri(){
+ return mContentUri;
+ }
+}
\ No newline at end of file
diff --git a/src/com/loganlinn/pivotaltrackie/provider/.svn/all-wcprops b/src/com/loganlinn/pivotaltrackie/provider/.svn/all-wcprops
new file mode 100644
index 0000000..b49841e
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/.svn/all-wcprops
@@ -0,0 +1,23 @@
+K 25
+svn:wc:ra_dav:version-url
+V 110
+/svn/!svn/ver/2029/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/provider
+END
+ProjectProvider.java
+K 25
+svn:wc:ra_dav:version-url
+V 131
+/svn/!svn/ver/2587/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/provider/ProjectProvider.java
+END
+ProjectContract.java
+K 25
+svn:wc:ra_dav:version-url
+V 131
+/svn/!svn/ver/2587/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/provider/ProjectContract.java
+END
+ProjectDatabase.java
+K 25
+svn:wc:ra_dav:version-url
+V 131
+/svn/!svn/ver/2389/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/provider/ProjectDatabase.java
+END
diff --git a/src/com/loganlinn/pivotaltrackie/provider/.svn/dir-prop-base b/src/com/loganlinn/pivotaltrackie/provider/.svn/dir-prop-base
new file mode 100644
index 0000000..95b2379
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/.svn/dir-prop-base
@@ -0,0 +1,6 @@
+K 10
+svn:ignore
+V 6
+.git*
+
+END
diff --git a/src/com/loganlinn/pivotaltrackie/provider/.svn/entries b/src/com/loganlinn/pivotaltrackie/provider/.svn/entries
new file mode 100644
index 0000000..bdae669
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/.svn/entries
@@ -0,0 +1,130 @@
+10
+
+dir
+2086
+https://vtnetapps.googlecode.com/svn/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/provider
+https://vtnetapps.googlecode.com/svn
+
+
+
+2010-11-26T19:33:38.466730Z
+2029
+loganlinn
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+13dd798f-c9a9-0d3a-1410-a4e436c6fec4
+
+ProjectProvider.java
+file
+2587
+
+
+
+2010-12-03T05:04:36.000000Z
+3a1856bbdb272abbea29a04b5278d9f7
+2010-12-07T22:40:29.935924Z
+2587
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5469
+
+ProjectContract.java
+file
+2587
+
+
+
+2010-12-06T06:09:58.000000Z
+af46039d0283eb614e4d7266b214d249
+2010-12-07T22:40:29.935924Z
+2587
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7141
+
+ProjectDatabase.java
+file
+2389
+
+
+
+2010-12-03T03:41:27.000000Z
+d987323aa0ead9515780e358a603afed
+2010-12-03T04:10:27.338840Z
+2389
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6101
+
diff --git a/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectContract.java.svn-base b/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectContract.java.svn-base
new file mode 100644
index 0000000..d22ef0b
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectContract.java.svn-base
@@ -0,0 +1,214 @@
+package com.loganlinn.pivotaltrackie.provider;
+
+import java.util.HashMap;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+public class ProjectContract {
+ /**
+ * Special value for {@link SyncColumns#UPDATED} indicating that an entry
+ * has never been updated, or doesn't exist yet.
+ */
+ public static final long UPDATED_NEVER = -2;
+
+ /**
+ * Special value for {@link SyncColumns#UPDATED} indicating that the last
+ * update time is unknown, usually when inserted from a local file source.
+ */
+ public static final long UPDATED_UNKNOWN = -1;
+
+ interface ProjectsColumns {
+ String PROJECT_ID = "project_id";
+ String NAME = "name";
+ String ITERATION_LENGTH = "iteration_length";
+ String WEEK_START_DAY = "week_start_day";
+ String POINT_SCALE = "point_scale";
+ String ACCOUNT = "account";
+ String VELOCITY_SCHEME = "velocity_sceme";
+ String CURRENT_VELOCITY = "current_velocity";
+ String INITIAL_VELOCITY = "initial_velocity";
+ String NUMBER_OF_DONE_ITERATIONS_TO_SHOW = "iterations_shown";
+ String ALLOW_ATTACHMENTS = "allow_attachments";
+ String PUBLIC = "public";
+ String USE_HTTPS = "use_https";
+ String BUGS_AND_CHORES_ARE_ESTIMATABLE = "estimate_bugs_chores";
+ String COMMIT_MODE = "commit_mode";
+ String LAST_ACTIVITY_AT = "last_activity";
+ String LABELS = "labels";
+ }
+
+ interface IterationColumns {
+ String ITERATION_ID = "iteration_id";
+ String PROJECT_ID = Projects.PROJECT_ID;
+ String NUMBER = "number";
+ String START = "start";
+ String END = "end";
+
+ }
+
+ interface StoriesColumns {
+ String STORY_ID = "story_id";
+ String PROJECT_ID = Projects.PROJECT_ID;
+ String STORY_TYPE = "story_type";
+ String URL = "url";
+ String ESTIMATE = "estimate";
+ String CURRENT_STATE = "current_state";
+ String DESCRIPTION = "description";
+ String NAME = "name";
+ String REQUESTED_BY = "requested_by";
+ String OWNED_BY = "owned_by";
+ String CREATED_AT = "created_at";
+ String ACCEPTED_AT = "accepted_at";
+ String LABELS = "labels";
+ String ITERATION_ID = IterationColumns.ITERATION_ID;
+ }
+
+ interface MembersColumns {
+ String MEMBER_ID = "member_id";
+ String PROJECT_ID = "project_id";
+ String EMAIL = "email";
+ String NAME = "name";
+ String INITIALS = "initials";
+ String ROLE = "role";
+ }
+
+ public interface SyncColumns {
+ /** Last time this entry was updated or synchronized. */
+ String UPDATED = "updated";
+ }
+
+ public static final String CONTENT_AUTHORITY = "com.loganlinn.pivotaltrackie";
+
+ private static final Uri BASE_CONTENT_URI = Uri.parse("content://"
+ + CONTENT_AUTHORITY);
+
+ private static final String PATH_PROJECTS = "projects";
+ private static final String PATH_STORIES = "stories";
+ private static final String PATH_MEMBERS = "members";
+ private static final String PATH_ITERATIONS = "iterations";
+
+ public static class Projects implements ProjectsColumns, BaseColumns,
+ SyncColumns {
+ private static final String TAG = "Projects";
+
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_PROJECTS).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.project";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.project";
+
+ public static Uri buildProjectUri(String projectId) {
+ return CONTENT_URI.buildUpon().appendPath(projectId).build();
+ }
+
+ public static Uri buildProjectUri(Integer projectId) {
+ return buildProjectUri(projectId.toString());
+ }
+
+ public static String getProjectId(Uri uri) {
+ Log.i(TAG, "ProjectId in [" + uri + "] is "
+ + uri.getPathSegments().get(1));
+ return uri.getPathSegments().get(1);
+ }
+ }
+
+ public static class Members implements MembersColumns, BaseColumns,
+ SyncColumns {
+ private static final String TAG = "Members";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_MEMBERS).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.member";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.member";
+
+ public static Uri buildMemberUri(String memberId) {
+ return CONTENT_URI.buildUpon().appendPath(memberId).build();
+ }
+
+ public static String getMemberId(Uri uri) {
+ Log.i(TAG, "MemberId in [" + uri + "] is "
+ + uri.getPathSegments().get(1));
+ return uri.getPathSegments().get(1);
+ }
+ }
+
+ public static class Iterations implements IterationColumns, BaseColumns,
+ SyncColumns {
+ public static final String TAG = "Iterations";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_ITERATIONS).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.iteration";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.iteration";
+
+ public static Uri buildIterationUri(String iterationId) {
+ return CONTENT_URI.buildUpon().appendPath(iterationId).build();
+ }
+
+ public static String getIterationId(Uri uri) {
+ return uri.getPathSegments().get(1);
+ }
+ }
+
+ public static class Stories implements StoriesColumns, BaseColumns,
+ SyncColumns {
+ private static final String TAG = "Stories";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_STORIES).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.story";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.story";
+
+ public static final String STORY_TYPE_FEATURE = "feature";
+ public static final String STORY_TYPE_CHORE = "chore";
+ public static final String STORY_TYPE_BUG = "bug";
+ public static final String CURRENT_STATUS_ACCEPTED = "accepted";
+ public static final String CURRENT_STATUS_UNSCHEDULED = "unscheduled";
+ public static final String CURRENT_STATUS_FINISHED = "finished";
+ public static final String CURRENT_STATUS_DELIVERED = "delivered";
+ public static final String CURRENT_STATUS_STARTED = "started";
+ public static final int NEW_STORY = -2;
+
+ /** Default "ORDER BY" clause. */
+ // public static final String DEFAULT_SORT = BlocksColumns.BLOCK_START +
+ // " ASC, "
+ // + BlocksColumns.BLOCK_END + " ASC";
+
+ public static Uri buildStoryUri(String storyId) {
+ return CONTENT_URI.buildUpon().appendPath(storyId).build();
+ }
+
+ public static Uri buildStoryUri(Integer storyId) {
+ return buildStoryUri(storyId.toString());
+ }
+
+ public static Uri buildStoryFilterUri(StoryFilter filter) {
+ return CONTENT_URI.buildUpon()
+ .appendPath(filter.getEncodedResult()).build();
+ }
+
+ public static String getStoryId(Uri uri) {
+ return uri.getPathSegments().get(1);
+ }
+
+ }
+
+
+ interface StoryFilter {
+ HashMap filterValues_ = new HashMap();
+
+ public void addLabelFilter(String label);
+
+ public void addIdFilter(String id);
+
+ public void addStoryTypeFilter(String type);
+
+ public void addCurrentStateFilter(String state);
+
+ public void addNameFilter(String name);
+
+ public String getEncodedResult();
+ }
+
+ private ProjectContract() {
+
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectDatabase.java.svn-base b/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectDatabase.java.svn-base
new file mode 100644
index 0000000..d7495bd
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectDatabase.java.svn-base
@@ -0,0 +1,147 @@
+package com.loganlinn.pivotaltrackie.provider;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.IterationColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.MembersColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.ProjectsColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.StoriesColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.SyncColumns;
+
+public class ProjectDatabase extends SQLiteOpenHelper {
+ private static final String TAG = "ProjectDatabase";
+
+ private static final String DATABASE_NAME = "project.db";
+ private static final int DATABASE_VERSION = 14;
+
+ interface Tables {
+ String PROJECTS = "projects";
+ String STORIES = "stories";
+ String MEMBERS = "members";
+ String STORY_TASK = "story_task";
+ String STORY_ATTACHMENT = "story_attachment";
+ String PROJECT_INTEGRATIONS = "project_integrations";
+ String PROJECT_LABELS = "project_labels";
+ String STORY_FILTERS = "story_filters";
+ String ITERATIONS = "iterations";
+
+ // Joins
+ /*
+ * Example: String SESSIONS_JOIN_BLOCKS_ROOMS = "sessions " +
+ * "LEFT OUTER JOIN blocks ON sessions.block_id=blocks.block_id " +
+ * "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id";
+ */
+ }
+
+ private interface References{
+ String PROJECT_ID = "REFERENCES " + Tables.PROJECTS + "(" + ProjectsColumns.PROJECT_ID + ")";
+ String ITERATION_ID = "REFERENCES " + Tables.ITERATIONS + "("+ IterationColumns.ITERATION_ID + ")";
+ }
+
+ public ProjectDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.i(TAG, "Creating database tables");
+ /*
+ * Projects Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.PROJECTS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + ProjectsColumns.PROJECT_ID + " INTEGER NOT NULL,"
+ + ProjectsColumns.NAME + " TEXT NOT NULL,"
+ + ProjectsColumns.ITERATION_LENGTH + " INTEGER NOT NULL,"
+ + ProjectsColumns.WEEK_START_DAY + " TEXT NOT NULL,"
+ + ProjectsColumns.POINT_SCALE + " TEXT NOT NULL,"
+ + ProjectsColumns.ACCOUNT + " TEXT NOT NULL,"
+ + ProjectsColumns.VELOCITY_SCHEME + " TEXT NOT NULL,"
+ + ProjectsColumns.CURRENT_VELOCITY + " INTEGER NOT NULL,"
+ + ProjectsColumns.INITIAL_VELOCITY + " INTEGER NOT NULL,"
+ + ProjectsColumns.NUMBER_OF_DONE_ITERATIONS_TO_SHOW + " INTEGER NOT NULL,"
+ + ProjectsColumns.ALLOW_ATTACHMENTS + " INTEGER NOT NULL,"
+ + ProjectsColumns.PUBLIC + " INTEGER NOT NULL,"
+ + ProjectsColumns.USE_HTTPS + " INTEGER NOT NULL,"
+ + ProjectsColumns.BUGS_AND_CHORES_ARE_ESTIMATABLE + " INTEGER,"
+ + ProjectsColumns.COMMIT_MODE + " INTEGER,"
+ + ProjectsColumns.LAST_ACTIVITY_AT + " TEXT,"
+ + ProjectsColumns.LABELS + " TEXT,"
+ + SyncColumns.UPDATED + " INTEGER NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+
+ /*
+ * Stories Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.STORIES + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + StoriesColumns.STORY_ID + " INTEGER NOT NULL,"
+ + StoriesColumns.PROJECT_ID + " INTEGER NOT NULL "+ References.PROJECT_ID +","
+ + StoriesColumns.ITERATION_ID + " INTEGER "+References.ITERATION_ID+","
+ + StoriesColumns.STORY_TYPE + " TEXT NOT NULL,"
+ + StoriesColumns.URL + " TEXT NOT NULL,"
+ + StoriesColumns.ESTIMATE + " INTEGER,"
+ + StoriesColumns.CURRENT_STATE + " TEXT NOT NULL,"
+ + StoriesColumns.DESCRIPTION + " TEXT,"
+ + StoriesColumns.NAME + " TEXT NOT NULL,"
+ + StoriesColumns.REQUESTED_BY + " TEXT,"
+ + StoriesColumns.OWNED_BY + " TEXT,"
+ + StoriesColumns.CREATED_AT + " TEXT,"
+ + StoriesColumns.ACCEPTED_AT + " TEXT,"
+ + StoriesColumns.LABELS + " TEXT,"
+ + SyncColumns.UPDATED + " INTEGER NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+
+ /*
+ * Members Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.MEMBERS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + MembersColumns.MEMBER_ID + " INTEGER NOT NULL,"
+ + MembersColumns.PROJECT_ID + " INTEGER NOT NULL "+ References.PROJECT_ID +","
+ + MembersColumns.EMAIL + " TEXT NOT NULL,"
+ + MembersColumns.NAME + " TEXT NOT NULL,"
+ + MembersColumns.INITIALS + " TEXT NOT NULL,"
+ + MembersColumns.ROLE + " TEXT NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+
+ /*
+ * Iterations Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.ITERATIONS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + IterationColumns.ITERATION_ID + " INTEGER NOT NULL,"
+ + IterationColumns.PROJECT_ID + " INTEGER NOT NULL "+References.PROJECT_ID+","
+ + IterationColumns.NUMBER + " INTEGER NOT NULL,"
+ + IterationColumns.START + " TEXT NOT NULL,"
+ + IterationColumns.END + " TEXT NOT NULL,"
+ + SyncColumns.UPDATED + " INTEGER NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.d(TAG, "onUpgrade() from " + oldVersion + " to " + newVersion);
+
+ if(oldVersion != DATABASE_VERSION){
+ Log.w(TAG, "Destroying old data during upgrade");
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.PROJECTS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.MEMBERS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORIES);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.PROJECT_INTEGRATIONS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.PROJECT_LABELS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORY_ATTACHMENT);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORY_TASK);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORY_FILTERS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.ITERATIONS);
+ onCreate(db);
+ }
+
+
+ }
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectProvider.java.svn-base b/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectProvider.java.svn-base
new file mode 100644
index 0000000..d8368c7
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/.svn/text-base/ProjectProvider.java.svn-base
@@ -0,0 +1,174 @@
+ package com.loganlinn.pivotaltrackie.provider;
+
+import java.util.Arrays;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.util.Log;
+
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Members;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Projects;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Stories;
+import com.loganlinn.pivotaltrackie.provider.ProjectDatabase.Tables;
+import com.loganlinn.pivotaltrackie.util.SelectionBuilder;
+
+public class ProjectProvider extends ContentProvider {
+ private static final String TAG = "ProjectProvider";
+ private static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final String MIME_XML = "text/xml";
+ private static final UriMatcher sUriMatcher = buildUriMatcher();
+
+ // Uri values
+ private static final int PROJECTS = 100;
+ private static final int PROJECTS_ID = 101;
+ private static final int STORIES = 200;
+ private static final int STORIES_ID = 201;
+ private static final int MEMBERS = 300;
+ private static final int MEMBERS_ID = 301;
+
+ private ProjectDatabase mOpenHelper;
+
+ private static UriMatcher buildUriMatcher() {
+ final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+ final String authority = ProjectContract.CONTENT_AUTHORITY;
+
+ matcher.addURI(authority, "projects", PROJECTS);
+ matcher.addURI(authority, "projects/*", PROJECTS_ID);
+
+ matcher.addURI(authority, "stories", STORIES);
+ matcher.addURI(authority, "stories/*", STORIES_ID);
+
+ matcher.addURI(authority, "members", MEMBERS);
+ matcher.addURI(authority, "members/*", MEMBERS_ID);
+
+ return matcher;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ final int match = sUriMatcher.match(uri);
+ switch (match) {
+ case PROJECTS:
+ return Projects.CONTENT_TYPE;
+ case PROJECTS_ID:
+ return Projects.CONTENT_ITEM_TYPE;
+ case STORIES:
+ return Stories.CONTENT_TYPE;
+ case STORIES_ID:
+ return Stories.CONTENT_ITEM_TYPE;
+ case MEMBERS:
+ return Members.CONTENT_TYPE;
+ case MEMBERS_ID:
+ return Members.CONTENT_ITEM_TYPE;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ final Context context = getContext();
+ mOpenHelper = new ProjectDatabase(context);
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (LOGV)
+ Log.v(TAG, "query(uri=" + uri + ", proj="
+ + Arrays.toString(projection) + ")");
+ final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+
+ // final int match = uriMatcher_.match(uri);
+
+ // switch(match){
+ // default: {
+ final SelectionBuilder builder = buildSelection(uri);
+ return builder.where(selection, selectionArgs).query(db, projection,
+ sortOrder);
+ // }
+ // }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ if (LOGV)
+ Log.v(TAG, "insert(uri=" + uri + ", values=" + values.toString()
+ + ")");
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sUriMatcher.match(uri);
+
+ switch (match) {
+ case PROJECTS:
+ db.insertOrThrow(Tables.PROJECTS, null, values);
+ return Projects.buildProjectUri(values
+ .getAsString(Projects.PROJECT_ID));
+ case STORIES:
+ db.insertOrThrow(Tables.STORIES, null, values);
+ return Stories.buildStoryUri(values.getAsString(Stories.STORY_ID));
+ case MEMBERS:
+ db.insertOrThrow(Tables.MEMBERS, null, values);
+ return Members
+ .buildMemberUri(values.getAsString(Members.MEMBER_ID));
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ if (LOGV)
+ Log.v(TAG, "update(uri=" + uri + ", values=" + values.toString()
+ + ")");
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final SelectionBuilder builder = buildSelection(uri);
+ return builder.where(selection, selectionArgs).update(db, values);
+
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (LOGV)
+ Log.v(TAG, "delete(uri=" + uri + ")");
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final SelectionBuilder builder = buildSelection(uri);
+ return builder.where(selection, selectionArgs).delete(db);
+ }
+
+ private SelectionBuilder buildSelection(Uri uri) {
+ final SelectionBuilder builder = new SelectionBuilder();
+ final int match = sUriMatcher.match(uri);
+ String projectId;
+ String storyId;
+ switch (match) {
+ case PROJECTS:
+ return builder.table(Tables.PROJECTS);
+ case PROJECTS_ID:
+ projectId = Projects.getProjectId(uri);
+ return builder.table(Tables.PROJECTS).where(
+ Projects.PROJECT_ID + "=?", projectId);
+ case STORIES:
+ return builder.table(Tables.STORIES);
+ case STORIES_ID:
+ storyId = Stories.getStoryId(uri);
+ return builder.table(Tables.STORIES).where(
+ Stories.STORY_ID + "=?", storyId);
+ case MEMBERS:
+ return builder.table(Tables.MEMBERS);
+ case MEMBERS_ID:
+ final String memberId = Members.getMemberId(uri);
+ return builder.table(Tables.MEMBERS).where(
+ Members.MEMBER_ID + "=?", memberId);
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/provider/ProjectContract.java b/src/com/loganlinn/pivotaltrackie/provider/ProjectContract.java
new file mode 100644
index 0000000..d22ef0b
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/ProjectContract.java
@@ -0,0 +1,214 @@
+package com.loganlinn.pivotaltrackie.provider;
+
+import java.util.HashMap;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+public class ProjectContract {
+ /**
+ * Special value for {@link SyncColumns#UPDATED} indicating that an entry
+ * has never been updated, or doesn't exist yet.
+ */
+ public static final long UPDATED_NEVER = -2;
+
+ /**
+ * Special value for {@link SyncColumns#UPDATED} indicating that the last
+ * update time is unknown, usually when inserted from a local file source.
+ */
+ public static final long UPDATED_UNKNOWN = -1;
+
+ interface ProjectsColumns {
+ String PROJECT_ID = "project_id";
+ String NAME = "name";
+ String ITERATION_LENGTH = "iteration_length";
+ String WEEK_START_DAY = "week_start_day";
+ String POINT_SCALE = "point_scale";
+ String ACCOUNT = "account";
+ String VELOCITY_SCHEME = "velocity_sceme";
+ String CURRENT_VELOCITY = "current_velocity";
+ String INITIAL_VELOCITY = "initial_velocity";
+ String NUMBER_OF_DONE_ITERATIONS_TO_SHOW = "iterations_shown";
+ String ALLOW_ATTACHMENTS = "allow_attachments";
+ String PUBLIC = "public";
+ String USE_HTTPS = "use_https";
+ String BUGS_AND_CHORES_ARE_ESTIMATABLE = "estimate_bugs_chores";
+ String COMMIT_MODE = "commit_mode";
+ String LAST_ACTIVITY_AT = "last_activity";
+ String LABELS = "labels";
+ }
+
+ interface IterationColumns {
+ String ITERATION_ID = "iteration_id";
+ String PROJECT_ID = Projects.PROJECT_ID;
+ String NUMBER = "number";
+ String START = "start";
+ String END = "end";
+
+ }
+
+ interface StoriesColumns {
+ String STORY_ID = "story_id";
+ String PROJECT_ID = Projects.PROJECT_ID;
+ String STORY_TYPE = "story_type";
+ String URL = "url";
+ String ESTIMATE = "estimate";
+ String CURRENT_STATE = "current_state";
+ String DESCRIPTION = "description";
+ String NAME = "name";
+ String REQUESTED_BY = "requested_by";
+ String OWNED_BY = "owned_by";
+ String CREATED_AT = "created_at";
+ String ACCEPTED_AT = "accepted_at";
+ String LABELS = "labels";
+ String ITERATION_ID = IterationColumns.ITERATION_ID;
+ }
+
+ interface MembersColumns {
+ String MEMBER_ID = "member_id";
+ String PROJECT_ID = "project_id";
+ String EMAIL = "email";
+ String NAME = "name";
+ String INITIALS = "initials";
+ String ROLE = "role";
+ }
+
+ public interface SyncColumns {
+ /** Last time this entry was updated or synchronized. */
+ String UPDATED = "updated";
+ }
+
+ public static final String CONTENT_AUTHORITY = "com.loganlinn.pivotaltrackie";
+
+ private static final Uri BASE_CONTENT_URI = Uri.parse("content://"
+ + CONTENT_AUTHORITY);
+
+ private static final String PATH_PROJECTS = "projects";
+ private static final String PATH_STORIES = "stories";
+ private static final String PATH_MEMBERS = "members";
+ private static final String PATH_ITERATIONS = "iterations";
+
+ public static class Projects implements ProjectsColumns, BaseColumns,
+ SyncColumns {
+ private static final String TAG = "Projects";
+
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_PROJECTS).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.project";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.project";
+
+ public static Uri buildProjectUri(String projectId) {
+ return CONTENT_URI.buildUpon().appendPath(projectId).build();
+ }
+
+ public static Uri buildProjectUri(Integer projectId) {
+ return buildProjectUri(projectId.toString());
+ }
+
+ public static String getProjectId(Uri uri) {
+ Log.i(TAG, "ProjectId in [" + uri + "] is "
+ + uri.getPathSegments().get(1));
+ return uri.getPathSegments().get(1);
+ }
+ }
+
+ public static class Members implements MembersColumns, BaseColumns,
+ SyncColumns {
+ private static final String TAG = "Members";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_MEMBERS).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.member";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.member";
+
+ public static Uri buildMemberUri(String memberId) {
+ return CONTENT_URI.buildUpon().appendPath(memberId).build();
+ }
+
+ public static String getMemberId(Uri uri) {
+ Log.i(TAG, "MemberId in [" + uri + "] is "
+ + uri.getPathSegments().get(1));
+ return uri.getPathSegments().get(1);
+ }
+ }
+
+ public static class Iterations implements IterationColumns, BaseColumns,
+ SyncColumns {
+ public static final String TAG = "Iterations";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_ITERATIONS).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.iteration";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.iteration";
+
+ public static Uri buildIterationUri(String iterationId) {
+ return CONTENT_URI.buildUpon().appendPath(iterationId).build();
+ }
+
+ public static String getIterationId(Uri uri) {
+ return uri.getPathSegments().get(1);
+ }
+ }
+
+ public static class Stories implements StoriesColumns, BaseColumns,
+ SyncColumns {
+ private static final String TAG = "Stories";
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon()
+ .appendPath(PATH_STORIES).build();
+ public static final String CONTENT_TYPE = "vnd.loganlinn.cursor.dir/vnd.pivotaltrackie.story";
+ public static final String CONTENT_ITEM_TYPE = "vnd.loganlinn.cursor.item/vnd.pivotaltrackie.story";
+
+ public static final String STORY_TYPE_FEATURE = "feature";
+ public static final String STORY_TYPE_CHORE = "chore";
+ public static final String STORY_TYPE_BUG = "bug";
+ public static final String CURRENT_STATUS_ACCEPTED = "accepted";
+ public static final String CURRENT_STATUS_UNSCHEDULED = "unscheduled";
+ public static final String CURRENT_STATUS_FINISHED = "finished";
+ public static final String CURRENT_STATUS_DELIVERED = "delivered";
+ public static final String CURRENT_STATUS_STARTED = "started";
+ public static final int NEW_STORY = -2;
+
+ /** Default "ORDER BY" clause. */
+ // public static final String DEFAULT_SORT = BlocksColumns.BLOCK_START +
+ // " ASC, "
+ // + BlocksColumns.BLOCK_END + " ASC";
+
+ public static Uri buildStoryUri(String storyId) {
+ return CONTENT_URI.buildUpon().appendPath(storyId).build();
+ }
+
+ public static Uri buildStoryUri(Integer storyId) {
+ return buildStoryUri(storyId.toString());
+ }
+
+ public static Uri buildStoryFilterUri(StoryFilter filter) {
+ return CONTENT_URI.buildUpon()
+ .appendPath(filter.getEncodedResult()).build();
+ }
+
+ public static String getStoryId(Uri uri) {
+ return uri.getPathSegments().get(1);
+ }
+
+ }
+
+
+ interface StoryFilter {
+ HashMap filterValues_ = new HashMap();
+
+ public void addLabelFilter(String label);
+
+ public void addIdFilter(String id);
+
+ public void addStoryTypeFilter(String type);
+
+ public void addCurrentStateFilter(String state);
+
+ public void addNameFilter(String name);
+
+ public String getEncodedResult();
+ }
+
+ private ProjectContract() {
+
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/provider/ProjectDatabase.java b/src/com/loganlinn/pivotaltrackie/provider/ProjectDatabase.java
new file mode 100644
index 0000000..d7495bd
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/ProjectDatabase.java
@@ -0,0 +1,147 @@
+package com.loganlinn.pivotaltrackie.provider;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.IterationColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.MembersColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.ProjectsColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.StoriesColumns;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.SyncColumns;
+
+public class ProjectDatabase extends SQLiteOpenHelper {
+ private static final String TAG = "ProjectDatabase";
+
+ private static final String DATABASE_NAME = "project.db";
+ private static final int DATABASE_VERSION = 14;
+
+ interface Tables {
+ String PROJECTS = "projects";
+ String STORIES = "stories";
+ String MEMBERS = "members";
+ String STORY_TASK = "story_task";
+ String STORY_ATTACHMENT = "story_attachment";
+ String PROJECT_INTEGRATIONS = "project_integrations";
+ String PROJECT_LABELS = "project_labels";
+ String STORY_FILTERS = "story_filters";
+ String ITERATIONS = "iterations";
+
+ // Joins
+ /*
+ * Example: String SESSIONS_JOIN_BLOCKS_ROOMS = "sessions " +
+ * "LEFT OUTER JOIN blocks ON sessions.block_id=blocks.block_id " +
+ * "LEFT OUTER JOIN rooms ON sessions.room_id=rooms.room_id";
+ */
+ }
+
+ private interface References{
+ String PROJECT_ID = "REFERENCES " + Tables.PROJECTS + "(" + ProjectsColumns.PROJECT_ID + ")";
+ String ITERATION_ID = "REFERENCES " + Tables.ITERATIONS + "("+ IterationColumns.ITERATION_ID + ")";
+ }
+
+ public ProjectDatabase(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.i(TAG, "Creating database tables");
+ /*
+ * Projects Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.PROJECTS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + ProjectsColumns.PROJECT_ID + " INTEGER NOT NULL,"
+ + ProjectsColumns.NAME + " TEXT NOT NULL,"
+ + ProjectsColumns.ITERATION_LENGTH + " INTEGER NOT NULL,"
+ + ProjectsColumns.WEEK_START_DAY + " TEXT NOT NULL,"
+ + ProjectsColumns.POINT_SCALE + " TEXT NOT NULL,"
+ + ProjectsColumns.ACCOUNT + " TEXT NOT NULL,"
+ + ProjectsColumns.VELOCITY_SCHEME + " TEXT NOT NULL,"
+ + ProjectsColumns.CURRENT_VELOCITY + " INTEGER NOT NULL,"
+ + ProjectsColumns.INITIAL_VELOCITY + " INTEGER NOT NULL,"
+ + ProjectsColumns.NUMBER_OF_DONE_ITERATIONS_TO_SHOW + " INTEGER NOT NULL,"
+ + ProjectsColumns.ALLOW_ATTACHMENTS + " INTEGER NOT NULL,"
+ + ProjectsColumns.PUBLIC + " INTEGER NOT NULL,"
+ + ProjectsColumns.USE_HTTPS + " INTEGER NOT NULL,"
+ + ProjectsColumns.BUGS_AND_CHORES_ARE_ESTIMATABLE + " INTEGER,"
+ + ProjectsColumns.COMMIT_MODE + " INTEGER,"
+ + ProjectsColumns.LAST_ACTIVITY_AT + " TEXT,"
+ + ProjectsColumns.LABELS + " TEXT,"
+ + SyncColumns.UPDATED + " INTEGER NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+
+ /*
+ * Stories Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.STORIES + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + StoriesColumns.STORY_ID + " INTEGER NOT NULL,"
+ + StoriesColumns.PROJECT_ID + " INTEGER NOT NULL "+ References.PROJECT_ID +","
+ + StoriesColumns.ITERATION_ID + " INTEGER "+References.ITERATION_ID+","
+ + StoriesColumns.STORY_TYPE + " TEXT NOT NULL,"
+ + StoriesColumns.URL + " TEXT NOT NULL,"
+ + StoriesColumns.ESTIMATE + " INTEGER,"
+ + StoriesColumns.CURRENT_STATE + " TEXT NOT NULL,"
+ + StoriesColumns.DESCRIPTION + " TEXT,"
+ + StoriesColumns.NAME + " TEXT NOT NULL,"
+ + StoriesColumns.REQUESTED_BY + " TEXT,"
+ + StoriesColumns.OWNED_BY + " TEXT,"
+ + StoriesColumns.CREATED_AT + " TEXT,"
+ + StoriesColumns.ACCEPTED_AT + " TEXT,"
+ + StoriesColumns.LABELS + " TEXT,"
+ + SyncColumns.UPDATED + " INTEGER NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+
+ /*
+ * Members Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.MEMBERS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + MembersColumns.MEMBER_ID + " INTEGER NOT NULL,"
+ + MembersColumns.PROJECT_ID + " INTEGER NOT NULL "+ References.PROJECT_ID +","
+ + MembersColumns.EMAIL + " TEXT NOT NULL,"
+ + MembersColumns.NAME + " TEXT NOT NULL,"
+ + MembersColumns.INITIALS + " TEXT NOT NULL,"
+ + MembersColumns.ROLE + " TEXT NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+
+ /*
+ * Iterations Table
+ */
+ db.execSQL("CREATE TABLE " + Tables.ITERATIONS + " ("
+ + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ + IterationColumns.ITERATION_ID + " INTEGER NOT NULL,"
+ + IterationColumns.PROJECT_ID + " INTEGER NOT NULL "+References.PROJECT_ID+","
+ + IterationColumns.NUMBER + " INTEGER NOT NULL,"
+ + IterationColumns.START + " TEXT NOT NULL,"
+ + IterationColumns.END + " TEXT NOT NULL,"
+ + SyncColumns.UPDATED + " INTEGER NOT NULL,"
+ + "UNIQUE (" + BaseColumns._ID + ") ON CONFLICT REPLACE)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.d(TAG, "onUpgrade() from " + oldVersion + " to " + newVersion);
+
+ if(oldVersion != DATABASE_VERSION){
+ Log.w(TAG, "Destroying old data during upgrade");
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.PROJECTS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.MEMBERS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORIES);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.PROJECT_INTEGRATIONS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.PROJECT_LABELS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORY_ATTACHMENT);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORY_TASK);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.STORY_FILTERS);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.ITERATIONS);
+ onCreate(db);
+ }
+
+
+ }
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/provider/ProjectProvider.java b/src/com/loganlinn/pivotaltrackie/provider/ProjectProvider.java
new file mode 100644
index 0000000..d8368c7
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/provider/ProjectProvider.java
@@ -0,0 +1,174 @@
+ package com.loganlinn.pivotaltrackie.provider;
+
+import java.util.Arrays;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.util.Log;
+
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Members;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Projects;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Stories;
+import com.loganlinn.pivotaltrackie.provider.ProjectDatabase.Tables;
+import com.loganlinn.pivotaltrackie.util.SelectionBuilder;
+
+public class ProjectProvider extends ContentProvider {
+ private static final String TAG = "ProjectProvider";
+ private static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final String MIME_XML = "text/xml";
+ private static final UriMatcher sUriMatcher = buildUriMatcher();
+
+ // Uri values
+ private static final int PROJECTS = 100;
+ private static final int PROJECTS_ID = 101;
+ private static final int STORIES = 200;
+ private static final int STORIES_ID = 201;
+ private static final int MEMBERS = 300;
+ private static final int MEMBERS_ID = 301;
+
+ private ProjectDatabase mOpenHelper;
+
+ private static UriMatcher buildUriMatcher() {
+ final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+ final String authority = ProjectContract.CONTENT_AUTHORITY;
+
+ matcher.addURI(authority, "projects", PROJECTS);
+ matcher.addURI(authority, "projects/*", PROJECTS_ID);
+
+ matcher.addURI(authority, "stories", STORIES);
+ matcher.addURI(authority, "stories/*", STORIES_ID);
+
+ matcher.addURI(authority, "members", MEMBERS);
+ matcher.addURI(authority, "members/*", MEMBERS_ID);
+
+ return matcher;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ final int match = sUriMatcher.match(uri);
+ switch (match) {
+ case PROJECTS:
+ return Projects.CONTENT_TYPE;
+ case PROJECTS_ID:
+ return Projects.CONTENT_ITEM_TYPE;
+ case STORIES:
+ return Stories.CONTENT_TYPE;
+ case STORIES_ID:
+ return Stories.CONTENT_ITEM_TYPE;
+ case MEMBERS:
+ return Members.CONTENT_TYPE;
+ case MEMBERS_ID:
+ return Members.CONTENT_ITEM_TYPE;
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ final Context context = getContext();
+ mOpenHelper = new ProjectDatabase(context);
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (LOGV)
+ Log.v(TAG, "query(uri=" + uri + ", proj="
+ + Arrays.toString(projection) + ")");
+ final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+
+ // final int match = uriMatcher_.match(uri);
+
+ // switch(match){
+ // default: {
+ final SelectionBuilder builder = buildSelection(uri);
+ return builder.where(selection, selectionArgs).query(db, projection,
+ sortOrder);
+ // }
+ // }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ if (LOGV)
+ Log.v(TAG, "insert(uri=" + uri + ", values=" + values.toString()
+ + ")");
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final int match = sUriMatcher.match(uri);
+
+ switch (match) {
+ case PROJECTS:
+ db.insertOrThrow(Tables.PROJECTS, null, values);
+ return Projects.buildProjectUri(values
+ .getAsString(Projects.PROJECT_ID));
+ case STORIES:
+ db.insertOrThrow(Tables.STORIES, null, values);
+ return Stories.buildStoryUri(values.getAsString(Stories.STORY_ID));
+ case MEMBERS:
+ db.insertOrThrow(Tables.MEMBERS, null, values);
+ return Members
+ .buildMemberUri(values.getAsString(Members.MEMBER_ID));
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ if (LOGV)
+ Log.v(TAG, "update(uri=" + uri + ", values=" + values.toString()
+ + ")");
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final SelectionBuilder builder = buildSelection(uri);
+ return builder.where(selection, selectionArgs).update(db, values);
+
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (LOGV)
+ Log.v(TAG, "delete(uri=" + uri + ")");
+ final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ final SelectionBuilder builder = buildSelection(uri);
+ return builder.where(selection, selectionArgs).delete(db);
+ }
+
+ private SelectionBuilder buildSelection(Uri uri) {
+ final SelectionBuilder builder = new SelectionBuilder();
+ final int match = sUriMatcher.match(uri);
+ String projectId;
+ String storyId;
+ switch (match) {
+ case PROJECTS:
+ return builder.table(Tables.PROJECTS);
+ case PROJECTS_ID:
+ projectId = Projects.getProjectId(uri);
+ return builder.table(Tables.PROJECTS).where(
+ Projects.PROJECT_ID + "=?", projectId);
+ case STORIES:
+ return builder.table(Tables.STORIES);
+ case STORIES_ID:
+ storyId = Stories.getStoryId(uri);
+ return builder.table(Tables.STORIES).where(
+ Stories.STORY_ID + "=?", storyId);
+ case MEMBERS:
+ return builder.table(Tables.MEMBERS);
+ case MEMBERS_ID:
+ final String memberId = Members.getMemberId(uri);
+ return builder.table(Tables.MEMBERS).where(
+ Members.MEMBER_ID + "=?", memberId);
+ default:
+ throw new UnsupportedOperationException("Unknown uri: " + uri);
+ }
+ }
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/service/.svn/all-wcprops b/src/com/loganlinn/pivotaltrackie/service/.svn/all-wcprops
new file mode 100644
index 0000000..0a7bcf4
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/service/.svn/all-wcprops
@@ -0,0 +1,11 @@
+K 25
+svn:wc:ra_dav:version-url
+V 109
+/svn/!svn/ver/2029/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/service
+END
+SyncService.java
+K 25
+svn:wc:ra_dav:version-url
+V 126
+/svn/!svn/ver/2626/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/service/SyncService.java
+END
diff --git a/src/com/loganlinn/pivotaltrackie/service/.svn/dir-prop-base b/src/com/loganlinn/pivotaltrackie/service/.svn/dir-prop-base
new file mode 100644
index 0000000..95b2379
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/service/.svn/dir-prop-base
@@ -0,0 +1,6 @@
+K 10
+svn:ignore
+V 6
+.git*
+
+END
diff --git a/src/com/loganlinn/pivotaltrackie/service/.svn/entries b/src/com/loganlinn/pivotaltrackie/service/.svn/entries
new file mode 100644
index 0000000..3ffedad
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/service/.svn/entries
@@ -0,0 +1,62 @@
+10
+
+dir
+2086
+https://vtnetapps.googlecode.com/svn/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/service
+https://vtnetapps.googlecode.com/svn
+
+
+
+2010-11-26T19:33:38.466730Z
+2029
+loganlinn
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+13dd798f-c9a9-0d3a-1410-a4e436c6fec4
+
+SyncService.java
+file
+2626
+
+
+
+2010-12-08T15:14:18.000000Z
+1f6b210c59bd48181b9252b04c1c5eb0
+2010-12-10T17:14:25.002194Z
+2626
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7514
+
diff --git a/src/com/loganlinn/pivotaltrackie/service/.svn/text-base/SyncService.java.svn-base b/src/com/loganlinn/pivotaltrackie/service/.svn/text-base/SyncService.java.svn-base
new file mode 100644
index 0000000..9d14bf8
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/service/.svn/text-base/SyncService.java.svn-base
@@ -0,0 +1,221 @@
+package com.loganlinn.pivotaltrackie.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.http.client.HttpClient;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.IntentService;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.loganlinn.pivotaltracker.PivotalTracker;
+import com.loganlinn.pivotaltracker.StoryEntry;
+import com.loganlinn.pivotaltracker.PivotalTracker.Prefs;
+import com.loganlinn.pivotaltrackie.io.RemoteExecutor;
+import com.loganlinn.pivotaltrackie.io.RemoteProjectsHandler;
+import com.loganlinn.pivotaltrackie.io.RemoteStoriesHandler;
+import com.loganlinn.pivotaltrackie.io.XmlHandler;
+import com.loganlinn.pivotaltrackie.io.XmlHandler.HandlerException;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Projects;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Stories;
+import com.loganlinn.pivotaltrackie.util.HttpUtils;
+
+/**
+ * IntentService is a base class for Services that handle asynchronous requests
+ * (expressed as Intents) on demand. Clients send requests through
+ * startService(Intent) calls; the service is started as needed, handles each
+ * Intent in turn using a worker thread, and stops itself when it runs out of
+ * work.
+ *
+ * @author loganlinn
+ *
+ */
+public class SyncService extends IntentService {
+ private static final String TAG = "SyncService";
+ public static final String EXTRA_STATUS_RECEIVER = "com.loganlinn.pivotaltrackie.extra.STATUS_RECEIVER";
+ // public static final String ACTION_SYNC_PROJECTS =
+ // "com.loganlinn.pivotaltrackie.action.SYNC_PROJECTS";
+ public static final int STATUS_RUNNING = 0x1;
+ public static final int STATUS_ERROR = 0x2;
+ public static final int STATUS_FINISHED = 0x3;
+
+ public static final String ACTION_SYNC_PROJECTS = "com.loganlinn.pivotaltrackie.action.SYNC_PROJECTS";
+ public static final String ACTION_SYNC_STORIES = "com.loganlinn.pivotaltrackie.action.SYNC_STORIES";
+ public static final String ACTION_SYNC_STORY = "com.loganlinn.pivotaltrackie.action.SYNC_STORY";
+ public static final String ACTION_DELETE_STORY = "com.loganlinn.pivotaltrackie.action.DELETE_STORY";
+
+ public static final String SEND_TO_API = "SendToAPI";
+ private static final String PROJECTS_URL = "http://www.pivotaltracker.com/services/v3/projects";
+
+ private RemoteExecutor mRemoteExecutor;
+
+ public SyncService() {
+ super(TAG);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final HttpClient httpClient = HttpUtils.getHttpClient(this);
+ final ContentResolver resolver = getContentResolver();
+
+ // localExecutor_ = new LocalExecutor(getResources(), resolver);
+ mRemoteExecutor = new RemoteExecutor(httpClient, resolver);
+ }
+
+ private boolean connectionAvailable() {
+
+ ConnectivityManager cm = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE));
+ final boolean network = cm.getNetworkInfo(
+ ConnectivityManager.TYPE_MOBILE).isAvailable();
+ final boolean wifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
+ .isAvailable();
+
+ return network | wifi | true;
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.d(TAG, "onHandleIntent(intent=" + intent.toString() + ")");
+ final ResultReceiver receiver = intent
+ .getParcelableExtra(EXTRA_STATUS_RECEIVER);
+ if (receiver != null)
+ receiver.send(STATUS_RUNNING, Bundle.EMPTY);
+ final SharedPreferences prefs = getSharedPreferences(Prefs.NAME,
+ Context.MODE_PRIVATE);
+ final Context context = this;
+ Bundle returnData = Bundle.EMPTY;
+
+ if (connectionAvailable()) {
+
+ try {
+ final long startRemote = System.currentTimeMillis();
+ final String action = intent.getAction();
+
+ if (ACTION_SYNC_PROJECTS.equals(action)) {
+ mRemoteExecutor.executeGet(PROJECTS_URL,
+ new RemoteProjectsHandler(mRemoteExecutor));
+ } else if (ACTION_SYNC_STORIES.equals(action)) {
+
+ final String projectId = Projects.getProjectId(intent
+ .getData());
+ Log.i(TAG, "Fetching stories for project_id=" + projectId);
+ mRemoteExecutor.executeGet(PivotalTracker
+ .getStoriesUrl(projectId),
+ new RemoteStoriesHandler(mRemoteExecutor));
+
+ } else if (ACTION_SYNC_STORY.equals(action)) {
+ final Bundle intentData = intent.getExtras();
+ if (intentData != null) {
+
+ getContentResolver().delete(Stories.CONTENT_URI, null,
+ null);
+ final RemoteStoriesHandler handler = new RemoteStoriesHandler(
+ mRemoteExecutor);
+
+ final int projectId = intent.getIntExtra(
+ Stories.PROJECT_ID, -1);
+ final int storyId = intent.getIntExtra(
+ Stories.STORY_ID, -1);
+ Log.i(TAG, "Syncing story with id = "
+ + String.valueOf(storyId));
+ if (intentData.getBoolean(SEND_TO_API, false)) {
+ final StoryEntry story = StoryEntry
+ .fromBundle(intentData);
+
+ if (story.get(Stories.STORY_ID) == String
+ .valueOf(Stories.NEW_STORY)) { // add the
+ // story
+ Log.i(TAG, "Adding the story");
+ mRemoteExecutor.execute(PivotalTracker
+ .getPostRequest(story), handler);
+ } else { // update story
+ Log.i(TAG, "Updating the story");
+ mRemoteExecutor.execute(PivotalTracker
+ .getPutRequest(story), handler);
+ }
+ } else if (storyId != -1 && projectId != -1) {
+ handler.setContentUri(Stories
+ .buildStoryUri(storyId));
+
+ mRemoteExecutor.executeGet(PivotalTracker
+ .getStoryUrl(projectId, storyId), handler);
+ }
+ }
+
+ } else if (ACTION_DELETE_STORY.equals(action)) {
+
+ final int projectId = intent.getIntExtra(
+ Stories.PROJECT_ID, -5);
+ final int storyId = intent.getIntExtra(Stories.STORY_ID,
+ Stories.NEW_STORY);
+
+ if (projectId > 0 && storyId > 0) {
+ Log.i(TAG, "Deleting story " + storyId);
+ mRemoteExecutor.execute(PivotalTracker
+ .getDeleteStoryRequest(projectId, storyId),
+ new XmlHandler(
+ ProjectContract.CONTENT_AUTHORITY) {
+
+ @Override
+ public ArrayList parse(
+ XmlPullParser parser,
+ ContentResolver resolver)
+ throws XmlPullParserException,
+ IOException {
+ final ArrayList batch = new ArrayList();
+ return batch;
+ }
+
+ });
+ }
+
+ }
+
+ Log.d(TAG, "Remote sync took "
+ + (System.currentTimeMillis() - startRemote)
+ + "ms for " + action);
+
+ } catch (HandlerException e) {
+
+ } catch (Exception e) {
+
+ Log.e(TAG, "Problem while syncing", e);
+
+ if (receiver != null) {
+ // Pass back error to surface listener
+ final Bundle bundle = new Bundle();
+ bundle.putString(Intent.EXTRA_TEXT, e.toString());
+ receiver.send(STATUS_ERROR, bundle);
+ } else {
+ Log.d(TAG, "No receiver found");
+ }
+ }
+ }else{
+ //we do not have connection
+ if(receiver != null){
+ receiver.send(STATUS_ERROR, returnData);
+ }
+ }
+ // Announce success to any surface listener
+ Log.d(TAG, "sync finished");
+ if (receiver != null)
+ receiver.send(STATUS_FINISHED, returnData);
+ }
+
+ private void syncProjects() {
+
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/service/SyncService.java b/src/com/loganlinn/pivotaltrackie/service/SyncService.java
new file mode 100644
index 0000000..9d14bf8
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/service/SyncService.java
@@ -0,0 +1,221 @@
+package com.loganlinn.pivotaltrackie.service;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.http.client.HttpClient;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.IntentService;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.util.Log;
+
+import com.loganlinn.pivotaltracker.PivotalTracker;
+import com.loganlinn.pivotaltracker.StoryEntry;
+import com.loganlinn.pivotaltracker.PivotalTracker.Prefs;
+import com.loganlinn.pivotaltrackie.io.RemoteExecutor;
+import com.loganlinn.pivotaltrackie.io.RemoteProjectsHandler;
+import com.loganlinn.pivotaltrackie.io.RemoteStoriesHandler;
+import com.loganlinn.pivotaltrackie.io.XmlHandler;
+import com.loganlinn.pivotaltrackie.io.XmlHandler.HandlerException;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Projects;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Stories;
+import com.loganlinn.pivotaltrackie.util.HttpUtils;
+
+/**
+ * IntentService is a base class for Services that handle asynchronous requests
+ * (expressed as Intents) on demand. Clients send requests through
+ * startService(Intent) calls; the service is started as needed, handles each
+ * Intent in turn using a worker thread, and stops itself when it runs out of
+ * work.
+ *
+ * @author loganlinn
+ *
+ */
+public class SyncService extends IntentService {
+ private static final String TAG = "SyncService";
+ public static final String EXTRA_STATUS_RECEIVER = "com.loganlinn.pivotaltrackie.extra.STATUS_RECEIVER";
+ // public static final String ACTION_SYNC_PROJECTS =
+ // "com.loganlinn.pivotaltrackie.action.SYNC_PROJECTS";
+ public static final int STATUS_RUNNING = 0x1;
+ public static final int STATUS_ERROR = 0x2;
+ public static final int STATUS_FINISHED = 0x3;
+
+ public static final String ACTION_SYNC_PROJECTS = "com.loganlinn.pivotaltrackie.action.SYNC_PROJECTS";
+ public static final String ACTION_SYNC_STORIES = "com.loganlinn.pivotaltrackie.action.SYNC_STORIES";
+ public static final String ACTION_SYNC_STORY = "com.loganlinn.pivotaltrackie.action.SYNC_STORY";
+ public static final String ACTION_DELETE_STORY = "com.loganlinn.pivotaltrackie.action.DELETE_STORY";
+
+ public static final String SEND_TO_API = "SendToAPI";
+ private static final String PROJECTS_URL = "http://www.pivotaltracker.com/services/v3/projects";
+
+ private RemoteExecutor mRemoteExecutor;
+
+ public SyncService() {
+ super(TAG);
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final HttpClient httpClient = HttpUtils.getHttpClient(this);
+ final ContentResolver resolver = getContentResolver();
+
+ // localExecutor_ = new LocalExecutor(getResources(), resolver);
+ mRemoteExecutor = new RemoteExecutor(httpClient, resolver);
+ }
+
+ private boolean connectionAvailable() {
+
+ ConnectivityManager cm = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE));
+ final boolean network = cm.getNetworkInfo(
+ ConnectivityManager.TYPE_MOBILE).isAvailable();
+ final boolean wifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
+ .isAvailable();
+
+ return network | wifi | true;
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.d(TAG, "onHandleIntent(intent=" + intent.toString() + ")");
+ final ResultReceiver receiver = intent
+ .getParcelableExtra(EXTRA_STATUS_RECEIVER);
+ if (receiver != null)
+ receiver.send(STATUS_RUNNING, Bundle.EMPTY);
+ final SharedPreferences prefs = getSharedPreferences(Prefs.NAME,
+ Context.MODE_PRIVATE);
+ final Context context = this;
+ Bundle returnData = Bundle.EMPTY;
+
+ if (connectionAvailable()) {
+
+ try {
+ final long startRemote = System.currentTimeMillis();
+ final String action = intent.getAction();
+
+ if (ACTION_SYNC_PROJECTS.equals(action)) {
+ mRemoteExecutor.executeGet(PROJECTS_URL,
+ new RemoteProjectsHandler(mRemoteExecutor));
+ } else if (ACTION_SYNC_STORIES.equals(action)) {
+
+ final String projectId = Projects.getProjectId(intent
+ .getData());
+ Log.i(TAG, "Fetching stories for project_id=" + projectId);
+ mRemoteExecutor.executeGet(PivotalTracker
+ .getStoriesUrl(projectId),
+ new RemoteStoriesHandler(mRemoteExecutor));
+
+ } else if (ACTION_SYNC_STORY.equals(action)) {
+ final Bundle intentData = intent.getExtras();
+ if (intentData != null) {
+
+ getContentResolver().delete(Stories.CONTENT_URI, null,
+ null);
+ final RemoteStoriesHandler handler = new RemoteStoriesHandler(
+ mRemoteExecutor);
+
+ final int projectId = intent.getIntExtra(
+ Stories.PROJECT_ID, -1);
+ final int storyId = intent.getIntExtra(
+ Stories.STORY_ID, -1);
+ Log.i(TAG, "Syncing story with id = "
+ + String.valueOf(storyId));
+ if (intentData.getBoolean(SEND_TO_API, false)) {
+ final StoryEntry story = StoryEntry
+ .fromBundle(intentData);
+
+ if (story.get(Stories.STORY_ID) == String
+ .valueOf(Stories.NEW_STORY)) { // add the
+ // story
+ Log.i(TAG, "Adding the story");
+ mRemoteExecutor.execute(PivotalTracker
+ .getPostRequest(story), handler);
+ } else { // update story
+ Log.i(TAG, "Updating the story");
+ mRemoteExecutor.execute(PivotalTracker
+ .getPutRequest(story), handler);
+ }
+ } else if (storyId != -1 && projectId != -1) {
+ handler.setContentUri(Stories
+ .buildStoryUri(storyId));
+
+ mRemoteExecutor.executeGet(PivotalTracker
+ .getStoryUrl(projectId, storyId), handler);
+ }
+ }
+
+ } else if (ACTION_DELETE_STORY.equals(action)) {
+
+ final int projectId = intent.getIntExtra(
+ Stories.PROJECT_ID, -5);
+ final int storyId = intent.getIntExtra(Stories.STORY_ID,
+ Stories.NEW_STORY);
+
+ if (projectId > 0 && storyId > 0) {
+ Log.i(TAG, "Deleting story " + storyId);
+ mRemoteExecutor.execute(PivotalTracker
+ .getDeleteStoryRequest(projectId, storyId),
+ new XmlHandler(
+ ProjectContract.CONTENT_AUTHORITY) {
+
+ @Override
+ public ArrayList parse(
+ XmlPullParser parser,
+ ContentResolver resolver)
+ throws XmlPullParserException,
+ IOException {
+ final ArrayList batch = new ArrayList();
+ return batch;
+ }
+
+ });
+ }
+
+ }
+
+ Log.d(TAG, "Remote sync took "
+ + (System.currentTimeMillis() - startRemote)
+ + "ms for " + action);
+
+ } catch (HandlerException e) {
+
+ } catch (Exception e) {
+
+ Log.e(TAG, "Problem while syncing", e);
+
+ if (receiver != null) {
+ // Pass back error to surface listener
+ final Bundle bundle = new Bundle();
+ bundle.putString(Intent.EXTRA_TEXT, e.toString());
+ receiver.send(STATUS_ERROR, bundle);
+ } else {
+ Log.d(TAG, "No receiver found");
+ }
+ }
+ }else{
+ //we do not have connection
+ if(receiver != null){
+ receiver.send(STATUS_ERROR, returnData);
+ }
+ }
+ // Announce success to any surface listener
+ Log.d(TAG, "sync finished");
+ if (receiver != null)
+ receiver.send(STATUS_FINISHED, returnData);
+ }
+
+ private void syncProjects() {
+
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/ui/.svn/all-wcprops b/src/com/loganlinn/pivotaltrackie/ui/.svn/all-wcprops
new file mode 100644
index 0000000..32127bc
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/ui/.svn/all-wcprops
@@ -0,0 +1,47 @@
+K 25
+svn:wc:ra_dav:version-url
+V 104
+/svn/!svn/ver/2029/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui
+END
+XMLTokenHandler.java
+K 25
+svn:wc:ra_dav:version-url
+V 125
+/svn/!svn/ver/2587/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/XMLTokenHandler.java
+END
+ProjectActivity.java
+K 25
+svn:wc:ra_dav:version-url
+V 125
+/svn/!svn/ver/2626/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/ProjectActivity.java
+END
+EditStoryActivity.java
+K 25
+svn:wc:ra_dav:version-url
+V 127
+/svn/!svn/ver/2626/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/EditStoryActivity.java
+END
+HomeActivity.java
+K 25
+svn:wc:ra_dav:version-url
+V 122
+/svn/!svn/ver/2588/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/HomeActivity.java
+END
+LoginActivity.java
+K 25
+svn:wc:ra_dav:version-url
+V 123
+/svn/!svn/ver/2183/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/LoginActivity.java
+END
+ViewStoryActivity.java
+K 25
+svn:wc:ra_dav:version-url
+V 127
+/svn/!svn/ver/2588/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/ViewStoryActivity.java
+END
+ActivityState.java
+K 25
+svn:wc:ra_dav:version-url
+V 123
+/svn/!svn/ver/2183/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui/ActivityState.java
+END
diff --git a/src/com/loganlinn/pivotaltrackie/ui/.svn/entries b/src/com/loganlinn/pivotaltrackie/ui/.svn/entries
new file mode 100644
index 0000000..79cacf4
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/ui/.svn/entries
@@ -0,0 +1,314 @@
+10
+
+dir
+2086
+https://vtnetapps.googlecode.com/svn/trunk/assignments/logan_linn/final/PivotalTrackie/src/com/loganlinn/pivotaltrackie/ui
+https://vtnetapps.googlecode.com/svn
+
+
+
+2010-11-26T19:33:38.466730Z
+2029
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+13dd798f-c9a9-0d3a-1410-a4e436c6fec4
+
+XMLTokenHandler.java
+file
+2587
+
+
+
+2010-12-06T05:12:11.000000Z
+06df2ee80c8675c8524b455a3538c2b0
+2010-12-07T22:40:29.935924Z
+2587
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2028
+
+ProjectActivity.java
+file
+2626
+
+
+
+2010-12-08T14:25:35.000000Z
+8a79c577e8e9835e61a56fc9b536abf4
+2010-12-10T17:14:25.002194Z
+2626
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12169
+
+EditStoryActivity.java
+file
+2626
+
+
+
+2010-12-08T14:49:26.000000Z
+b03154886250de7d0044e8560c5c124b
+2010-12-10T17:14:25.002194Z
+2626
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10785
+
+HomeActivity.java
+file
+2588
+
+
+
+2010-12-07T22:44:02.000000Z
+79e0f5e563b083c45316dbf0442abd3f
+2010-12-08T00:10:34.757519Z
+2588
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8929
+
+LoginActivity.java
+file
+2183
+
+
+
+2010-12-02T03:23:53.000000Z
+23d3ba5e7e173afa6413ac9d188c5ca5
+2010-12-02T04:08:08.804433Z
+2183
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1741
+
+ViewStoryActivity.java
+file
+2588
+
+
+
+2010-12-07T23:54:09.000000Z
+2a94362d8e31f9d7b5930a10bcfb45de
+2010-12-08T00:10:34.757519Z
+2588
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7710
+
+ActivityState.java
+file
+2183
+
+
+
+2010-12-02T01:01:23.000000Z
+68003816ccbb4a3dea04fe44efaa34de
+2010-12-02T04:08:08.804433Z
+2183
+loganlinn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+322
+
+StoryActivity.java
+file
+2183
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+deleted
+
+PreferencesActivity.java
+file
+2183
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+deleted
+
diff --git a/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/ActivityState.java.svn-base b/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/ActivityState.java.svn-base
new file mode 100644
index 0000000..8fe607e
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/ActivityState.java.svn-base
@@ -0,0 +1,14 @@
+package com.loganlinn.pivotaltrackie.ui;
+
+import android.os.Handler;
+
+import com.loganlinn.pivotaltrackie.util.DetachableResultReceiver;
+
+class ActivityState {
+ public DetachableResultReceiver mReceiver;
+ public boolean mSyncing = false;
+
+ ActivityState() {
+ mReceiver = new DetachableResultReceiver(new Handler());
+ }
+}
diff --git a/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/EditStoryActivity.java.svn-base b/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/EditStoryActivity.java.svn-base
new file mode 100644
index 0000000..0d5bad4
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/EditStoryActivity.java.svn-base
@@ -0,0 +1,316 @@
+package com.loganlinn.pivotaltrackie.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.loganlinn.pivotaltracker.StoryEntry;
+import com.loganlinn.pivotaltracker.Member.DefaultMemberQuery;
+import com.loganlinn.pivotaltrackie.R;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Members;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Stories;
+import com.loganlinn.pivotaltrackie.service.SyncService;
+import com.loganlinn.pivotaltrackie.util.DetachableResultReceiver;
+import com.loganlinn.pivotaltrackie.util.NotifyingAsyncQueryHandler;
+import com.loganlinn.pivotaltrackie.util.SelectionBuilder;
+import com.loganlinn.pivotaltrackie.util.NotifyingAsyncQueryHandler.AsyncQueryListener;
+
+//import com.loganlinn.pivotaltrackie.provider.ProjectContract;
+public class EditStoryActivity extends Activity implements AsyncQueryListener,
+ DetachableResultReceiver.Receiver {
+ private static final String TAG = "EditStoryActivity";
+
+ private Spinner mEstimateSpinner;
+ private Spinner mStoryTypeSpinner;
+ private Spinner mRequestedBySpinner;
+ private Spinner mOwnedBySpinner;
+ private EditText mLabelsText;
+ private EditText mDescriptionText;
+ private EditText mNameText;
+ private TextView mTitle;
+ private int mProjectId;
+ private int mStoryId;
+ private StoryEntry mStoryEntry = null;
+
+ private ArrayList mMembers;
+ private ActivityState mState;
+ private ContentResolver mContentResolver;
+ private NotifyingAsyncQueryHandler mQueryHandler;
+ private ArrayAdapter mMemberListAdapter;
+ private ArrayAdapter mEstimateAdapter;
+ private ArrayAdapter mStoryTypeAdapter;
+ private ArrayList mMemberList;
+ private ArrayList mEstimateList;
+ private ArrayList mStoryTypeList;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_edit_story);
+
+ // Check for previous state
+ mState = (ActivityState) getLastNonConfigurationInstance();
+
+ final boolean previousState = (mState != null);
+
+ if (previousState) {
+ mState.mReceiver.setReceiver(this);
+ } else {
+ mState = new ActivityState();
+ mState.mReceiver.setReceiver(this);
+ }
+
+ mContentResolver = getContentResolver();
+ mQueryHandler = new NotifyingAsyncQueryHandler(mContentResolver, this);
+
+ mStoryId = getIntent().getIntExtra(Stories.STORY_ID, Stories.NEW_STORY);
+ mProjectId = getIntent().getIntExtra(Stories.PROJECT_ID, -5);
+ Log.i(TAG, "Editing story "+mStoryId+" in project "+mProjectId);
+ final Bundle bundleData = getIntent().getExtras();
+ if (bundleData != null) {
+ mStoryEntry = StoryEntry.fromBundle(bundleData);
+ mStoryEntry.sanitizeNullValues();
+ Log.i(TAG, mStoryEntry.toString());
+
+ }
+
+ // TODO: get point scheme from project
+ final int estimateValues = R.array.estimate123;
+ final int spinnerItem = android.R.layout.simple_spinner_item;
+ final Resources resources = this.getResources();
+
+ mEstimateList = new ArrayList(Arrays.asList(resources
+ .getStringArray(estimateValues)));
+ mStoryTypeList = new ArrayList(Arrays.asList(resources
+ .getStringArray(R.array.story_types)));
+ mMemberList = new ArrayList();
+ mMemberList.add(""); // add empty value
+
+ mEstimateAdapter = ArrayAdapter.createFromResource(this,
+ estimateValues, spinnerItem);
+ mStoryTypeAdapter = ArrayAdapter.createFromResource(this,
+ R.array.story_types, spinnerItem);
+
+ mMemberListAdapter = new ArrayAdapter(this, spinnerItem, mMemberList);
+
+ mStoryTypeAdapter
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mEstimateAdapter
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mMemberListAdapter
+ .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ mEstimateSpinner = (Spinner) findViewById(R.id.sp_estimate);
+ mEstimateSpinner.setAdapter(mEstimateAdapter);
+ mStoryTypeSpinner = (Spinner) findViewById(R.id.sp_story_type);
+ mStoryTypeSpinner.setAdapter(mStoryTypeAdapter);
+
+ mRequestedBySpinner = (Spinner) findViewById(R.id.sp_requested_by);
+ mRequestedBySpinner.setAdapter(mMemberListAdapter);
+ mOwnedBySpinner = (Spinner) findViewById(R.id.sp_owned_by);
+ mOwnedBySpinner.setAdapter(mMemberListAdapter);
+
+ mLabelsText = (EditText) findViewById(R.id.et_labels);
+ mDescriptionText = (EditText) findViewById(R.id.et_description);
+ mNameText = (EditText) findViewById(R.id.et_story_name);
+
+ // When creating a new story, show a clear button instead of delete
+ // button
+ mTitle = (TextView) findViewById(R.id.tv_title_bar_title);
+ if (mStoryId == Stories.NEW_STORY) {
+ mTitle.setText(R.string.add_story);
+ ImageButton clearButton = (ImageButton) findViewById(R.id.ic_title_cancel);
+ ImageButton deleteButton = (ImageButton) findViewById(R.id.ic_title_delete);
+ deleteButton.setVisibility(ImageButton.INVISIBLE);
+ clearButton.setVisibility(ImageButton.VISIBLE);
+ ImageView delSeparator = (ImageView) findViewById(R.id.iv_delete_sep);
+ ImageView clrSeparator = (ImageView) findViewById(R.id.iv_clear_sep);
+ delSeparator.setVisibility(ImageView.INVISIBLE);
+ clrSeparator.setVisibility(ImageView.INVISIBLE);
+
+
+
+ } else {
+ mTitle.setText(R.string.edit_story);
+ displayData();
+ }
+
+ }
+
+ public void setSpinnerByValue(String value, List list, Spinner spinner) {
+ int position = list.indexOf(value);
+ if (position == -1 && "".equals(value) == false) {
+ Log.i(TAG, value + " not in list, adding & moving spinner to it");
+ Log.i(TAG, list.toString());
+ Log.i(TAG, spinner.toString());
+
+ position = list.size();
+ list.add(value);
+ }
+ try{
+ spinner.setSelection(position);
+ }catch(java.lang.IndexOutOfBoundsException e){
+ e.printStackTrace();
+ }
+ }
+
+ public void onCancelClick(View v) {
+ finish();
+ }
+
+ public void onSaveClick(View v) {
+ Log.i(TAG, "Saving story for project " + mProjectId);
+
+ stageData();
+ Intent intent = new Intent(SyncService.ACTION_SYNC_STORY, null, this,
+ SyncService.class);
+
+ Bundle storyData = mStoryEntry.toBundle();
+ storyData.putBoolean(SyncService.SEND_TO_API, true);
+ intent.putExtras(storyData);
+ intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, mState.mReceiver);
+ intent.putExtra(Stories.PROJECT_ID, mProjectId);
+ intent.putExtra(Stories.STORY_ID, mStoryId);
+ startService(intent);
+ finish();
+ }
+
+ public void onDeleteClick(View v) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage("Are you sure you want to delete?").setCancelable(
+ false).setPositiveButton("Yes",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ Intent intent = new Intent(
+ SyncService.ACTION_DELETE_STORY, null, EditStoryActivity.this,
+ SyncService.class);
+
+ intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER,
+ mState.mReceiver);
+ intent.putExtra(Stories.PROJECT_ID, mProjectId);
+ intent.putExtra(Stories.STORY_ID, mStoryId);
+
+ startService(intent);
+
+ EditStoryActivity.this.finish();
+ }
+ }).setNegativeButton("No",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ public void stageData() {
+ mStoryEntry.put(Stories.STORY_ID, String.valueOf(mStoryId));
+ mStoryEntry.put(Stories.NAME, mNameText.getText().toString());
+ mStoryEntry.put(Stories.DESCRIPTION, mDescriptionText.getText()
+ .toString());
+ mStoryEntry.put(Stories.LABELS, mLabelsText.getText().toString());
+ String estimate = ((CharSequence) mEstimateSpinner
+ .getSelectedItem()).toString();
+ estimate = estimate.replace(" points", "");
+ mStoryEntry.put(Stories.ESTIMATE, estimate);
+ mStoryEntry.put(Stories.STORY_TYPE, ((CharSequence) mStoryTypeSpinner
+ .getSelectedItem()).toString());
+ mStoryEntry.put(Stories.OWNED_BY, ((CharSequence) mOwnedBySpinner
+ .getSelectedItem()).toString());
+ mStoryEntry.put(Stories.REQUESTED_BY,
+ ((CharSequence) mRequestedBySpinner.getSelectedItem())
+ .toString());
+ }
+
+ public void displayData() {
+
+ mNameText.setText(mStoryEntry.get(Stories.NAME));
+ mDescriptionText.setText(mStoryEntry.get(Stories.DESCRIPTION));
+ mLabelsText.setText(mStoryEntry.get(Stories.LABELS));
+ setSpinnerByValue(mStoryEntry.get(Stories.REQUESTED_BY), mMemberList,
+ mRequestedBySpinner);
+ setSpinnerByValue(mStoryEntry.get(Stories.OWNED_BY), mMemberList,
+ mOwnedBySpinner);
+
+ String estimate = mStoryEntry.get(Stories.ESTIMATE);
+ if(estimate.length() == 0){
+ estimate = "0 points";
+ }else{
+ estimate += " points";
+ }
+ Log.i(TAG, "ESTIMATE: "+estimate);
+
+ setSpinnerByValue(estimate, mEstimateList,
+ mEstimateSpinner);
+ setSpinnerByValue(mStoryEntry.get(Stories.STORY_TYPE), mStoryTypeList,
+ mStoryTypeSpinner);
+
+ }
+
+ public void queryMembers() {
+ final Uri membersUri = Members.CONTENT_URI;
+ final SelectionBuilder builder = new SelectionBuilder();
+ builder.where(Members.PROJECT_ID + "=?", String.valueOf(mProjectId));
+ mQueryHandler.startQuery(membersUri, DefaultMemberQuery.PROJECTION);
+ }
+
+ @Override
+ public void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+
+ mMembers.add(cursor.getString(DefaultMemberQuery.NAME));
+
+ } while (cursor.moveToNext());
+ }
+ displayData();
+ }
+
+ @Override
+ public void onReceiveResult(int resultCode, Bundle resultData) {
+ Log.v(TAG, "Received Result code=" + resultCode);
+ Log.v(TAG, resultData.keySet().toString());
+
+ switch (resultCode) {
+ case SyncService.STATUS_RUNNING: {
+ mState.mSyncing = true;
+ break;
+ }
+ case SyncService.STATUS_FINISHED: {
+ mState.mSyncing = false;
+ Toast.makeText(EditStoryActivity.this, "Save sucessful",
+ Toast.LENGTH_LONG).show();
+ break;
+ }
+ case SyncService.STATUS_ERROR: {
+ mState.mSyncing = false;
+ final String errorText = getString(R.string.toast_sync_error,
+ resultData.getStringArray(Intent.EXTRA_TEXT));
+ Toast
+ .makeText(EditStoryActivity.this, errorText,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+}
diff --git a/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/HomeActivity.java.svn-base b/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/HomeActivity.java.svn-base
new file mode 100644
index 0000000..864b537
--- /dev/null
+++ b/src/com/loganlinn/pivotaltrackie/ui/.svn/text-base/HomeActivity.java.svn-base
@@ -0,0 +1,308 @@
+package com.loganlinn.pivotaltrackie.ui;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.loganlinn.pivotaltracker.PivotalTracker;
+import com.loganlinn.pivotaltracker.Project.DefaultProjectsQuery;
+import com.loganlinn.pivotaltrackie.R;
+import com.loganlinn.pivotaltrackie.provider.ProjectContract.Projects;
+import com.loganlinn.pivotaltrackie.service.SyncService;
+import com.loganlinn.pivotaltrackie.util.DateTimeUtils;
+import com.loganlinn.pivotaltrackie.util.DetachableResultReceiver;
+import com.loganlinn.pivotaltrackie.util.NotifyingAsyncQueryHandler;
+import com.loganlinn.pivotaltrackie.util.NotifyingAsyncQueryHandler.AsyncQueryListener;
+
+public class HomeActivity extends Activity implements AsyncQueryListener,
+ DetachableResultReceiver.Receiver, OnItemClickListener {
+ private static final String TAG = "HomeActivity";
+
+ private ActivityState mState;
+ private Handler mMessageHandler = new Handler();
+ private NotifyingAsyncQueryHandler mQueryHandler;
+
+ public ProjectListAdapter mListAdapter;
+ protected ListView mListView;
+ protected List