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
+ * 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