diff --git a/MyTracks/res/menu/track_list_context_menu.xml b/MyTracks/res/menu/track_list_context_menu.xml new file mode 100644 index 000000000..31c5e48c4 --- /dev/null +++ b/MyTracks/res/menu/track_list_context_menu.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/ContextualActionModeCallback.java b/MyTracks/src/com/google/android/apps/mytracks/ContextualActionModeCallback.java new file mode 100644 index 000000000..3cbad9d89 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/ContextualActionModeCallback.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012 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; + +/** + * Callback when an item in the contextual action mode is selected. + * + * @author Jimmy Shih + */ +public interface ContextualActionModeCallback { + + /** + * Invoked when an item is selected. + * + * @param itemId the context menu item id + * @param id the row id of the item that is selected + */ + public boolean onClick(int itemId, long id); +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java index 15d7195e2..8f4a31024 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java @@ -13,15 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.google.android.apps.mytracks; import static com.google.android.apps.mytracks.Constants.CHART_TAB_TAG; import static com.google.android.apps.mytracks.Constants.MAP_TAB_TAG; import static com.google.android.apps.mytracks.Constants.STATS_TAB_TAG; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.WaypointCreationRequest; +import com.google.android.apps.mytracks.fragments.ChartFragment; +import com.google.android.apps.mytracks.fragments.ChartSettingsDialogFragment; +import com.google.android.apps.mytracks.fragments.DeleteOneTrackDialogFragment; +import com.google.android.apps.mytracks.fragments.InstallEarthDialogFragment; +import com.google.android.apps.mytracks.fragments.MapFragment; +import com.google.android.apps.mytracks.fragments.StatsFragment; import com.google.android.apps.mytracks.io.file.SaveActivity; import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest; @@ -31,19 +37,14 @@ import com.google.android.apps.mytracks.services.ServiceUtils; import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; import com.google.android.apps.mytracks.util.AnalyticsUtils; import com.google.android.apps.mytracks.util.ApiAdapterFactory; -import com.google.android.apps.mytracks.util.DialogUtils; import com.google.android.maps.mytracks.R; -import android.app.AlertDialog; -import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.RemoteException; @@ -54,8 +55,6 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; -import android.widget.CheckBox; -import android.widget.RadioGroup; import android.widget.TabHost; import android.widget.TabHost.TabSpec; import android.widget.Toast; @@ -75,11 +74,7 @@ public class TrackDetailActivity extends FragmentActivity { private static final String TAG = TrackDetailActivity.class.getSimpleName(); private static final String CURRENT_TAG_KEY = "tab"; - - private static final int DIALOG_INSTALL_EARTH_ID = 0; - private static final int DIALOG_DELETE_CURRENT_ID = 1; - private static final int DIALOG_CHART_SETTINGS_ID = 2; - + private SharedPreferences sharedPreferences; private TrackDataHub trackDataHub; private TrackRecordingServiceConnection trackRecordingServiceConnection; @@ -203,81 +198,7 @@ public class TrackDetailActivity extends FragmentActivity { super.onDestroy(); trackRecordingServiceConnection.unbind(); } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case DIALOG_INSTALL_EARTH_ID: - return new AlertDialog.Builder(this).setCancelable(true) - .setMessage(R.string.track_detail_install_earth_message) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent() - .setData(Uri.parse(SaveActivity.GOOGLE_EARTH_MARKET_URL)); - startActivity(intent); - } - }) - .create(); - case DIALOG_DELETE_CURRENT_ID: - return DialogUtils.createConfirmationDialog(this, - R.string.track_detail_delete_confirm_message, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - deleteCurrentTrack(); - startTrackListActivity(); - } - }); - case DIALOG_CHART_SETTINGS_ID: - return createChartSettingsDialog(); - default: - return null; - } - } - - private Dialog createChartSettingsDialog() { - final ChartFragment chartFragment = (ChartFragment) getSupportFragmentManager() - .findFragmentByTag(CHART_TAB_TAG); - View view = getLayoutInflater().inflate(R.layout.chart_settings, null); - final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x); - radioGroup - .check(chartFragment.getMode() == ChartView.Mode.BY_DISTANCE ? R.id.chart_settings_by_distance - : R.id.chart_settings_by_time); - - final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES]; - checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view - .findViewById(R.id.chart_settings_elevation); - checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed); - checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power); - checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view - .findViewById(R.id.chart_settings_cadence); - checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view - .findViewById(R.id.chart_settings_heart_rate); - - // set checkboxes values - for (int i = 0; i < ChartView.NUM_SERIES; i++) { - checkBoxes[i].setChecked(chartFragment.isChartValueSeriesEnabled(i)); - } - checkBoxes[ChartView.SPEED_SERIES].setText(chartFragment.isReportSpeed() ? R.string.stat_speed - : R.string.stat_pace); - - return new AlertDialog.Builder(this).setCancelable(true) - .setNegativeButton(R.string.generic_cancel, null) - .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - chartFragment - .setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance ? ChartView.Mode.BY_DISTANCE - : ChartView.Mode.BY_TIME); - for (int i = 0; i < ChartView.NUM_SERIES; i++) { - chartFragment.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked()); - } - chartFragment.update(); - } - }).setTitle(R.string.menu_chart_settings).setView(view).create(); - } - + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.track_detail, menu); @@ -353,7 +274,8 @@ public class TrackDetailActivity extends FragmentActivity { .putExtra(SaveActivity.EXTRA_PLAY_TRACK, true); startActivity(intent); } else { - showDialog(DIALOG_INSTALL_EARTH_ID); + new InstallEarthDialogFragment().show( + getSupportFragmentManager(), InstallEarthDialogFragment.INSTALL_EARTH_DIALOG_TAG); } return true; case R.id.track_detail_share_map: @@ -405,7 +327,8 @@ public class TrackDetailActivity extends FragmentActivity { .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId)); return true; case R.id.track_detail_delete: - showDialog(DIALOG_DELETE_CURRENT_ID); + DeleteOneTrackDialogFragment.newInstance(trackId).show( + getSupportFragmentManager(), DeleteOneTrackDialogFragment.DELETE_ONE_TRACK_DIALOG_TAG); return true; case R.id.track_detail_my_location: mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG); @@ -420,7 +343,8 @@ public class TrackDetailActivity extends FragmentActivity { } return true; case R.id.track_detail_chart_settings: - showDialog(DIALOG_CHART_SETTINGS_ID); + new ChartSettingsDialogFragment().show( + getSupportFragmentManager(), ChartSettingsDialogFragment.CHART_SETTINGS_DIALOG_TAG); return true; case R.id.track_detail_sensor_state: startActivity(new Intent(this, SensorStateActivity.class)); @@ -558,13 +482,6 @@ public class TrackDetailActivity extends FragmentActivity { } } - /** - * Deletes the current track. - */ - private void deleteCurrentTrack() { - MyTracksProviderUtils.Factory.get(TrackDetailActivity.this).deleteTrack(trackId); - } - /** * Starts the {@link TrackListActivity}. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java index 455ffd029..371399722 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java @@ -17,7 +17,8 @@ package com.google.android.apps.mytracks; import com.google.android.apps.mytracks.content.TracksColumns; -import com.google.android.apps.mytracks.fragments.DeleteAllDialogFragment; +import com.google.android.apps.mytracks.fragments.DeleteAllTrackDialogFragment; +import com.google.android.apps.mytracks.fragments.DeleteOneTrackDialogFragment; import com.google.android.apps.mytracks.fragments.EulaDialogFragment; import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.services.ITrackRecordingService; @@ -43,11 +44,14 @@ 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; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.TextView; @@ -87,6 +91,7 @@ public class TrackListActivity extends FragmentActivity { try { recordingTrackId = service.startNewTrack(); startNewRecording = false; + startTrackDetailActivity(recordingTrackId); Toast.makeText( TrackListActivity.this, R.string.track_list_record_success, Toast.LENGTH_SHORT).show(); Log.d(TAG, "Started a new recording"); @@ -120,7 +125,16 @@ public class TrackListActivity extends FragmentActivity { adapter.notifyDataSetChanged(); } }; - + + // Callback when an item is selected in the contextual action mode + private ContextualActionModeCallback contextualActionModeCallback = + new ContextualActionModeCallback() { + @Override + public boolean onClick(int itemId, long id) { + return handleContextItem(itemId, id); + } + }; + private TrackRecordingServiceConnection trackRecordingServiceConnection; private SharedPreferences sharedPreferences; private boolean metricUnits; @@ -157,10 +171,7 @@ public class TrackListActivity extends FragmentActivity { listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - Intent intent = new Intent(TrackListActivity.this, TrackDetailActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id); - startActivity(intent); + startTrackDetailActivity(id); } }); adapter = new ResourceCursorAdapter(this, R.layout.track_list_item, null, 0) { @@ -204,7 +215,9 @@ public class TrackListActivity extends FragmentActivity { } }; listView.setAdapter(adapter); - + ApiAdapterFactory.getApiAdapter().configureListViewContextualMenu( + this, listView, R.menu.track_list_context_menu, contextualActionModeCallback); + getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks() { @Override public Loader onCreateLoader(int arg0, Bundle arg1) { @@ -339,8 +352,8 @@ public class TrackListActivity extends FragmentActivity { ExportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.TCX)); return true; case R.id.track_list_delete_all: - new DeleteAllDialogFragment().show( - getSupportFragmentManager(), DeleteAllDialogFragment.DELETE_ALL_DIALOG_TAG); + new DeleteAllTrackDialogFragment().show( + getSupportFragmentManager(), DeleteAllTrackDialogFragment.DELETE_ALL_TRACK_DIALOG_TAG); return true; case R.id.track_list_aggregated_statistics: startActivity(new Intent(this, AggregatedStatsActivity.class)); @@ -356,6 +369,54 @@ public class TrackListActivity extends FragmentActivity { } } + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + getMenuInflater().inflate(R.menu.track_list_context_menu, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (handleContextItem(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).id)) { + return true; + } + return super.onContextItemSelected(item); + } + + /** + * Handles a context item selection. + * + * @param itemId the menu item id + * @param trackId the track id + * @return true if handled. + */ + private boolean handleContextItem(int itemId, long trackId) { + switch (itemId) { + case R.id.track_list_context_menu_edit: + startActivity(new Intent(this, TrackEditActivity.class).putExtra( + TrackEditActivity.EXTRA_TRACK_ID, trackId)); + return true; + case R.id.track_list_context_menu_delete: + DeleteOneTrackDialogFragment.newInstance(trackId).show( + getSupportFragmentManager(), DeleteOneTrackDialogFragment.DELETE_ONE_TRACK_DIALOG_TAG); + return true; + default: + return false; + } + } + + /** + * Starts {@link TrackDetailActivity}. + * + * @param trackId the track id. + */ + private void startTrackDetailActivity(long trackId) { + Intent intent = new Intent(TrackListActivity.this, TrackDetailActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId); + startActivity(intent); + } + /** * Returns true if recording. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java similarity index 98% rename from MyTracks/src/com/google/android/apps/mytracks/ChartFragment.java rename to MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java index cc46dc13d..e0e8bf9a8 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartFragment.java +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartFragment.java @@ -14,9 +14,12 @@ * the License. */ -package com.google.android.apps.mytracks; +package com.google.android.apps.mytracks.fragments; +import com.google.android.apps.mytracks.ChartView; import com.google.android.apps.mytracks.ChartView.Mode; +import com.google.android.apps.mytracks.Constants; +import com.google.android.apps.mytracks.MyTracksApplication; import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartSettingsDialogFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartSettingsDialogFragment.java new file mode 100644 index 000000000..15eda36fe --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/ChartSettingsDialogFragment.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012 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.fragments; + +import static com.google.android.apps.mytracks.Constants.CHART_TAB_TAG; + +import com.google.android.apps.mytracks.ChartView; +import com.google.android.maps.mytracks.R; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.View; +import android.widget.CheckBox; +import android.widget.RadioGroup; + +/** + * A DialogFragment to show chart settings. + * + * @author Jimmy Shih + */ +public class ChartSettingsDialogFragment extends DialogFragment { + + public static final String CHART_SETTINGS_DIALOG_TAG = "chartSettingsDialog"; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final ChartFragment chartFragment = (ChartFragment) getActivity() + .getSupportFragmentManager().findFragmentByTag(CHART_TAB_TAG); + View view = getActivity().getLayoutInflater().inflate(R.layout.chart_settings, null); + final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x); + radioGroup.check(chartFragment.getMode() == ChartView.Mode.BY_DISTANCE + ? R.id.chart_settings_by_distance : R.id.chart_settings_by_time); + + final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES]; + checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view.findViewById( + R.id.chart_settings_elevation); + checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed); + checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power); + checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view.findViewById( + R.id.chart_settings_cadence); + checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view.findViewById( + R.id.chart_settings_heart_rate); + + // set checkboxes values + for (int i = 0; i < ChartView.NUM_SERIES; i++) { + checkBoxes[i].setChecked(chartFragment.isChartValueSeriesEnabled(i)); + } + checkBoxes[ChartView.SPEED_SERIES].setText(chartFragment.isReportSpeed() + ? R.string.stat_speed : R.string.stat_pace); + + return new AlertDialog.Builder(getActivity()) + .setCancelable(true) + .setNegativeButton(R.string.generic_cancel, null) + .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + chartFragment.setMode( + radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance + ? ChartView.Mode.BY_DISTANCE : ChartView.Mode.BY_TIME); + for (int i = 0; i < ChartView.NUM_SERIES; i++) { + chartFragment.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked()); + } + chartFragment.update(); + } + }) + .setTitle(R.string.menu_chart_settings) + .setView(view) + .create(); + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteAllDialogFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteAllTrackDialogFragment.java similarity index 90% rename from MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteAllDialogFragment.java rename to MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteAllTrackDialogFragment.java index 1c845378c..d847b6f95 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteAllDialogFragment.java +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteAllTrackDialogFragment.java @@ -30,9 +30,9 @@ import android.support.v4.app.DialogFragment; * * @author Jimmy Shih */ -public class DeleteAllDialogFragment extends DialogFragment { +public class DeleteAllTrackDialogFragment extends DialogFragment { - public static final String DELETE_ALL_DIALOG_TAG = "deleteAllDialog"; + public static final String DELETE_ALL_TRACK_DIALOG_TAG = "deleteAllTrackDialog"; @Override public Dialog onCreateDialog(Bundle savedInstanceState) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteOneTrackDialogFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteOneTrackDialogFragment.java new file mode 100644 index 000000000..f5291b9d0 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/DeleteOneTrackDialogFragment.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012 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.fragments; + +import com.google.android.apps.mytracks.TrackListActivity; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.util.DialogUtils; +import com.google.android.maps.mytracks.R; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; + +/** + * A DialogFragment to delete one track. + * + * @author Jimmy Shih + */ +public class DeleteOneTrackDialogFragment extends DialogFragment { + + public static final String DELETE_ONE_TRACK_DIALOG_TAG = "deleteOneTrackDialog"; + private static final String KEY_TRACK_ID = "trackId"; + + public static DeleteOneTrackDialogFragment newInstance(long trackId) { + Bundle bundle = new Bundle(); + bundle.putLong(KEY_TRACK_ID, trackId); + + DeleteOneTrackDialogFragment deleteTrackDialogFragment = new DeleteOneTrackDialogFragment(); + deleteTrackDialogFragment.setArguments(bundle); + return deleteTrackDialogFragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return DialogUtils.createConfirmationDialog(getActivity(), + R.string.track_detail_delete_confirm_message, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MyTracksProviderUtils.Factory.get(getActivity()) + .deleteTrack(getArguments().getLong(KEY_TRACK_ID)); + startActivity(new Intent(getActivity(), TrackListActivity.class).addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)); + } + }); + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/InstallEarthDialogFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/InstallEarthDialogFragment.java new file mode 100644 index 000000000..cb6127602 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/InstallEarthDialogFragment.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 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.fragments; + +import com.google.android.apps.mytracks.io.file.SaveActivity; +import com.google.android.maps.mytracks.R; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; + +/** + * A DialogFragment to install Google Earth. + * + * @author Jimmy Shih + */ +public class InstallEarthDialogFragment extends DialogFragment { + + public static final String INSTALL_EARTH_DIALOG_TAG = "installEarthDialog"; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setCancelable(true) + .setMessage(R.string.track_detail_install_earth_message) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent().setData(Uri.parse(SaveActivity.GOOGLE_EARTH_MARKET_URL))); + } + }) + .create(); + } +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java similarity index 98% rename from MyTracks/src/com/google/android/apps/mytracks/MapFragment.java rename to MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java index f8e5abf77..cba1214a0 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/MapFragment.java @@ -14,8 +14,11 @@ * the License. */ -package com.google.android.apps.mytracks; +package com.google.android.apps.mytracks.fragments; +import com.google.android.apps.mytracks.MapOverlay; +import com.google.android.apps.mytracks.MyTracksApplication; +import com.google.android.apps.mytracks.TrackDetailActivity; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; import com.google.android.apps.mytracks.content.Track; diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/StatsFragment.java similarity index 97% rename from MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java rename to MyTracks/src/com/google/android/apps/mytracks/fragments/StatsFragment.java index 5cb3b3627..8cae95a57 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/StatsFragment.java @@ -14,8 +14,11 @@ * the License. */ -package com.google.android.apps.mytracks; +package com.google.android.apps.mytracks.fragments; +import com.google.android.apps.mytracks.Constants; +import com.google.android.apps.mytracks.MyTracksApplication; +import com.google.android.apps.mytracks.StatsUtilities; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType; diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java index 2234f8b8f..34e5bd954 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java @@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.MapOverlay.CachedLocation; import com.google.android.maps.GeoPoint; import com.google.android.maps.Projection; import com.google.android.maps.mytracks.R; +import com.google.common.annotations.VisibleForTesting; import android.content.Context; import android.graphics.Canvas; @@ -138,4 +139,14 @@ public class DynamicSpeedTrackPathPainter implements TrackPathPainter { } return path; } + + /** + * Returns coloredPaths. + * + * @return coloredPaths + */ + @VisibleForTesting + List getColoredPaths() { + return coloredPaths; + } } \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java index c73927cd6..7d89f488e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java @@ -19,6 +19,7 @@ import com.google.android.apps.mytracks.MapOverlay.CachedLocation; import com.google.android.maps.GeoPoint; import com.google.android.maps.Projection; import com.google.android.maps.mytracks.R; +import com.google.common.annotations.VisibleForTesting; import android.content.Context; import android.graphics.Canvas; @@ -29,16 +30,15 @@ import android.graphics.Rect; import java.util.List; - /** * A path painter that not variates the path colors. - * - * @author Vangelis S. + * + * @author Vangelis S. */ public class SingleColorTrackPathPainter implements TrackPathPainter { private final Paint selectedTrackPaint; private Path path; - + public SingleColorTrackPathPainter(Context context) { selectedTrackPaint = TrackPathUtilities.getPaint(R.color.red, context); } @@ -47,49 +47,64 @@ public class SingleColorTrackPathPainter implements TrackPathPainter { public void drawTrack(Canvas canvas) { canvas.drawPath(path, selectedTrackPaint); } - + @Override public void updatePath(Projection projection, Rect viewRect, int startLocationIdx, Boolean alwaysVisible, List points) { + path = new Path(); + updatePath(projection, viewRect, startLocationIdx, alwaysVisible, points, path); + + } + + /** + * Updates the path. + * + * @param projection The Canvas to draw upon. + * @param viewRect The Path to be drawn. + * @param startLocationIdx The start point from where update the path. + * @param alwaysVisible Flag for always visible. + * @param points The list of points used to update the path. + * @param pathToUpdate The path to be created. + */ + @VisibleForTesting + void updatePath(Projection projection, Rect viewRect, int startLocationIdx, + Boolean alwaysVisible, List points, Path pathToUpdate) { + pathToUpdate.incReserve(points.size()); // Whether to start a new segment on new valid and visible point. - boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid; + boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid; boolean lastVisible = !newSegment; final Point pt = new Point(); // Loop over track points. - int numPoints = points.size(); - path = newPath(); - path.incReserve(numPoints); - - for (int i = startLocationIdx; i < numPoints ; ++i) { + for (int i = startLocationIdx; i < points.size(); ++i) { CachedLocation loc = points.get(i); - + // Check if valid, if not then indicate a new segment. if (!loc.valid) { newSegment = true; continue; } - + final GeoPoint geoPoint = loc.geoPoint; // Check if this breaks the existing segment. - boolean visible = alwaysVisible + boolean visible = alwaysVisible || viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6()); if (!visible && !lastVisible) { // This is a point outside view not connected to a visible one. newSegment = true; } lastVisible = visible; - + // Either move to beginning of a new segment or continue the old one. projection.toPixels(geoPoint, pt); if (newSegment) { - path.moveTo(pt.x, pt.y); + pathToUpdate.moveTo(pt.x, pt.y); newSegment = false; } else { - path.lineTo(pt.x, pt.y); + pathToUpdate.lineTo(pt.x, pt.y); } } } - + @Override public void clear() { path = null; @@ -99,12 +114,12 @@ public class SingleColorTrackPathPainter implements TrackPathPainter { public boolean needsRedraw() { return false; } - + @Override public Path getLastPath() { return path; } - + // Visible for testing public Path newPath() { return new Path(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/Api11Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api11Adapter.java index c86a0cea9..9759bb854 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/Api11Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api11Adapter.java @@ -16,12 +16,19 @@ package com.google.android.apps.mytracks.util; +import com.google.android.apps.mytracks.ContextualActionModeCallback; + import android.annotation.TargetApi; -import android.app.ActionBar; import android.app.Activity; import android.app.SearchManager; import android.content.Context; +import android.view.ActionMode; +import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ListView; import android.widget.SearchView; /** @@ -42,6 +49,45 @@ public class Api11Adapter extends Api10Adapter { activity.getActionBar().setDisplayHomeAsUpEnabled(true); } + @Override + public void configureListViewContextualMenu(final Activity activity, ListView listView, final int menuId, + final ContextualActionModeCallback contextualActionModeCallback) { + listView.setOnItemLongClickListener(new OnItemLongClickListener() { + ActionMode actionMode; + + @Override + public boolean onItemLongClick( + AdapterView parent, View view, int position, final long id) { + if (actionMode != null) { return false; } + actionMode = activity.startActionMode(new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(menuId, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // Return false to indicate no change. + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + actionMode = null; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return contextualActionModeCallback.onClick(item.getItemId(), id); + } + }); + view.setSelected(true); + return true; + } + }); + }; + @Override public void configureSearchWidget(Activity activity, MenuItem menuItem) { SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java index fb6d5cffb..be6c744c5 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/Api7Adapter.java @@ -16,6 +16,7 @@ package com.google.android.apps.mytracks.util; import com.google.android.apps.mytracks.Constants; +import com.google.android.apps.mytracks.ContextualActionModeCallback; import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; import com.google.android.apps.mytracks.services.sensors.BluetoothConnectionManager; import com.google.android.apps.mytracks.services.tasks.PeriodicTask; @@ -32,6 +33,7 @@ import android.content.SharedPreferences.Editor; import android.util.Log; import android.view.MenuItem; import android.view.Window; +import android.widget.ListView; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -113,6 +115,12 @@ public class Api7Adapter implements ApiAdapter { // Do nothing } + @Override + public void configureListViewContextualMenu(Activity activity, ListView listView, int menuId, + ContextualActionModeCallback contextualActionModeCallback) { + activity.registerForContextMenu(listView); + } + @Override public void configureSearchWidget(Activity activity, MenuItem menuItem) { // Do nothing diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java index 62b469633..fe6384560 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiAdapter.java @@ -15,6 +15,7 @@ */ package com.google.android.apps.mytracks.util; +import com.google.android.apps.mytracks.ContextualActionModeCallback; import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; import com.google.android.apps.mytracks.services.tasks.PeriodicTask; import com.google.api.client.http.HttpTransport; @@ -25,6 +26,7 @@ import android.bluetooth.BluetoothSocket; import android.content.Context; import android.content.SharedPreferences; import android.view.MenuItem; +import android.widget.ListView; import java.io.IOException; @@ -123,6 +125,20 @@ public interface ApiAdapter { */ public void configureActionBarHomeAsUp(Activity activity); + /** + * Configures the list view context menu. + *

