merge heads

This commit is contained in:
Jimmy Shih
2012-04-02 14:50:06 -07:00
parent 2d05ff98ca
commit 9575401127
15 changed files with 671 additions and 549 deletions
+1 -1
View File
@@ -256,7 +256,7 @@ limitations under the License.
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.TrackList" >
<activity android:name="com.google.android.apps.mytracks.TrackListActivity" >
<meta-data
android:name="android.app.default_searchable"
android:value="com.google.android.apps.mytracks.SearchActivity" />
-58
View File
@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apl/res/com.google.android.apps.mytracks"
android:id="@+id/tracklist_root"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_bar"
android:layout_weight="0"
android:padding="3dp">
<TextView android:id="@+id/message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="#fff"
android:text="@string/track_list_pick_one"
android:layout_weight="1"/>
</LinearLayout>
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<LinearLayout style="@style/ButtonLayout">
<LinearLayout style="@style/ButtonSpacer"/>
<Button android:id="@+id/tracklist_btn_import_all"
android:layout_gravity="left"
android:text="@string/track_list_import_all"
style="@style/Button"/>
<Button android:id="@+id/tracklist_btn_export_all"
android:layout_gravity="center_horizontal"
android:text="@string/track_list_export_all"
style="@style/Button"/>
<Button android:id="@+id/tracklist_btn_delete_all"
android:layout_gravity="right"
android:text="@string/track_list_delete_all"
style="@style/Button"/>
<LinearLayout style="@style/ButtonSpacer"/>
</LinearLayout>
</LinearLayout>
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/track_list"
android:layout_height="fill_parent"
android:layout_width="fill_parent" />
</LinearLayout>
+77
View File
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="vertical"
android:padding="8dp" >
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal" >
<TextView
android:drawablePadding="8dp"
android:id="@+id/track_list_item_name"
android:gravity="center_vertical"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:singleLine="true"
style="@android:style/TextAppearance.DeviceDefault.Large" />
<TextView
android:id="@+id/track_list_item_category"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_width="wrap_content"
android:singleLine="true"
style="@android:style/TextAppearance.DeviceDefault.Small" />
</LinearLayout>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal" >
<TextView
android:id="@+id/track_list_item_time"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:singleLine="true"
android:textColor="@color/green"
style="@android:style/TextAppearance.DeviceDefault.Small" />
<TextView
android:id="@+id/track_list_item_distance"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_width="wrap_content"
android:singleLine="true"
android:textColor="@color/green"
style="@android:style/TextAppearance.DeviceDefault.Small" />
<TextView
android:id="@+id/track_list_item_start"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_width="wrap_content"
android:singleLine="true"
android:textColor="@color/green"
style="@android:style/TextAppearance.DeviceDefault.Small" />
</LinearLayout>
<TextView
android:id="@+id/track_list_item_description"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:singleLine="false"
style="@android:style/TextAppearance.DeviceDefault.Small" />
</LinearLayout>
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:icon="@drawable/menu_record_track"
android:id="@+id/menu_record_track"
android:showAsAction="always"
android:title="@string/menu_record_track"/>
<item
android:icon="@drawable/menu_stop_recording"
android:id="@+id/menu_stop_recording"
android:showAsAction="always"
android:title="@string/menu_stop_recording"/>
<item
android:actionViewClass="android.widget.SearchView"
android:icon="@drawable/menu_search"
android:id="@+id/menu_search"
android:showAsAction="always|collapseActionView"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_import_all"
android:title="@string/menu_import_all"/>
<item
android:id="@+id/menu_export_all"
android:title="@string/menu_export_all"/>
<item
android:id="@+id/menu_delete_all"
android:title="@string/menu_delete_all"/>
<item
android:id="@+id/menu_aggregated_statistics"
android:title="@string/menu_aggregated_statistics"/>
<item
android:icon="@drawable/menu_settings"
android:id="@+id/menu_settings"
android:title="@string/menu_settings"/>
<item
android:icon="@drawable/menu_help"
android:id="@+id/menu_help"
android:title="@string/menu_help"/>
</menu>
+1
View File
@@ -17,6 +17,7 @@ limitations under the License.
<resources>
<color name="black">#FF000000</color>
<color name="blue">#FF0000FF</color>
<color name="green">#FF00FF00</color>
<color name="gray">#FFAAAAAA</color>
<color name="red">#FFFF0000</color>
<color name="stat_label_background">#FFF0F0F0</color>
+12 -25
View File
@@ -973,10 +973,22 @@ limitations under the License.
translation_description="In the menu, to change the chart settings.">
Chart settings
</string>
<string name="menu_delete_all"
translation_description="In the menu, to delete all the tracks.">
Delete all
</string>
<string name="menu_export_all"
translation_description="In the menu, to export all the tracks to the SD card.">
Export all
</string>
<string name="menu_help"
translation_description="In the menu, to go to the help page.">
Help
</string>
<string name="menu_import_all"
translation_description="In the menu, to import all the tracks from the SD card.">
Import all
</string>
<string name="menu_map_mode"
translation_description="In the menu, to switch the display to the map mode.">
Map mode
@@ -2277,12 +2289,6 @@ limitations under the License.
Screenshot: track_list_context_menu.png">
This track
</string>
<string name="track_list_delete_all"
translation_description="In the 'Tracks' page, the button on the bottom to delete all tracks.
Screenshot: track_list.png">
Delete all
</string>
<string name="track_list_delete_all_confirm_message"
translation_description="In the 'Tracks' page, when pressing the 'Delete all' button, the
confirmation dialog message informing the user that all tracks and markers will be permanently
@@ -2313,12 +2319,6 @@ limitations under the License.
Screenshot: track_list_context_menu.png">
Edit
</string>
<string name="track_list_export_all"
translation_description="In the 'Tracks' page, the button on the bottom to export all tracks.
Screenshot: track_list.png">
Export all
</string>
<string name="track_list_export_file"
translation_description="In the 'Tracks' page, after pressing the 'Export all' button, the menu
option to export all the tracks as a certain file format
@@ -2327,19 +2327,6 @@ limitations under the License.
Screenshot: track_list_export_file.png">
Export as %1$s
</string>
<string name="track_list_import_all"
translation_description="In the 'Tracks' page, the button on the bottom to import all tracks.
Screenshot: track_list.png">
Import all
</string>
<string name="track_list_pick_one"
translation_description="In the 'Tracks' page, the instruction at the top of the page informing
the user he can press and hold a track for more options.
Screenshot: track_list.png">
Pick a track. Press &amp; hold for more options.
</string>
<string name="track_list_play"
translation_description="In the context menu for a track, the menu option to play the track.">
Play
@@ -118,7 +118,7 @@ class MenuManager {
return true;
}
case R.id.menu_tracks: {
activity.startActivityForResult(new Intent(activity, TrackList.class),
activity.startActivityForResult(new Intent(activity, TrackListActivity.class),
Constants.SHOW_TRACK);
return true;
}
@@ -1,462 +0,0 @@
/*
* Copyright 2008 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 static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
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;
import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
/**
* A list activity displaying all the recorded tracks. There's a context
* menu (via long press) displaying various options such as showing, editing,
* deleting, sending to Google, or writing to SD card.
*
* @author Leif Hendrik Wilden
*/
public class TrackList extends ListActivity
implements SharedPreferences.OnSharedPreferenceChangeListener,
View.OnClickListener {
private static final int DIALOG_INSTALL_EARTH_ID = 0;
private static final int DIALOG_EXPORT_ALL_ID = 1;
private static final int DIALOG_DELETE_ALL_ID = 2;
private static final int DIALOG_DELETE_CURRENT_ID = 3;
private int contextPosition = -1;
private long trackId = -1;
private ListView listView = null;
private boolean metricUnits = true;
private Cursor tracksCursor = null;
/**
* The id of the currently recording track.
*/
private long recordingTrackId = -1;
private final OnCreateContextMenuListener contextMenuListener =
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
menu.setHeaderTitle(R.string.track_list_context_menu_title);
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
trackId = TrackList.this.listView.getAdapter().getItemId(contextPosition);
menu.add(Menu.NONE, Constants.MENU_SHOW, Menu.NONE, R.string.track_list_show_on_map);
menu.add(Menu.NONE, Constants.MENU_EDIT, Menu.NONE, R.string.track_list_edit_track);
if (!isRecording() || trackId != recordingTrackId) {
String saveFileFormat = getString(R.string.track_list_save_file);
String shareFileFormat = getString(R.string.track_list_share_file);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
menu.add(Menu.NONE, Constants.MENU_PLAY, Menu.NONE, R.string.track_list_play);
menu.add(Menu.NONE, Constants.MENU_SEND_TO_GOOGLE, Menu.NONE,
R.string.track_list_send_google);
SubMenu share = menu.addSubMenu(
Menu.NONE, Constants.MENU_SHARE, Menu.NONE, R.string.track_list_share_track);
share.add(
Menu.NONE, Constants.MENU_SHARE_MAP, Menu.NONE, R.string.track_list_share_map);
share.add(Menu.NONE, Constants.MENU_SHARE_FUSION_TABLE, Menu.NONE,
R.string.track_list_share_fusion_table);
share.add(Menu.NONE, Constants.MENU_SHARE_GPX_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[0]));
share.add(Menu.NONE, Constants.MENU_SHARE_KML_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[1]));
share.add(Menu.NONE, Constants.MENU_SHARE_CSV_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[2]));
share.add(Menu.NONE, Constants.MENU_SHARE_TCX_FILE, Menu.NONE,
String.format(shareFileFormat, fileTypes[3]));
SubMenu save = menu.addSubMenu(
Menu.NONE, Constants.MENU_WRITE_TO_SD_CARD, Menu.NONE, R.string.track_list_save_sd);
save.add(Menu.NONE, Constants.MENU_SAVE_GPX_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[0]));
save.add(Menu.NONE, Constants.MENU_SAVE_KML_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[1]));
save.add(Menu.NONE, Constants.MENU_SAVE_CSV_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[2]));
save.add(Menu.NONE, Constants.MENU_SAVE_TCX_FILE, Menu.NONE,
String.format(saveFileFormat, fileTypes[3]));
menu.add(Menu.NONE, Constants.MENU_DELETE, Menu.NONE, R.string.track_list_delete_track);
}
}
};
private final Runnable serviceBindingChanged = new Runnable() {
@Override
public void run() {
updateButtonsEnabled();
}
};
private TrackRecordingServiceConnection serviceConnection;
private SharedPreferences preferences;
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
if (key == null) {
return;
}
if (key.equals(getString(R.string.metric_units_key))) {
metricUnits = sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
if (tracksCursor != null && !tracksCursor.isClosed()) {
tracksCursor.requery();
}
}
if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId = sharedPreferences.getLong(
getString(R.string.recording_track_key), -1);
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent result = new Intent();
result.putExtra("trackid", id);
setResult(Constants.SHOW_TRACK, result);
finish();
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
Intent intent;
switch (item.getItemId()) {
case Constants.MENU_SHOW: {
onListItemClick(null, null, 0, trackId);
return true;
}
case Constants.MENU_EDIT: {
intent = new Intent(this, TrackDetail.class).putExtra(TrackDetail.TRACK_ID, trackId);
startActivity(intent);
return true;
}
case Constants.MENU_PLAY:
if (PlayTrackUtils.isEarthInstalled(this)) {
PlayTrackUtils.playTrack(this, trackId);
return true;
} else {
showDialog(DIALOG_INSTALL_EARTH_ID);
return true;
}
case Constants.MENU_SEND_TO_GOOGLE:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, true, true));
startActivity(intent);
return true;
case Constants.MENU_SHARE_MAP:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, false, false));
startActivity(intent);
return true;
case Constants.MENU_SHARE_FUSION_TABLE:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, false, true, false));
startActivity(intent);
return true;
case Constants.MENU_SHARE_GPX_FILE:
startSaveActivity(TrackFileFormat.GPX, true);
return true;
case Constants.MENU_SHARE_KML_FILE:
startSaveActivity(TrackFileFormat.KML, true);
return true;
case Constants.MENU_SHARE_CSV_FILE:
startSaveActivity(TrackFileFormat.CSV, true);
return true;
case Constants.MENU_SHARE_TCX_FILE:
startSaveActivity(TrackFileFormat.TCX, true);
return true;
case Constants.MENU_SAVE_GPX_FILE:
startSaveActivity(TrackFileFormat.GPX, false);
return true;
case Constants.MENU_SAVE_KML_FILE:
startSaveActivity(TrackFileFormat.KML, false);
return true;
case Constants.MENU_SAVE_CSV_FILE:
startSaveActivity(TrackFileFormat.CSV, false);
return true;
case Constants.MENU_SAVE_TCX_FILE:
startSaveActivity(TrackFileFormat.TCX, false);
return true;
case Constants.MENU_DELETE:
showDialog(DIALOG_DELETE_CURRENT_ID);
return true;
default:
Log.w(TAG, "Unknown menu item: " + item.getItemId() + "(" + item.getTitle() + ")");
return super.onMenuItemSelected(featureId, item);
}
}
/**
* Starts the {@link SaveActivity} to save trackId.
*
* @param trackFileFormat the track file format
* @param shareTrack true to share the track after saving
*/
private void startSaveActivity(TrackFileFormat trackFileFormat, boolean shareTrack) {
Intent intent = new Intent(this, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat)
.putExtra(SaveActivity.EXTRA_SHARE_TRACK, shareTrack);
startActivity(intent);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tracklist_btn_delete_all:
showDialog(DIALOG_DELETE_ALL_ID);
break;
case R.id.tracklist_btn_export_all:
showDialog(DIALOG_EXPORT_ALL_ID);
break;
case R.id.tracklist_btn_import_all:
Intent intent = new Intent(this, ImportActivity.class)
.putExtra(ImportActivity.EXTRA_IMPORT_ALL, true);
startActivity(intent);
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_list);
listView = getListView();
listView.setOnCreateContextMenuListener(contextMenuListener);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindingChanged);
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
deleteAll.setOnClickListener(this);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
exportAll.setOnClickListener(this);
updateButtonsEnabled();
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
preferences.registerOnSharedPreferenceChangeListener(this);
metricUnits =
preferences.getBoolean(getString(R.string.metric_units_key), true);
recordingTrackId =
preferences.getLong(getString(R.string.recording_track_key), -1);
tracksCursor = getContentResolver().query(
TracksColumns.CONTENT_URI, null, null, null, "_id DESC");
startManagingCursor(tracksCursor);
setListAdapter();
}
@Override
protected void onStart() {
super.onStart();
serviceConnection.bindIfRunning();
}
@Override
protected void onDestroy() {
serviceConnection.unbind();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.search_only, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_search:
onSearchRequested();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_INSTALL_EARTH_ID:
return PlayTrackUtils.createInstallEarthDialog(this);
case DIALOG_EXPORT_ALL_ID:
String exportFileFormat = getString(R.string.track_list_export_file);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
String[] choices = new String[fileTypes.length];
for (int i = 0; i < fileTypes.length; i++) {
choices[i] = String.format(exportFileFormat, fileTypes[i]);
}
return new AlertDialog.Builder(this)
.setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
Intent intent = new Intent(TrackList.this, ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT,
(Parcelable) TrackFileFormat.values()[index]);
TrackList.this.startActivity(intent);
}
})
.setSingleChoiceItems(choices, 0, null)
.setTitle(R.string.track_list_export_all)
.create();
case DIALOG_DELETE_ALL_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.track_list_delete_all_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyTracksProviderUtils.Factory.get(TrackList.this).deleteAllTracks();
SharedPreferences sharedPreferences = getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
// TODO: Go through data manager
editor.putLong(getString(R.string.selected_track_key), -1L);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
});
case DIALOG_DELETE_CURRENT_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.track_list_delete_track_confirm_message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyTracksProviderUtils.Factory.get(TrackList.this).deleteTrack(trackId);
// If the deleted track was selected, unselect it.
String selectedTrackKey = getString(R.string.selected_track_key);
SharedPreferences sharedPreferences = getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
if (sharedPreferences.getLong(selectedTrackKey, -1L) == trackId) {
Editor editor = sharedPreferences.edit().putLong(selectedTrackKey, -1L);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
}
});
default:
return null;
}
}
private void updateButtonsEnabled() {
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
boolean notRecording = !isRecording();
deleteAll.setEnabled(notRecording);
exportAll.setEnabled(notRecording);
}
private void setListAdapter() {
// Get a cursor with all tracks
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
this,
R.layout.mytracks_list_item,
tracksCursor,
new String[] { TracksColumns.NAME, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.DESCRIPTION,
TracksColumns.CATEGORY },
new int[] { R.id.track_list_item_name, R.id.track_list_item_time,
R.id.track_list_item_stats, R.id.track_list_item_description,
R.id.track_list_item_category });
final int startTimeIdx =
tracksCursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
final int totalTimeIdx =
tracksCursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
final int totalDistanceIdx =
tracksCursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
TextView textView = (TextView) view;
if (columnIndex == startTimeIdx) {
long time = cursor.getLong(startTimeIdx);
textView.setText(StringUtils.formatDateTime(TrackList.this, time));
} else if (columnIndex == totalDistanceIdx) {
double totalDistance = cursor.getDouble(totalDistanceIdx);
long totalTime = cursor.getLong(totalTimeIdx);
String totalDistanceStr = StringUtils.formatTimeDistance(
TrackList.this, totalTime, totalDistance, metricUnits);
textView.setText(totalDistanceStr);
} else {
textView.setText(cursor.getString(columnIndex));
if (textView.getText().length() < 1) {
textView.setVisibility(View.GONE);
} else {
textView.setVisibility(View.VISIBLE);
}
}
return true;
}
});
setListAdapter(adapter);
}
private boolean isRecording() {
return ServiceUtils.isRecording(TrackList.this, serviceConnection.getServiceIfBound(), preferences);
}
}
@@ -0,0 +1,395 @@
/*
* Copyright 2008 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.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
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.Editor;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
/**
* An activity displaying a list of tracks.
*
* @author Leif Hendrik Wilden
*/
public class TrackListActivity extends Activity {
private static final String TAG = TrackListActivity.class.getSimpleName();
private static final int DIALOG_EXPORT_ALL_ID = 0;
private static final int DIALOG_DELETE_ALL_ID = 1;
// Callback when the trackRecordingServiceConnection binding changes
private final Runnable bindChangedCallback = new Runnable() {
@Override
public void run() {
synchronized (trackRecordingServiceConnection) {
if (startNewRecording) {
startRecording();
} else {
updateMenu();
adapter.notifyDataSetChanged();
}
}
}
};
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private SharedPreferences sharedPreferences;
private boolean metricUnits;
private long recordingTrackId;
private ListView listView;
private ResourceCursorAdapter adapter;
// True to start a new recording
private boolean startNewRecording = false;
private MenuItem recordTrack;
private MenuItem stopRecording;
private MenuItem search;
private MenuItem importAll;
private MenuItem exportAll;
private MenuItem deleteAll;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.track_list);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(
this, bindChangedCallback);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences.registerOnSharedPreferenceChangeListener(
new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (key == null) {
return;
}
if (key.equals(getString(R.string.metric_units_key))) {
metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true);
}
if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId = sharedPreferences.getLong(
getString(R.string.recording_track_key), -1L);
}
adapter.notifyDataSetChanged();
}
});
metricUnits = sharedPreferences.getBoolean(getString(R.string.metric_units_key), true);
recordingTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1L);
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
Cursor trackCursor = myTracksProviderUtils.getTracksCursor(
null, null, TracksColumns._ID + " DESC");
startManagingCursor(trackCursor);
listView = (ListView) findViewById(R.id.track_list);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
setResult(Constants.SHOW_TRACK, new Intent().putExtra("trackid", id));
finish();
}
});
adapter = new ResourceCursorAdapter(this, R.layout.track_list_item, trackCursor, true) {
@Override
public void bindView(View view, Context context, Cursor cursor) {
int idIndex = cursor.getColumnIndex(TracksColumns._ID);
int nameIndex = cursor.getColumnIndex(TracksColumns.NAME);
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int timeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int distanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int startIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
boolean isRecording = cursor.getLong(idIndex) == recordingTrackId;
TextView name = (TextView) view.findViewById(R.id.track_list_item_name);
name.setText(cursor.getString(nameIndex));
name.setCompoundDrawablesWithIntrinsicBounds(
isRecording ? R.drawable.menu_record_track : R.drawable.track, 0, 0, 0);
TextView category = (TextView) view.findViewById(R.id.track_list_item_category);
category.setText(cursor.getString(categoryIndex));
TextView time = (TextView) view.findViewById(R.id.track_list_item_time);
time.setText(StringUtils.formatElapsedTime(cursor.getLong(timeIndex)));
time.setVisibility(isRecording ? View.GONE : View.VISIBLE);
TextView distance = (TextView) view.findViewById(R.id.track_list_item_distance);
distance.setText(StringUtils.formatDistance(
TrackListActivity.this, cursor.getDouble(distanceIndex), metricUnits));
distance.setVisibility(isRecording ? View.GONE : View.VISIBLE);
TextView start = (TextView) view.findViewById(R.id.track_list_item_start);
start.setText(
StringUtils.formatDateTime(TrackListActivity.this, cursor.getLong(startIndex)));
start.setVisibility(start.getText().equals(name.getText()) ? View.GONE : View.VISIBLE);
TextView description = (TextView) view.findViewById(R.id.track_list_item_description);
description.setText(cursor.getString(descriptionIndex));
description.setVisibility(description.getText().length() == 0 ? View.GONE : View.VISIBLE);
}
};
listView.setAdapter(adapter);
}
@Override
protected void onStart() {
super.onStart();
trackRecordingServiceConnection.bindIfRunning();
}
@Override
protected void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection.unbind();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_list, menu);
recordTrack = menu.findItem(R.id.menu_record_track);
stopRecording = menu.findItem(R.id.menu_stop_recording);
search = menu.findItem(R.id.menu_search);
importAll = menu.findItem(R.id.menu_import_all);
exportAll = menu.findItem(R.id.menu_export_all);
deleteAll = menu.findItem(R.id.menu_delete_all);
ApiAdapterFactory.getApiAdapter().configureSearchWidget(this, search);
updateMenu();
return true;
}
/**
* Updates the menu based on whether My Tracks is recording or not.
*/
private void updateMenu() {
boolean isRecording = ServiceUtils.isRecording(
this, trackRecordingServiceConnection.getServiceIfBound(), sharedPreferences);
updateMenuItems(isRecording);
}
/**
* Updates the menu items.
*
* @param isRecording true if recording
*/
private void updateMenuItems(boolean isRecording) {
if (recordTrack != null) {
recordTrack.setVisible(!isRecording);
}
if (stopRecording != null) {
stopRecording.setVisible(isRecording);
}
if (importAll != null) {
importAll.setVisible(!isRecording);
}
if (exportAll != null) {
exportAll.setVisible(!isRecording);
}
if (deleteAll != null) {
deleteAll.setVisible(!isRecording);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_record_track:
updateMenuItems(true);
synchronized (trackRecordingServiceConnection) {
startNewRecording = true;
trackRecordingServiceConnection.startAndBind();
/*
* If the binding has happened, then invoke the callback to start a
* new recording. If the binding hasn't happened, then invoking the
* callback will have no effect. But when the binding occurs, the
* callback will get invoked.
*/
bindChangedCallback.run();
}
return true;
case R.id.menu_stop_recording:
updateMenuItems(false);
stopRecording();
trackRecordingServiceConnection.stop();
return true;
case R.id.menu_search:
return ApiAdapterFactory.getApiAdapter().handleSearchMenuSelection(this);
case R.id.menu_import_all:
startActivity(new Intent(this, ImportActivity.class)
.putExtra(ImportActivity.EXTRA_IMPORT_ALL, true));
return true;
case R.id.menu_export_all:
showDialog(DIALOG_EXPORT_ALL_ID);
return true;
case R.id.menu_delete_all:
showDialog(DIALOG_DELETE_ALL_ID);
return true;
case R.id.menu_aggregated_statistics:
startActivity(new Intent(this, AggregatedStatsActivity.class));
return true;
case R.id.menu_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_help:
startActivity(new Intent(this, WelcomeActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Starts a new recording.
*/
private void startRecording() {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
try {
recordingTrackId = service.startNewTrack();
startNewRecording = false;
adapter.notifyDataSetChanged();
Toast.makeText(TrackListActivity.this, R.string.track_record_success, Toast.LENGTH_SHORT)
.show();
Log.d(TAG, "Started a new recording");
} catch (Exception e) {
Toast.makeText(TrackListActivity.this, R.string.track_record_error, Toast.LENGTH_LONG)
.show();
Log.d(TAG, "Unable to start a new recording.", e);
}
} else {
Log.d(TAG, "service not available to start a new recording");
}
}
/**
* Stops the current recording.
*/
private void stopRecording() {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
try {
service.endCurrentTrack();
if (recordingTrackId != -1L) {
Intent intent = new Intent(this, TrackDetail.class)
.putExtra(TrackDetail.SHOW_CANCEL, false)
.putExtra(TrackDetail.TRACK_ID, recordingTrackId);
startActivity(intent);
}
recordingTrackId = -1L;
adapter.notifyDataSetChanged();
} catch (Exception e) {
Log.d(TAG, "Unable to stop recording.", e);
}
}
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_EXPORT_ALL_ID:
return createExportAllDialog();
case DIALOG_DELETE_ALL_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.track_list_delete_all_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyTracksProviderUtils.Factory.get(TrackListActivity.this).deleteAllTracks();
/*
* TODO Verify that selected_track_key is still needed with the
* ICS navigation design
*/
Editor editor = sharedPreferences.edit();
// TODO: Go through data manager
editor.putLong(getString(R.string.selected_track_key), -1L);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
});
default:
return null;
}
}
/**
* Creates an export all dialog.
*/
private AlertDialog createExportAllDialog() {
String exportFileFormat = getString(R.string.track_list_export_file);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
String[] choices = new String[fileTypes.length];
for (int i = 0; i < fileTypes.length; i++) {
choices[i] = String.format(exportFileFormat, fileTypes[i]);
}
return new AlertDialog.Builder(this).setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
Intent intent = new Intent(TrackListActivity.this, ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT,
(Parcelable) TrackFileFormat.values()[index]);
TrackListActivity.this.startActivity(intent);
}
})
.setSingleChoiceItems(choices, 0, null)
.setTitle(R.string.track_list_export_all)
.create();
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SEARCH) {
if (ApiAdapterFactory.getApiAdapter().handleSearchKey(search)) {
return true;
}
}
return super.onKeyUp(keyCode, event);
}
}
@@ -16,18 +16,37 @@
package com.google.android.apps.mytracks.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.view.MenuItem;
import android.view.Window;
import android.widget.SearchView;
/**
* API level 11 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
public class Api11Adapter extends Api9Adapter {
@TargetApi(11)
public class Api11Adapter extends Api10Adapter {
@Override
public void showActionBar(Activity activity) {
activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menuItem.getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName()));
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
// Returns false to allow the platform to expand the search widget.
return false;
}
}
@@ -0,0 +1,35 @@
/*
* 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.util;
import android.annotation.TargetApi;
import android.view.MenuItem;
/**
* API level 14 specific implementation of the {@link ApiAdapter}.
*
* @author Jimmy Shih
*/
@TargetApi(14)
public class Api14Adapter extends Api11Adapter {
@Override
public boolean handleSearchKey(MenuItem menuItem) {
menuItem.expandActionView();
return true;
}
}
@@ -30,6 +30,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import android.view.MenuItem;
import android.view.Window;
import java.io.IOException;
@@ -108,4 +109,21 @@ public class Api7Adapter implements ApiAdapter {
// via the regular menu.
activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
// Do nothing
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
activity.onSearchRequested();
return true;
}
@Override
public boolean handleSearchKey(MenuItem menuItem) {
// Return false and allow the framework to handle the search key.
return false;
}
}
@@ -24,6 +24,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.SharedPreferences;
import android.view.MenuItem;
import java.io.IOException;
@@ -110,4 +111,31 @@ public interface ApiAdapter {
*/
public void showActionBar(Activity activity);
/**
* Configures the search widget.
*
* Due to changes in API level 11.
*
* @param activity the activity
* @param menuItem the search menu item
*/
public void configureSearchWidget(Activity activity, MenuItem menuItem);
/**
* Handles the search menu selection. Returns true if handled.
*
* Due to changes in API level 11.
*
* @param activity the activity
*/
public boolean handleSearchMenuSelection(Activity activity);
/**
* Handles the search key press. Returns true if handled.
*
* Due to changes in API level 14.
*
* @param menu the search menu
*/
public boolean handleSearchKey(MenuItem menu);
}
@@ -31,7 +31,10 @@ public class ApiAdapterFactory {
*/
public static ApiAdapter getApiAdapter() {
if (apiAdapter == null) {
if (Build.VERSION.SDK_INT >= 11) {
if (Build.VERSION.SDK_INT >= 14) {
apiAdapter = new Api14Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 11) {
apiAdapter = new Api11Adapter();
return apiAdapter;
} else if (Build.VERSION.SDK_INT >= 10) {