diff --git a/MyTracks/res/layout/track_list_header.xml b/MyTracks/res/layout/track_list_header.xml new file mode 100644 index 000000000..814644b0a --- /dev/null +++ b/MyTracks/res/layout/track_list_header.xml @@ -0,0 +1,25 @@ + + + diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index 21934680e..e3f8b85be 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -2424,7 +2424,12 @@ limitations under the License. success."> Now recording a new track. - + + Shared with me + + diff --git a/MyTracks/src/com/google/android/apps/mytracks/SectionResourceCursorAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/SectionResourceCursorAdapter.java new file mode 100644 index 000000000..d350287e0 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/SectionResourceCursorAdapter.java @@ -0,0 +1,168 @@ +/* + * Copyright 2013 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.google.android.apps.mytracks; + +import com.google.android.apps.mytracks.content.TracksColumns; +import com.google.android.maps.mytracks.R; + +import android.content.Context; +import android.database.Cursor; +import android.database.DataSetObserver; +import android.support.v4.widget.ResourceCursorAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +/** + * A section based resource cursor adapter. Currently only supports one + * additional section header, "Shared with me". + * + * @author Jimmy Shih + */ +public abstract class SectionResourceCursorAdapter extends ResourceCursorAdapter { + + private enum ItemType { + TRACK, HEADER + } + + private final LayoutInflater layoutInflater; + private int sharedWithMeIndex = -1; + + public SectionResourceCursorAdapter(Context context, int layout, Cursor cursor, int flags) { + super(context, layout, cursor, flags); + this.layoutInflater = LayoutInflater.from(context); + + if (cursor != null) { + sharedWithMeIndex = getSharedWithMeIndex(); + cursor.registerDataSetObserver(dataSetObserver); + } + } + + private DataSetObserver dataSetObserver = new DataSetObserver() { + @Override + public void onChanged() { + sharedWithMeIndex = getSharedWithMeIndex(); + }; + + @Override + public void onInvalidated() { + sharedWithMeIndex = -1; + }; + }; + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (getItemViewType(position) == ItemType.TRACK.ordinal()) { + return super.getView(getCursorPosition(position), convertView, parent); + } else { + TextView textView; + if (convertView == null) { + textView = (TextView) layoutInflater.inflate(R.layout.track_list_header, parent, false); + } else { + textView = (TextView) convertView; + } + return textView; + } + } + + @Override + public int getViewTypeCount() { + return ItemType.values().length; + } + + @Override + public int getCount() { + int count = super.getCount(); + return count != 0 && sharedWithMeIndex != -1 ? count + 1 : count; + } + + @Override + public boolean isEnabled(int position) { + return getItemViewType(position) == ItemType.TRACK.ordinal(); + } + + @Override + public Object getItem(int position) { + if (getItemViewType(position) == ItemType.TRACK.ordinal()) { + return super.getItem(getCursorPosition(position)); + } + return null; + } + + @Override + public long getItemId(int position) { + if (getItemViewType(position) == ItemType.TRACK.ordinal()) { + return super.getItemId(getCursorPosition(position)); + } + return -1L; + } + + @Override + public int getItemViewType(int position) { + if (sharedWithMeIndex != -1 && sharedWithMeIndex == position) { + return ItemType.HEADER.ordinal(); + } + return ItemType.TRACK.ordinal(); + } + + @Override + public Cursor swapCursor(Cursor newCursor) { + if (getCursor() != null) { + getCursor().unregisterDataSetObserver(dataSetObserver); + } + + Cursor oldCursor = super.swapCursor(newCursor); + if (newCursor != null) { + sharedWithMeIndex = getSharedWithMeIndex(); + newCursor.registerDataSetObserver(dataSetObserver); + } + return oldCursor; + } + + /** + * Gets the Shared with me header index. + */ + private int getSharedWithMeIndex() { + int i = 0; + Cursor cursor = getCursor(); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndex(TracksColumns.SHAREDWITHME); + do { + int sharedWithMe = cursor.getInt(index); + if (sharedWithMe == 1) { + // Returns the index where sharedWithMe == 1 + return i; + } + i++; + } while (cursor.moveToNext()); + } + return -1; + } + + /** + * Gets the underlying cursor position. + * + * @param position the requested position + */ + private int getCursorPosition(int position) { + if (sharedWithMeIndex != -1 && position > sharedWithMeIndex) { + return position - 1; + } + return position; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java index 31eba1360..866a16e43 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java @@ -66,7 +66,6 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.support.v4.widget.ResourceCursorAdapter; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -97,7 +96,8 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac private static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 0; private static final String[] PROJECTION = new String[] { TracksColumns._ID, TracksColumns.NAME, TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME, - TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON }; + TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON, + TracksColumns.SHAREDWITHME}; // Callback when the trackRecordingServiceConnection binding changes. private final Runnable bindChangedCallback = new Runnable() { @@ -180,7 +180,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; updateMenuItems(isRecording); - resourceCursorAdapter.notifyDataSetChanged(); + sectionResourceCursorAdapter.notifyDataSetChanged(); trackController.update(isRecording, recordingTrackPaused); } }); @@ -333,7 +333,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac private TrackRecordingServiceConnection trackRecordingServiceConnection; private TrackController trackController; private ListView listView; - private ResourceCursorAdapter resourceCursorAdapter; + private SectionResourceCursorAdapter sectionResourceCursorAdapter; private TrackDataHub trackDataHub; // Preferences @@ -379,7 +379,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac startActivity(intent); } }); - resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { + sectionResourceCursorAdapter = new SectionResourceCursorAdapter(this, R.layout.list_item, null, 0) { @Override public void bindView(View view, Context context, Cursor cursor) { int idIndex = cursor.getColumnIndex(TracksColumns._ID); @@ -406,7 +406,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac totalDistance, startTimeDisplay, cursor.getString(descriptionIndex)); } }; - listView.setAdapter(resourceCursorAdapter); + listView.setAdapter(sectionResourceCursorAdapter); ApiAdapterFactory.getApiAdapter() .configureListViewContextualMenu(this, listView, contextualActionModeCallback); @@ -414,17 +414,17 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac @Override public Loader onCreateLoader(int arg0, Bundle arg1) { return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null, - null, TracksColumns._ID + " DESC"); + null, TracksColumns.SHAREDWITHME + " ASC, " + TracksColumns._ID + " DESC"); } @Override public void onLoadFinished(Loader loader, Cursor cursor) { - resourceCursorAdapter.swapCursor(cursor); + sectionResourceCursorAdapter.swapCursor(cursor); } @Override public void onLoaderReset(Loader loader) { - resourceCursorAdapter.swapCursor(null); + sectionResourceCursorAdapter.swapCursor(null); } }); trackDataHub = TrackDataHub.newInstance(this); @@ -462,7 +462,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac // Update UI boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; updateMenuItems(isRecording); - resourceCursorAdapter.notifyDataSetChanged(); + sectionResourceCursorAdapter.notifyDataSetChanged(); trackController.update(isRecording, recordingTrackPaused); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java index a01f4a3ef..f3d9ffac2 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sync/SyncAdapter.java @@ -161,14 +161,18 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter { context, R.string.drive_deleted_list_key, PreferencesUtils.DRIVE_DELETED_LIST_DEFAULT); String deletedIds[] = TextUtils.split(driveDeletedList, ";"); for (String id : deletedIds) { - File driveFile = drive.files().get(id).execute(); - if (SyncUtils.isInFolder(driveFile, folderId)) { - if (!driveFile.getLabels().getTrashed()) { - drive.files().trash(id).execute(); + try { + File driveFile = drive.files().get(id).execute(); + if (SyncUtils.isInFolder(driveFile, folderId)) { + if (!driveFile.getLabels().getTrashed()) { + drive.files().trash(id).execute(); + } + // if trashed, ignore + } else if (SyncUtils.isSharedWithMe(driveFile)) { + drive.files().delete(id).execute(); } - // if trashed, ignore - } else if (SyncUtils.isSharedWithMe(driveFile)) { - drive.files().delete(id).execute(); + } catch (IOException e) { + // safe to ignore } } PreferencesUtils.setString(