+ * Due to changes in API level 11. + * + * @param activity the activity + * @param listView the list view + * @param menuId the menu resource id + * @param contextualActionModeCallback the callback when an item is selected + * in the contextual action mode + */ + public void configureListViewContextualMenu(Activity activity, ListView listView, int menuId, + ContextualActionModeCallback contextualActionModeCallback); + /** * Configures the search widget. * diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java similarity index 98% rename from MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java rename to MyTracksTest/src/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java index 69418ddd6..c1d0c8a57 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/fragments/ChartFragmentTest.java @@ -14,8 +14,10 @@ * the License. */ -package com.google.android.apps.mytracks; +package com.google.android.apps.mytracks.fragments; +import com.google.android.apps.mytracks.ChartView; +import com.google.android.apps.mytracks.TrackStubUtils; import com.google.android.apps.mytracks.content.MyTracksLocation; import com.google.android.apps.mytracks.content.Sensor; import com.google.android.apps.mytracks.content.Sensor.SensorDataSet; diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainterTest.java new file mode 100644 index 000000000..a583861fe --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainterTest.java @@ -0,0 +1,165 @@ +/* + * Copyright 2012 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.maps; + +import com.google.android.apps.mytracks.ColoredPath; +import com.google.android.apps.mytracks.MapOverlay.CachedLocation; +import com.google.android.apps.mytracks.TrackStubUtils; +import com.google.android.maps.mytracks.R; +import com.google.android.testing.mocking.AndroidMock; +import com.google.android.testing.mocking.UsesMocks; + +import java.util.List; + +/** + * Tests for the {@link DynamicSpeedTrackPathPainter}. + * + * @author Youtao Liu + */ +public class DynamicSpeedTrackPathPainterTest extends TrackPathPainterTestCase { + + private DynamicSpeedTrackPathPainter dynamicSpeedTrackPathPainter; + private TrackPathDescriptor trackPathDescriptor; + // This number must bigger than 10 to meet the requirement of test. + private static final int NUMBER_OF_LOCATIONS = 100; + private static final int LOCATIONS_PER_SEGMENT = 25; + // The maximum speed(KM/H) which is considered slow. + private static final int SLOW_SPEED_KMH = 30; + // The maximum speed(KM/H) which is considered normal. + private static final int NORMAL_SPEED_KMH = 50; + + // Convert from kilometers per hour to meters per second + private static final double KMH_TO_MS = 1 / 3.6; + private static final int SLOW_SPEED_MS = (int) (SLOW_SPEED_KMH * KMH_TO_MS); + private static final int NORMAL_SPEED_MS = (int) (NORMAL_SPEED_KMH * KMH_TO_MS); + + @Override + protected void setUp() throws Exception { + super.setUp(); + initialTrackPathDescriptorMock(); + dynamicSpeedTrackPathPainter = new DynamicSpeedTrackPathPainter(getContext(), + trackPathDescriptor); + } + + /** + * Tests the method + * {@link DynamicSpeedTrackPathPainter#updatePath(com.google.android.maps.Projection, android.graphics.Rect, int, Boolean, java.util.List)} + * when all locations are invalid. + */ + public void testUpdatePath_AllInvalidLocation() { + List points = createCachedLocations(NUMBER_OF_LOCATIONS, INVALID_LATITUDE, -1); + dynamicSpeedTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), + myTracksOverlay.getMapViewRect(mockView), 1, true, points); + AndroidMock.verify(trackPathDescriptor); + // Should be zero for there is no valid locations. + assertEquals(0, dynamicSpeedTrackPathPainter.getColoredPaths().size()); + } + + /** + * Tests the + * {@link DynamicSpeedTrackPathPainter#updatePath(com.google.android.maps.Projection, android.graphics.Rect, int, Boolean, java.util.List)} + * when all locations are valid. + */ + public void testUpdatePath_AllValidLocation() { + List points = createCachedLocations(NUMBER_OF_LOCATIONS, + TrackStubUtils.INITIAL_LATITUDE, -1); + + // Gets a number as the start index of points. + int startLocationIdx = NUMBER_OF_LOCATIONS / 2; + dynamicSpeedTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), + myTracksOverlay.getMapViewRect(mockView), startLocationIdx, true, points); + + AndroidMock.verify(trackPathDescriptor); + assertEquals(NUMBER_OF_LOCATIONS - startLocationIdx, dynamicSpeedTrackPathPainter + .getColoredPaths().size()); + } + + /** + * Tests the + * {@link DynamicSpeedTrackPathPainter#updatePath(com.google.android.maps.Projection, android.graphics.Rect, int, Boolean, java.util.List)} + * when all locations are valid. This test setups 4 segments with 25 points + * each. The first segment has slow speed, the second segment has normal + * speed, the third segment has fast speed, and the fourth segment has slow + * speed. + */ + public void testUpdatePath_CheckColoredPath() { + // Gets the slow speed. Divide SLOW_SPEED by 2 to make it smaller than + // SLOW_SPEED. Speed in MyTracksLocation use MS, but speed in CachedLocation + // use KMH. + int slowSpeed = SLOW_SPEED_MS / 2; + // Gets the normal speed. Makes it smaller than SLOW_SPEED and bigger than + // NORMAL_SPEED. Speed in MyTracksLocation use MS, but speed in + // CachedLocation use KMH. + int normalSpeed = (SLOW_SPEED_MS + NORMAL_SPEED_MS) / 2; + // Gets the fast speed. Multiply it by 2 to make it bigger than + // NORMAL_SPEED. Speed in MyTracksLocation use MS, but speed in + // CachedLocation use KMH. + int fastSpeed = NORMAL_SPEED_MS * 2; + + // Get a number of startLocationIdx. And divide NUMBER_OF_LOCATIONS by 8 to + // make sure it is less than numberOfFirstThreeSegments. + int startLocationIdx = LOCATIONS_PER_SEGMENT / 2; + + List points = createCachedLocations(LOCATIONS_PER_SEGMENT, + TrackStubUtils.INITIAL_LATITUDE, slowSpeed); + points.addAll(createCachedLocations(LOCATIONS_PER_SEGMENT, TrackStubUtils.INITIAL_LATITUDE, + normalSpeed)); + points.addAll(createCachedLocations(LOCATIONS_PER_SEGMENT, TrackStubUtils.INITIAL_LATITUDE, + fastSpeed)); + points.addAll(createCachedLocations(LOCATIONS_PER_SEGMENT, TrackStubUtils.INITIAL_LATITUDE, + slowSpeed)); + + dynamicSpeedTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), + myTracksOverlay.getMapViewRect(mockView), startLocationIdx, true, points); + + AndroidMock.verify(trackPathDescriptor); + List coloredPath = dynamicSpeedTrackPathPainter.getColoredPaths(); + assertEquals(NUMBER_OF_LOCATIONS - startLocationIdx, coloredPath.size()); + + // Checks different speeds with different color in the coloredPath. + for (int i = 0; i < NUMBER_OF_LOCATIONS - startLocationIdx; i++) { + if (i < LOCATIONS_PER_SEGMENT - startLocationIdx) { + // Slow. + assertEquals(getContext().getResources().getColor(R.color.slow_path), coloredPath.get(i) + .getPathPaint().getColor()); + } else if (i < LOCATIONS_PER_SEGMENT * 2 - startLocationIdx) { + // Normal. + assertEquals(getContext().getResources().getColor(R.color.normal_path), coloredPath.get(i) + .getPathPaint().getColor()); + } else if (i < LOCATIONS_PER_SEGMENT * 3 - startLocationIdx) { + // Fast. + assertEquals(getContext().getResources().getColor(R.color.fast_path), coloredPath.get(i) + .getPathPaint().getColor()); + } else { + // Slow. + assertEquals(getContext().getResources().getColor(R.color.slow_path), coloredPath.get(i) + .getPathPaint().getColor()); + } + } + } + + /** + * Initials a mocked TrackPathDescriptor object. + */ + @UsesMocks(TrackPathDescriptor.class) + private void initialTrackPathDescriptorMock() { + trackPathDescriptor = AndroidMock.createMock(TrackPathDescriptor.class); + AndroidMock.expect(trackPathDescriptor.getSlowSpeed()).andReturn(SLOW_SPEED_KMH); + AndroidMock.expect(trackPathDescriptor.getNormalSpeed()).andReturn(NORMAL_SPEED_KMH); + AndroidMock.replay(trackPathDescriptor); + } + +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainterTest.java new file mode 100644 index 000000000..1679c547a --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainterTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012 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.maps; + +import com.google.android.apps.mytracks.MapOverlay.CachedLocation; +import com.google.android.apps.mytracks.TrackStubUtils; +import com.google.android.testing.mocking.AndroidMock; +import com.google.android.testing.mocking.UsesMocks; + +import android.graphics.Path; + +import java.util.List; + +/** + * Tests for the {@link SingleColorTrackPathPainter}. + * + * @author Youtao Liu + */ +public class SingleColorTrackPathPainterTest extends TrackPathPainterTestCase { + + private SingleColorTrackPathPainter singleColorTrackPathPainter; + private Path pathMock; + private static final int NUMBER_OF_LOCATIONS = 100; + + /** + * Initials a mocked TrackPathDescriptor object and + * singleColorTrackPathPainter. + */ + @Override + @UsesMocks(Path.class) + protected void setUp() throws Exception { + super.setUp(); + + pathMock = AndroidMock.createStrictMock(Path.class); + singleColorTrackPathPainter = new SingleColorTrackPathPainter(getContext()); + } + + /** + * Tests the + * {@link SingleColorTrackPathPainter#updatePath(com.google.android.maps.Projection, android.graphics.Rect, int, Boolean, List, Path)} + * method when all locations are valid. + */ + public void testUpdatePath_AllValidLocation() { + pathMock.incReserve(NUMBER_OF_LOCATIONS); + List points = createCachedLocations(NUMBER_OF_LOCATIONS, + TrackStubUtils.INITIAL_LATITUDE, -1); + // Gets a number as the start index of points. + int startLocationIdx = NUMBER_OF_LOCATIONS / 2; + + for (int i = startLocationIdx; i < NUMBER_OF_LOCATIONS; i++) { + pathMock.lineTo(0, 0); + } + + AndroidMock.replay(pathMock); + singleColorTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), + myTracksOverlay.getMapViewRect(mockView), startLocationIdx, true, points, pathMock); + AndroidMock.verify(pathMock); + } + + /** + * Tests the + * {@link SingleColorTrackPathPainter#updatePath(com.google.android.maps.Projection, android.graphics.Rect, int, Boolean, List, Path)} + * method when all locations are invalid. + */ + public void testUpdatePath_AllInvalidLocation() { + pathMock.incReserve(NUMBER_OF_LOCATIONS); + List points = createCachedLocations(NUMBER_OF_LOCATIONS, INVALID_LATITUDE, -1); + // Gets a random number from 1 to numberOfLocations. + int startLocationIdx = NUMBER_OF_LOCATIONS / 2; + AndroidMock.replay(pathMock); + singleColorTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), + myTracksOverlay.getMapViewRect(mockView), startLocationIdx, true, points, pathMock); + AndroidMock.verify(pathMock); + } + + /** + * Tests the + * {@link SingleColorTrackPathPainter#updatePath(com.google.android.maps.Projection, android.graphics.Rect, int, Boolean, List, Path)} + * method when there are three segments. + */ + public void testUpdatePath_ThreeSegments() { + // First segment. + List points = createCachedLocations(NUMBER_OF_LOCATIONS, + TrackStubUtils.INITIAL_LATITUDE, -1); + points.addAll(createCachedLocations(1, INVALID_LATITUDE, -1)); + // Second segment. + points.addAll(createCachedLocations(NUMBER_OF_LOCATIONS, TrackStubUtils.INITIAL_LATITUDE, -1)); + points.addAll(createCachedLocations(1, INVALID_LATITUDE, -1)); + // Third segment. + points.addAll(createCachedLocations(NUMBER_OF_LOCATIONS, TrackStubUtils.INITIAL_LATITUDE, -1)); + // Gets a random number from 1 to numberOfLocations. + int startLocationIdx = NUMBER_OF_LOCATIONS / 2; + pathMock.incReserve(NUMBER_OF_LOCATIONS *3 + 1 +1); + for (int i = 0; i < NUMBER_OF_LOCATIONS - startLocationIdx; i++) { + pathMock.lineTo(0, 0); + } + pathMock.moveTo(0, 0); + for (int i = 0; i < NUMBER_OF_LOCATIONS - 1; i++) { + pathMock.lineTo(0, 0); + } + pathMock.moveTo(0, 0); + for (int i = 0; i < NUMBER_OF_LOCATIONS - 1; i++) { + pathMock.lineTo(0, 0); + } + + AndroidMock.replay(pathMock); + singleColorTrackPathPainter.updatePath(myTracksOverlay.getMapProjection(mockView), + myTracksOverlay.getMapViewRect(mockView), startLocationIdx, true, points, pathMock); + AndroidMock.verify(pathMock); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java index 59ceb94fd..b53909b6e 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/maps/TrackPathPainterTestCase.java @@ -15,22 +15,32 @@ */ package com.google.android.apps.mytracks.maps; +import com.google.android.apps.mytracks.MapOverlay; +import com.google.android.apps.mytracks.MapOverlay.CachedLocation; import com.google.android.apps.mytracks.MockMyTracksOverlay; +import com.google.android.apps.mytracks.TrackStubUtils; import com.google.android.maps.MapView; import android.graphics.Canvas; +import android.location.Location; import android.test.AndroidTestCase; +import java.util.ArrayList; +import java.util.List; + /** * Tests for the MyTracks track path descriptors and painters. * * @author Vangelis S. */ public class TrackPathPainterTestCase extends AndroidTestCase { + protected Canvas canvas; protected MockMyTracksOverlay myTracksOverlay; protected MapView mockView; + final int INVALID_LATITUDE = 100; + @Override protected void setUp() throws Exception { super.setUp(); @@ -40,4 +50,27 @@ public class TrackPathPainterTestCase extends AndroidTestCase { myTracksOverlay.setTrackDrawingEnabled(true); mockView = null; } + + /** + * Creates a list of CachedLocations. + * + * @param number the number of locations + * @param latitude the latitude value of locations. + * @param speed the speed(meter per second) of locations, and will give a default valid value if + * less than zero + * @return the simulated locations + */ + List createCachedLocations(int number, double latitude, float speed) { + List points = new ArrayList(); + for (int i = 0; i < number; ++i) { + Location location = TrackStubUtils.createMyTracksLocation(latitude, + TrackStubUtils.INITIAL_LONGITUDE, TrackStubUtils.INITIAL_ALTITUDE); + if (speed > 0) { + location.setSpeed(speed); + } + CachedLocation cachedLocation = new CachedLocation(location); + points.add(cachedLocation); + } + return points; + } }