Merge heads

This commit is contained in:
youtaol
2013-08-15 09:46:14 +08:00
parent 4d8c44782e
commit d64b6a4d93
30 changed files with 785 additions and 189 deletions
+3
View File
@@ -107,6 +107,9 @@ limitations under the License.
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.faketouch"
android:required="false" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+62 -34
View File
@@ -24,9 +24,9 @@ limitations under the License.
android:shrinkColumns="1"
android:stretchColumns="1"
style="@style/ListItem" >
<TableRow
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<!-- First row -->
<TableRow>
<!-- First column -->
<ImageView
android:contentDescription="@string/icon_track"
android:id="@+id/list_item_icon"
@@ -35,39 +35,67 @@ limitations under the License.
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_width="wrap_content" />
<TableLayout
<!-- Second column -->
<LinearLayout
android:baselineAligned="false"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:shrinkColumns="0"
android:stretchColumns="0" >
<TableRow>
<TextView
android:id="@+id/list_item_name"
android:layout_gravity="bottom"
android:layout_width="fill_parent"
android:singleLine="true"
style="@style/TextMedium" />
<TextView
android:id="@+id/list_item_date"
style="@style/ListItemDate" />
</TableRow>
<TableRow>
<!-- Need to match the style set in ListItemUtils.setListItem -->
<TextView
android:id="@+id/list_item_time_distance"
android:layout_gravity="bottom"
android:layout_width="fill_parent"
android:singleLine="true"
style="@style/TextSmall" />
<TextView
android:id="@+id/list_item_time"
style="@style/ListItemDate" />
</TableRow>
</TableLayout>
android:layout_width="match_parent" >
<LinearLayout
android:layout_height="wrap_content"
android:layout_weight="3"
android:layout_width="0dp" >
<TableLayout
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="0dp"
android:shrinkColumns="*"
android:stretchColumns="*" >
<TableRow>
<TextView
android:ellipsize="end"
android:id="@+id/list_item_name"
android:singleLine="true"
style="@style/TextMedium" />
</TableRow>
<TableRow>
<!-- Need to match the style set in ListItemUtils.setListItem -->
<TextView
android:ellipsize="end"
android:id="@+id/list_item_time_distance"
android:singleLine="true"
style="@style/TextSmall" />
</TableRow>
</TableLayout>
<ImageView
android:id="@+id/list_item_photo"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:layout_width="0dp"
android:scaleType="centerCrop" />
</LinearLayout>
<TableLayout
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="0dp"
android:shrinkColumns="*"
android:stretchColumns="*" >
<TableRow>
<TextView
android:id="@+id/list_item_date"
style="@style/ListItemDate" />
</TableRow>
<TableRow>
<TextView
android:id="@+id/list_item_time"
style="@style/ListItemDate" />
</TableRow>
</TableLayout>
</LinearLayout>
</TableRow>
<TableRow
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<!-- Second row -->
<TableRow>
<TextView
android:id="@+id/list_item_description"
android:layout_column="1"
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2013 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent" >
<android.support.v4.view.ViewPager
android:id="@+id/maker_detail_activity_view_pager"
android:layout_height="match_parent"
android:layout_width="match_parent" />
</LinearLayout>
@@ -21,14 +21,19 @@ limitations under the License.
android:layout_width="match_parent"
android:orientation="vertical"
android:padding="8dp" >
<TextView
android:id="@+id/marker_detail_name"
style="@style/TextLarge" />
<LinearLayout
android:id="@+id/marker_detail_waypoint_section"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/marker_detail_waypoint_photo"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:scaleType="centerInside" />
<TextView
android:id="@+id/marker_detail_waypoint_name"
style="@style/TextLarge.WithTopMargin" />
<TextView
android:id="@+id/marker_detail_waypoint_marker_type"
style="@style/TextLarge.WithTopMargin" />
@@ -41,6 +46,9 @@ limitations under the License.
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/marker_detail_statistics_name"
style="@style/TextLarge" />
<TextView
android:text="@string/marker_detail_statistics_title"
style="@style/TextLarge.WithTopMargin" />
+1 -1
View File
@@ -17,7 +17,7 @@ limitations under the License.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:icon="@drawable/ic_menu_map_layer"
android:orderInCategory="5"
android:orderInCategory="6"
android:showAsAction="ifRoom"
android:title="@string/menu_map_layer">
<menu>
+19 -13
View File
@@ -21,68 +21,74 @@ limitations under the License.
android:orderInCategory="1"
android:showAsAction="ifRoom"
android:title="@string/menu_insert_marker"/>
<item
android:icon="@drawable/ic_menu_insert_photo"
android:id="@+id/track_detail_insert_photo"
android:orderInCategory="2"
android:showAsAction="ifRoom"
android:title="@string/menu_insert_photo"/>
<item
android:icon="@drawable/ic_menu_play"
android:id="@+id/track_detail_play"
android:orderInCategory="2"
android:orderInCategory="3"
android:showAsAction="ifRoom"
android:title="@string/menu_play"/>
<item
android:icon="@drawable/ic_menu_share"
android:id="@+id/track_detail_share"
android:orderInCategory="3"
android:orderInCategory="4"
android:showAsAction="ifRoom"
android:title="@string/menu_share"/>
<item
android:icon="@drawable/ic_menu_markers"
android:id="@+id/track_detail_markers"
android:orderInCategory="4"
android:orderInCategory="5"
android:showAsAction="ifRoom"
android:title="@string/menu_markers"/>
<!-- android:orderInCategory="5" is in map.xml -->
<!-- android:orderInCategory="6" is in map.xml -->
<item
android:icon="@drawable/ic_menu_export"
android:id="@+id/track_detail_export"
android:orderInCategory="6"
android:orderInCategory="7"
android:title="@string/menu_export"/>
<item
android:icon="@drawable/ic_menu_edit"
android:id="@+id/track_detail_edit"
android:orderInCategory="7"
android:orderInCategory="8"
android:title="@string/menu_edit"/>
<item
android:icon="@drawable/ic_menu_delete"
android:id="@+id/track_detail_delete"
android:orderInCategory="8"
android:orderInCategory="9"
android:title="@string/menu_delete"/>
<item
android:icon="@drawable/ic_menu_voice_frequency"
android:id="@+id/track_detail_voice_frequency"
android:orderInCategory="9"
android:orderInCategory="10"
android:title="@string/menu_voice_frequency"/>
<item
android:icon="@drawable/ic_menu_split_frequency"
android:id="@+id/track_detail_split_frequency"
android:orderInCategory="10"
android:orderInCategory="11"
android:title="@string/menu_split_frequency"/>
<item
android:icon="@drawable/ic_menu_sensor_state"
android:id="@+id/track_detail_sensor_state"
android:orderInCategory="11"
android:orderInCategory="12"
android:title="@string/menu_sensor_state"/>
<item
android:icon="@drawable/ic_menu_settings"
android:id="@+id/track_detail_settings"
android:orderInCategory="12"
android:orderInCategory="13"
android:title="@string/menu_settings"/>
<item
android:icon="@drawable/ic_menu_feedback"
android:id="@+id/track_detail_feedback"
android:orderInCategory="13"
android:orderInCategory="14"
android:title="@string/menu_feedback"/>
<item
android:icon="@drawable/ic_menu_help"
android:id="@+id/track_detail_help"
android:orderInCategory="14"
android:orderInCategory="15"
android:title="@string/menu_help"/>
</menu>
+10
View File
@@ -1005,6 +1005,10 @@ limitations under the License.
translation_description="When deleting multiple markers, the confirmation message.">
All markers for this track will be permanently deleted from the device.
</string>
<string name="marker_insert_photo_error"
translation_description="The error message when unable to insert a photo marker.">
Unable to insert a photo marker. Try again.
</string>
<string name="marker_label_end"
translation_description="When inserting a marker to indicate the end of a track, the marker's
name. E.g., 'track_name (End)'.
@@ -1124,6 +1128,12 @@ limitations under the License.
Character limit: ~20 (1 line)">
Insert marker
</string>
<string name="menu_insert_photo"
translation_description="In the menu, to insert a photo.
Character limit: ~20 (1 line)">
Insert photo
</string>
<string name="menu_map"
translation_description="In the menu, under the map layer option, the submenu to display normal
map.
@@ -80,8 +80,9 @@ public abstract class AbstractSendToGoogleActivity extends AbstractMyTracksActiv
private static final int FUSION_TABLES_REQUEST_CODE = 1;
private static final int SPREADSHEETS_REQUEST_CODE = 2;
private static final int DELETE_REQUEST_CODE = 3;
protected static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 4;
protected static final int GOOGLE_PLAY_SERVICES_REQUEST_CODE = 4;
protected static final int CAMERA_REQUEST_CODE = 5;
private SendRequest sendRequest;
@Override
@@ -17,22 +17,22 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.fragments.DeleteMarkerDialogFragment;
import com.google.android.apps.mytracks.fragments.DeleteMarkerDialogFragment.DeleteMarkerCaller;
import com.google.android.apps.mytracks.fragments.MarkerDetailFragment;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
/**
* An activity to display marker detail info.
@@ -44,72 +44,59 @@ public class MarkerDetailActivity extends AbstractMyTracksActivity implements De
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = MarkerDetailActivity.class.getSimpleName();
private MyTracksProviderUtils myTracksProviderUtils;
private long markerId;
private Waypoint waypoint;
private TextView name;
private View waypointSection;
private View statisticsSection;
private ArrayList<Long> markerIds;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L);
if (markerId == -1L) {
Log.d(TAG, "invalid marker id");
finish();
return;
}
name = (TextView) findViewById(R.id.marker_detail_name);
waypointSection = findViewById(R.id.marker_detail_waypoint_section);
statisticsSection = findViewById(R.id.marker_detail_statistics_section);
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
waypoint = myTracksProviderUtils.getWaypoint(markerId);
markerIds = new ArrayList<Long>();
int markerIndex = -1;
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
* first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
Waypoint current = myTracksProviderUtils.createWaypoint(cursor);
markerIds.add(current.getId());
if (current.getId() == markerId) {
markerIndex = markerIds.size() - 1;
}
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
ViewPager viewPager = (ViewPager) findViewById(R.id.maker_detail_activity_view_pager);
viewPager.setAdapter(new MarkerDetailPagerAdapter(getSupportFragmentManager()));
if (markerIndex != -1) {
viewPager.setCurrentItem(markerIndex);
}
}
@Override
protected int getLayoutResId() {
return R.layout.marker_detail;
}
@Override
protected void onResume() {
super.onResume();
waypoint = MyTracksProviderUtils.Factory.get(this).getWaypoint(markerId);
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
finish();
return;
}
name.setText(getString(R.string.generic_name_line, waypoint.getName()));
if (waypoint.getType() == WaypointType.WAYPOINT) {
waypointSection.setVisibility(View.VISIBLE);
statisticsSection.setVisibility(View.GONE);
TextView markerType = (TextView) findViewById(R.id.marker_detail_waypoint_marker_type);
markerType.setText(
getString(R.string.marker_detail_waypoint_marker_type, waypoint.getCategory()));
TextView description = (TextView) findViewById(R.id.marker_detail_waypoint_description);
description.setText(getString(R.string.generic_description_line, waypoint.getDescription()));
} else {
waypointSection.setVisibility(View.GONE);
statisticsSection.setVisibility(View.VISIBLE);
StatsUtils.setTripStatisticsValues(this, waypoint.getTripStatistics());
StatsUtils.setLocationValues(this, waypoint.getLocation(), false);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.marker_detail, menu);
Track track = myTracksProviderUtils.getTrack(waypoint.getTrackId());
boolean isSharedWithMe = track != null ? track.isSharedWithMe() : true;
menu.findItem(R.id.marker_detail_edit).setVisible(!isSharedWithMe);
menu.findItem(R.id.marker_detail_delete).setVisible(!isSharedWithMe);
return true;
return R.layout.marker_detail_activity;
}
@Override
@@ -120,29 +107,6 @@ public class MarkerDetailActivity extends AbstractMyTracksActivity implements De
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Intent intent;
switch (item.getItemId()) {
case R.id.marker_detail_show_on_map:
intent = IntentUtils.newIntent(this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.newInstance(new long[] { markerId })
.show(getSupportFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onDeleteMarkerDone() {
runOnUiThread(new Runnable() {
@@ -152,4 +116,26 @@ public class MarkerDetailActivity extends AbstractMyTracksActivity implements De
}
});
}
}
/**
* Marker detail pager adapter.
*
* @author Jimmy Shih
*/
private class MarkerDetailPagerAdapter extends FragmentStatePagerAdapter {
public MarkerDetailPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@Override
public Fragment getItem(int position) {
return MarkerDetailFragment.newInstance(markerIds.get(position));
}
@Override
public int getCount() {
return markerIds.size();
}
}
}
@@ -169,6 +169,7 @@ public class MarkerEditActivity extends AbstractMyTracksActivity {
waypointName.getText().toString(),
waypointMarkerType.getText().toString(),
waypointDescription.getText().toString(),
null,
null);
TrackRecordingServiceConnectionUtils.addMarker(
this, trackRecordingServiceConnection, waypointCreationRequest);
@@ -63,7 +63,7 @@ public class MarkerListActivity extends AbstractMyTracksActivity implements Dele
private static final String[] PROJECTION = new String[] { WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TYPE, WaypointsColumns.TIME };
WaypointsColumns.TYPE, WaypointsColumns.TIME, WaypointsColumns.PHOTOURL };
// Callback when an item is selected in the contextual action mode
private ContextualActionModeCallback
@@ -173,19 +173,25 @@ public class MarkerListActivity extends AbstractMyTracksActivity implements Dele
public void bindView(View view, Context context, Cursor cursor) {
int typeIndex = cursor.getColumnIndex(WaypointsColumns.TYPE);
int nameIndex = cursor.getColumnIndex(WaypointsColumns.NAME);
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
int photoUrlIndex = cursor.getColumnIndex(WaypointsColumns.PHOTOURL);
int timeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
int categoryIndex = cursor.getColumnIndex(WaypointsColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(WaypointsColumns.DESCRIPTION);
boolean statistics = WaypointType.values()[cursor.getInt(typeIndex)]
== WaypointType.STATISTICS;
int iconId = statistics ? R.drawable.ic_marker_yellow_pushpin
: R.drawable.ic_marker_blue_pushpin;
String name = cursor.getString(nameIndex);
String sharedOwner = track.getSharedOwner();
String photoUrl = cursor.getString(photoUrlIndex);
long time = cursor.getLong(timeIndex);
String category = statistics ? null : cursor.getString(categoryIndex);
String description = statistics ? null : cursor.getString(descriptionIndex);
ListItemUtils.setListItem(MarkerListActivity.this, view, false, true, iconId,
R.string.icon_marker, cursor.getString(nameIndex), category, null, null,
cursor.getLong(timeIndex), description, track.getSharedOwner());
R.string.icon_marker, name, sharedOwner, null, null, photoUrl, time, category,
description);
}
};
listView.setAdapter(resourceCursorAdapter);
@@ -83,12 +83,14 @@ public class SearchListActivity extends AbstractSendToGoogleActivity implements
private static final String ICON_ID_FIELD = "icon";
private static final String ICON_CONTENT_DESCRIPTION_ID_FIELD = "iconContentDescription";
private static final String NAME_FIELD = "name";
private static final String CATEGORY_FIELD = "category";
private static final String SHARED_OWNER_FIELD = "sharedOwner";
private static final String TOTAL_TIME_FIELD = "totalTime";
private static final String TOTAL_DISTANCE_FIELD = "totalDistance";
private static final String PHOTO_URL_FIELD = "photoUrl";
private static final String START_TIME_FIELD = "startTime";
private static final String CATEGORY_FIELD = "category";
private static final String DESCRIPTION_FIELD = "description";
private static final String SHARED_OWNER_FIELD = "sharedOwner";
private static final String TRACK_ID_FIELD = "trackId";
private static final String MARKER_ID_FIELD = "markerId";
@@ -228,15 +230,17 @@ public class SearchListActivity extends AbstractSendToGoogleActivity implements
int iconId = (Integer) resultMap.get(ICON_ID_FIELD);
int iconContentDescriptionId = (Integer) resultMap.get(ICON_CONTENT_DESCRIPTION_ID_FIELD);
String name = (String) resultMap.get(NAME_FIELD);
String category = (String) resultMap.get(CATEGORY_FIELD);
String sharedOwner = (String) resultMap.get(SHARED_OWNER_FIELD);
String totalTime = (String) resultMap.get(TOTAL_TIME_FIELD);
String totalDistance = (String) resultMap.get(TOTAL_DISTANCE_FIELD);
String photoUrl = (String) resultMap.get(PHOTO_URL_FIELD);
Long startTime = (Long) resultMap.get(START_TIME_FIELD);
String category = (String) resultMap.get(CATEGORY_FIELD);
String description = (String) resultMap.get(DESCRIPTION_FIELD);
String sharedOwner = (String) resultMap.get(SHARED_OWNER_FIELD);
ListItemUtils.setListItem(SearchListActivity.this, view, isRecording, isPaused, iconId,
iconContentDescriptionId, name, category, totalTime, totalDistance, startTime,
description, sharedOwner);
iconContentDescriptionId, name, sharedOwner, totalTime, totalDistance, photoUrl,
startTime, category, description);
return view;
}
};
@@ -487,14 +491,15 @@ public class SearchListActivity extends AbstractSendToGoogleActivity implements
statistics ? R.drawable.ic_marker_yellow_pushpin : R.drawable.ic_marker_blue_pushpin);
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.icon_marker);
resultMap.put(NAME_FIELD, waypoint.getName());
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory());
resultMap.put(SHARED_OWNER_FIELD, sharedOwner);
// Display the marker's track name in the total time field
resultMap.put(TOTAL_TIME_FIELD, trackName == null ? null
: getString(R.string.search_list_marker_track_location, trackName));
resultMap.put(TOTAL_DISTANCE_FIELD, null);
resultMap.put(PHOTO_URL_FIELD, waypoint.getPhotoUrl());
resultMap.put(START_TIME_FIELD, waypoint.getLocation().getTime());
resultMap.put(CATEGORY_FIELD, statistics ? null : waypoint.getCategory());
resultMap.put(DESCRIPTION_FIELD, statistics ? null : waypoint.getDescription());
resultMap.put(SHARED_OWNER_FIELD, sharedOwner);
resultMap.put(TRACK_ID_FIELD, waypoint.getTrackId());
resultMap.put(MARKER_ID_FIELD, waypoint.getId());
}
@@ -515,13 +520,14 @@ public class SearchListActivity extends AbstractSendToGoogleActivity implements
resultMap.put(ICON_ID_FIELD, TrackIconUtils.getIconDrawable(icon));
resultMap.put(ICON_CONTENT_DESCRIPTION_ID_FIELD, R.string.icon_track);
resultMap.put(NAME_FIELD, track.getName());
resultMap.put(CATEGORY_FIELD, category);
resultMap.put(SHARED_OWNER_FIELD, track.getSharedOwner());
resultMap.put(TOTAL_TIME_FIELD, StringUtils.formatElapsedTime(tripStatitics.getTotalTime()));
resultMap.put(TOTAL_DISTANCE_FIELD,
StringUtils.formatDistance(this, tripStatitics.getTotalDistance(), metricUnits));
resultMap.put(PHOTO_URL_FIELD, null);
resultMap.put(START_TIME_FIELD, tripStatitics.getStartTime());
resultMap.put(CATEGORY_FIELD, category);
resultMap.put(DESCRIPTION_FIELD, track.getDescription());
resultMap.put(SHARED_OWNER_FIELD, track.getSharedOwner());
resultMap.put(TRACK_ID_FIELD, track.getId());
resultMap.put(MARKER_ID_FIELD, null);
}
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.fragments.ChartFragment;
import com.google.android.apps.mytracks.fragments.ConfirmDeleteDialogFragment;
@@ -36,6 +37,7 @@ import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection
import com.google.android.apps.mytracks.settings.SettingsActivity;
import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.GoogleFeedbackUtils;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
@@ -46,10 +48,15 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@@ -57,6 +64,12 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* An activity to show the track detail.
@@ -69,9 +82,15 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final String CURRENT_TAB_TAG_KEY = "current_tab_tag_key";
private static final String PHOTO_URI_KEY = "photo_uri_key";
private static final String HAS_PHOTO_KEY = "has_photo_key";
// The following are set in onCreate
private boolean hasCamera;
private Uri photoUri;
private boolean hasPhoto;
private MyTracksProviderUtils myTracksProviderUtils;
private SharedPreferences sharedPreferences;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -91,6 +110,7 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
private String sensorType = PreferencesUtils.SENSOR_TYPE_DEFAULT;
private MenuItem insertMarkerMenuItem;
private MenuItem insertPhotoMenuItem;
private MenuItem playMenuItem;
private MenuItem shareMenuItem;
private MenuItem exportMenuItem;
@@ -107,6 +127,19 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
@Override
public void run() {
trackController.update(trackId == recordingTrackId, recordingTrackPaused);
if (hasPhoto && photoUri != null) {
WaypointCreationRequest waypointCreationRequest = new WaypointCreationRequest(
WaypointType.WAYPOINT, false, null, null, null, null, photoUri.toString());
long id = TrackRecordingServiceConnectionUtils.addMarker(
TrackDetailActivity.this, trackRecordingServiceConnection, waypointCreationRequest);
hasPhoto = false;
if (id != -1L) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(photoUri);
sendBroadcast(mediaScanIntent);
}
}
}
});
}
@@ -182,6 +215,10 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
photoUri = savedInstanceState != null ? (Uri) savedInstanceState.getParcelable(PHOTO_URI_KEY) : null;
hasPhoto = savedInstanceState != null ? savedInstanceState.getBoolean(HAS_PHOTO_KEY, false) : false;
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
handleIntent(getIntent());
@@ -266,6 +303,19 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAB_TAG_KEY, tabHost.getCurrentTabTag());
if (photoUri != null) {
outState.putParcelable(PHOTO_URI_KEY, photoUri);
}
outState.putBoolean(HAS_PHOTO_KEY, hasPhoto);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAMERA_REQUEST_CODE) {
hasPhoto = resultCode == RESULT_OK;
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
@@ -309,6 +359,7 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
.setVisible(ApiAdapterFactory.getApiAdapter().isGoogleFeedbackAvailable());
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
insertPhotoMenuItem = menu.findItem(R.id.track_detail_insert_photo);
playMenuItem = menu.findItem(R.id.track_detail_play);
shareMenuItem = menu.findItem(R.id.track_detail_share);
@@ -338,6 +389,36 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
.putExtra(MarkerEditActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent);
return true;
case R.id.track_detail_insert_photo:
try {
if (!FileUtils.isExternalStorageWriteable()) {
Toast.makeText(this, R.string.external_storage_not_writable, Toast.LENGTH_LONG).show();
return false;
}
File dir = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
FileUtils.SDCARD_TOP_DIR);
FileUtils.ensureDirectoryExists(dir);
String fileName = SimpleDateFormat.getDateTimeInstance().format(new Date());
File file = new File(dir, FileUtils.buildUniqueFileName(dir, fileName, "jpeg"));
if (file.exists()) {
Toast.makeText(this, R.string.marker_insert_photo_error, Toast.LENGTH_LONG).show();
return false;
}
file.createNewFile();
photoUri = Uri.fromFile(file);
intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(
MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intent, CAMERA_REQUEST_CODE);
} catch (IOException e) {
Log.e(TAG, "Unable to insert photo marker", e);
return false;
}
return true;
case R.id.track_detail_play:
confirmPlay(new long[] {trackId});
return true;
@@ -513,6 +594,9 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
if (insertMarkerMenuItem != null) {
insertMarkerMenuItem.setVisible(isRecording && !isPaused);
}
if (insertPhotoMenuItem != null) {
insertPhotoMenuItem.setVisible(hasCamera && isRecording && !isPaused);
}
if (playMenuItem != null) {
playMenuItem.setVisible(!isRecording);
}
@@ -361,28 +361,28 @@ public class TrackListActivity extends AbstractSendToGoogleActivity
int idIndex = cursor.getColumnIndex(TracksColumns._ID);
int iconIndex = cursor.getColumnIndex(TracksColumns.ICON);
int nameIndex = cursor.getColumnIndex(TracksColumns.NAME);
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int sharedOwnerIndex = cursor.getColumnIndex(TracksColumns.SHAREDOWNER);
int totalTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int totalDistanceIndex = cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
int startTimeIndex = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
int sharedOwnerIndex = cursor.getColumnIndex(TracksColumns.SHAREDOWNER);
boolean isRecording = cursor.getLong(idIndex) == recordingTrackId;
String icon = cursor.getString(iconIndex);
int iconId = TrackIconUtils.getIconDrawable(icon);
String name = cursor.getString(nameIndex);
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
String sharedOwner = cursor.getString(sharedOwnerIndex);
String totalTime = StringUtils.formatElapsedTime(cursor.getLong(totalTimeIndex));
String totalDistance = StringUtils.formatDistance(
TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits);
long startTime = cursor.getLong(startTimeIndex);
long startTime = cursor.getLong(startTimeIndex);
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
String description = cursor.getString(descriptionIndex);
String sharedOwner = cursor.getString(sharedOwnerIndex);
ListItemUtils.setListItem(TrackListActivity.this, view, isRecording, recordingTrackPaused,
iconId, R.string.icon_track, name, category, totalTime, totalDistance, startTime,
description, sharedOwner);
iconId, R.string.icon_track, name, sharedOwner, totalTime, totalDistance, null,
startTime, category, description);
}
};
listView.setAdapter(sectionResourceCursorAdapter);
@@ -49,7 +49,7 @@ import java.util.ArrayList;
public class MyTracksProvider extends ContentProvider {
private static final String TAG = MyTracksProvider.class.getSimpleName();
private static final int DATABASE_VERSION = 21;
private static final int DATABASE_VERSION = 22;
@VisibleForTesting
static final String DATABASE_NAME = "mytracks.db";
@@ -124,6 +124,12 @@ public class MyTracksProvider extends ContentProvider {
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.SHAREDOWNER
+ " STRING");
}
if (oldVersion <= 21) {
Log.w(TAG, "Upgrade DB: Adding photo url column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD " + WaypointsColumns.PHOTOURL
+ " STRING");
}
}
}
}
@@ -0,0 +1,209 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.MarkerEditActivity;
import com.google.android.apps.mytracks.TrackDetailActivity;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.PhotoUtils;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
/**
* A fragment to show marker details.
*
* @author Jimmy Shih
*/
public class MarkerDetailFragment extends Fragment {
private static final String TAG = MarkerDetailFragment.class.getSimpleName();
private static final String KEY_MARKER_ID = "markerId";
public static MarkerDetailFragment newInstance(long markerId) {
MarkerDetailFragment fragment = new MarkerDetailFragment();
Bundle bundle = new Bundle();
bundle.putLong(KEY_MARKER_ID, markerId);
fragment.setArguments(bundle);
return fragment;
}
private MyTracksProviderUtils myTracksProviderUtils;
private Waypoint waypoint;
private Bitmap bitmap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long markerId = getArguments().getLong(KEY_MARKER_ID);
if (markerId == -1L) {
Log.d(TAG, "invalid marker id");
getFragmentManager().popBackStack();
return;
}
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(getActivity());
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
return inflater.inflate(R.layout.marker_detail_fragment, container, false);
};
@Override
public void onResume() {
super.onResume();
// Need to update the waypoint in case returning after an edit
updateWaypoint(true);
update();
}
public void onPause() {
super.onPause();
if (bitmap != null) {
bitmap.recycle();
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.marker_detail, menu);
updateWaypoint(false);
Track track = myTracksProviderUtils.getTrack(waypoint.getTrackId());
boolean isSharedWithMe = track != null ? track.isSharedWithMe() : true;
menu.findItem(R.id.marker_detail_edit).setVisible(!isSharedWithMe);
menu.findItem(R.id.marker_detail_delete).setVisible(!isSharedWithMe);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
long markerId = getArguments().getLong(KEY_MARKER_ID);
FragmentActivity fragmentActivity = getActivity();
Intent intent;
switch (item.getItemId()) {
case R.id.marker_detail_show_on_map:
intent = IntentUtils.newIntent(fragmentActivity, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId);
startActivity(intent);
return true;
case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.newInstance(new long[] { markerId })
.show(getChildFragmentManager(), DeleteMarkerDialogFragment.DELETE_MARKER_DIALOG_TAG);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Updates the waypoint.
*
* @param refresh true to always update
*/
private void updateWaypoint(boolean refresh) {
if (refresh || waypoint == null) {
waypoint = myTracksProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID));
if (waypoint == null) {
Log.d(TAG, "waypoint is null");
getFragmentManager().popBackStack();
}
}
}
/**
* Updates the UI.
*/
private void update() {
View waypointSection = getView().findViewById(R.id.marker_detail_waypoint_section);
View statisticsSection = getView().findViewById(R.id.marker_detail_statistics_section);
if (waypoint.getType() == WaypointType.WAYPOINT) {
waypointSection.setVisibility(View.VISIBLE);
statisticsSection.setVisibility(View.GONE);
ImageView imageView = (ImageView) getView().findViewById(R.id.marker_detail_waypoint_photo);
String photoUrl = waypoint.getPhotoUrl();
if (photoUrl == null || photoUrl.equals("")) {
imageView.setVisibility(View.GONE);
} else {
imageView.setVisibility(View.VISIBLE);
Display defaultDisplay = getActivity().getWindowManager().getDefaultDisplay();
@SuppressWarnings("deprecation")
int displayWidth = defaultDisplay.getWidth();
@SuppressWarnings("deprecation")
int displayHeight = defaultDisplay.getHeight();
bitmap = PhotoUtils.setImageVew(
imageView, Uri.parse(photoUrl), displayWidth, displayHeight, true);
}
TextView name = (TextView) getView().findViewById(R.id.marker_detail_waypoint_name);
name.setText(getString(R.string.generic_name_line, waypoint.getName()));
TextView markerType = (TextView) getView()
.findViewById(R.id.marker_detail_waypoint_marker_type);
markerType.setText(
getString(R.string.marker_detail_waypoint_marker_type, waypoint.getCategory()));
TextView description = (TextView) getView()
.findViewById(R.id.marker_detail_waypoint_description);
description.setText(getString(R.string.generic_description_line, waypoint.getDescription()));
} else {
waypointSection.setVisibility(View.GONE);
statisticsSection.setVisibility(View.VISIBLE);
TextView name = (TextView) getView().findViewById(R.id.marker_detail_statistics_name);
name.setText(getString(R.string.generic_name_line, waypoint.getName()));
StatsUtils.setTripStatisticsValues(getActivity(), waypoint.getTripStatistics());
StatsUtils.setLocationValues(getActivity(), waypoint.getLocation(), false);
}
}
}
@@ -282,7 +282,7 @@ abstract class AbstractImporter extends DefaultHandler {
// Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription,
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
-1L, -1L, trackPoint, tripStatistics);
-1L, -1L, trackPoint, tripStatistics, "");
myTracksProviderUtils.insertWaypoint(newWaypoint);
}
waypoint = null;
@@ -584,7 +584,7 @@ abstract class AbstractImporter extends DefaultHandler {
waypointLocation.setLongitude(180);
Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon,
track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation,
tripStatistics);
tripStatistics, "");
myTracksProviderUtils.insertWaypoint(waypoint);
}
@@ -499,9 +499,13 @@ public class TrackRecordingService extends Service {
duration = 0L;
}
String photoUrl = waypointCreationRequest.getPhotoUrl() != null ? waypointCreationRequest
.getPhotoUrl()
: "";
// Insert waypoint
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId,
waypointType, length, duration, -1L, -1L, location, tripStatistics);
waypointType, length, duration, -1L, -1L, location, tripStatistics, photoUrl);
Uri uri = myTracksProviderUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment());
}
@@ -37,8 +37,7 @@ public class FileUtils {
* Name of the top-level directory inside the SD card where our files will be
* read from/written to.
*/
@VisibleForTesting
static final String SDCARD_TOP_DIR = "MyTracks";
public static final String SDCARD_TOP_DIR = "MyTracks";
/**
* The maximum FAT32 path length. See the FAT32 spec at
@@ -18,8 +18,11 @@ package com.google.android.apps.mytracks.util;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.text.format.DateUtils;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -38,23 +41,25 @@ public class ListItemUtils {
/**
* Sets a list item.
*
* @param context the context
* @param activity the activity
* @param view the list item view
* @param isRecording true if recording
* @param isPaused true if paused
* @param name the name value
* @param iconId the icon id
* @param iconContentDescriptionId the icon content description id
* @param category the category value
* @param name the name value
* @param sharedOwner if shared with me track, the owner, else null
* @param totalTime the total time value
* @param totalDistance the total distance value
* @param photoUrl the photo url
* @param startTime the start time value
* @param category the category value
* @param description the description value
* @param sharedOwner if shared with me track, the owner, else null
*/
public static void setListItem(Context context, View view, boolean isRecording, boolean isPaused,
int iconId, int iconContentDescriptionId, String name, String category, String totalTime,
String totalDistance, long startTime, String description, String sharedOwner) {
public static void setListItem(Activity activity, View view, boolean isRecording,
boolean isPaused, int iconId, int iconContentDescriptionId, String name, String sharedOwner,
String totalTime, String totalDistance, String photoUrl, long startTime, String category,
String description) {
if (isRecording) {
iconId = isPaused ? R.drawable.ic_track_paused : R.drawable.ic_track_recording;
@@ -64,29 +69,45 @@ public class ListItemUtils {
ImageView iconImageView = (ImageView) view.findViewById(R.id.list_item_icon);
iconImageView.setImageResource(iconId);
iconImageView.setContentDescription(context.getString(iconContentDescriptionId));
iconImageView.setContentDescription(activity.getString(iconContentDescriptionId));
// Set name
TextView nameTextView = (TextView) view.findViewById(R.id.list_item_name);
nameTextView.setText(name);
// Set sharedOwner/totalTime/totalDistance
TextView timeDistanceTextView = (TextView) view.findViewById(R.id.list_item_time_distance);
if (isRecording) {
timeDistanceTextView.setTextColor(context.getResources()
timeDistanceTextView.setTextColor(activity.getResources()
.getColor(isPaused ? android.R.color.white : R.color.recording_text));
} else {
// Need to match the style set in list_item.xml
timeDistanceTextView.setTextAppearance(context, R.style.TextSmall);
timeDistanceTextView.setTextAppearance(activity, R.style.TextSmall);
}
setTextView(timeDistanceTextView,
getTimeDistance(context, isRecording, isPaused, sharedOwner, totalTime, totalDistance));
getTimeDistance(activity, isRecording, isPaused, sharedOwner, totalTime, totalDistance));
String[] startTimeDisplay = getStartTime(isRecording, context, startTime);
// Set photoUrl
ImageView photo = (ImageView) view.findViewById(R.id.list_item_photo);
if (photoUrl == null || photoUrl.equals("")) {
photo.setVisibility(View.GONE);
} else {
photo.setVisibility(View.VISIBLE);
Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
// Set the initial width to 35% of the display width
int width = (int) (defaultDisplay.getWidth() * .35);
PhotoUtils.setImageVew(photo, Uri.parse(photoUrl), width, 0, false);
}
// Set date/time
String[] startTimeDisplay = getStartTime(isRecording, activity, startTime);
TextView dateTextView = (TextView) view.findViewById(R.id.list_item_date);
setTextView(dateTextView, startTimeDisplay[0]);
TextView timeTextView = (TextView) view.findViewById(R.id.list_item_time);
setTextView(timeTextView, startTimeDisplay[1]);
// Set category/description
TextView descriptionTextView = (TextView) view.findViewById(R.id.list_item_description);
setTextView(descriptionTextView, getDescription(isRecording, category, description));
}
@@ -0,0 +1,160 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
import java.io.IOException;
/**
* Utilities for photos.
*
* @author Jimmy Shih
*/
public class PhotoUtils {
private static final String TAG = PhotoUtils.class.getSimpleName();
private PhotoUtils() {}
/**
* Sets an image view.
*
* @param imageView the image view
* @param uri the image uri
* @param displayWidth the display width
* @param displayHeight the display height
* @param fitWithIn true to fit within the display area. False to crop and fill
* the display area.
*/
public static Bitmap setImageVew(
ImageView imageView, Uri uri, int displayWidth, int displayHeight, boolean fitWithIn) {
// Get the image dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(uri.getPath(), options);
// Set targetWidth and targetHeight
int targetWidth = displayWidth;
int targetHeight = displayHeight;
if (targetHeight == 0) {
targetHeight = (int) (targetWidth * ((float) options.outHeight / (float) options.outWidth));
imageView.getLayoutParams().height = targetHeight;
}
// Set imageWidth and imageHeight based on image rotation
int rotation = getRotation(uri);
int imageWidth;
int imageHeight;
if (rotation == 0 || rotation == 180) {
imageWidth = options.outWidth;
imageHeight = options.outHeight;
} else {
imageWidth = options.outHeight;
imageHeight = options.outWidth;
}
// Get a scaled down version of the image
options.inJustDecodeBounds = false;
options.inSampleSize = getInSampleSize(
imageWidth, imageHeight, targetWidth, targetHeight, fitWithIn);
options.inPurgeable = true;
Bitmap scaledBitmap = BitmapFactory.decodeFile(uri.getPath(), options);
// Get the final bitmap after rotating the scaled down image
Bitmap bitmap;
if (rotation == 0) {
bitmap = scaledBitmap;
} else {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
bitmap = Bitmap.createBitmap(
scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
scaledBitmap.recycle();
}
imageView.setImageBitmap(bitmap);
return bitmap;
}
/**
* Gets the image rotation
*
* @param uri the image uri
*/
private static int getRotation(Uri uri) {
try {
ExifInterface exifInterface = new ExifInterface(uri.getPath());
switch (exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return 0;
}
} catch (IOException e) {
Log.e(TAG, "Unable to get photo orientation", e);
return 0;
}
}
/**
* Gets the in sample size.
*
* @param imageWidth the image width
* @param imageHeight the image height
* @param targetWidth the target width
* @param targetHeight the target height
* @param fitWithin true to fit within the target area. False to crop and fill
* all target area.
*/
private static int getInSampleSize(
int imageWidth, int imageHeight, int targetWidth, int targetHeight, boolean fitWithin) {
float widthRatio = 1;
if (imageWidth > targetWidth) {
widthRatio = (float) imageWidth / (float) targetWidth;
}
float heightRatio = 1;
if (imageHeight > targetHeight) {
heightRatio = (float) imageHeight / (float) targetHeight;
}
/*
* To fit within the target area, return the larger sample ratio so the
* image will not be larger than the target dimensions. To crop and fill the
* target area, return the smaller sample ratio so the image will be greater
* than the target dimensions. Use Math.floor to not under sample.
*/
return (int) Math.floor(
fitWithin ? Math.max(widthRatio, heightRatio) : Math.min(widthRatio, heightRatio));
}
}
@@ -170,7 +170,7 @@ public class TrackRecordingServiceConnectionUtils {
/**
* Adds a marker.
*/
public static void addMarker(Context context,
public static long addMarker(Context context,
TrackRecordingServiceConnection trackRecordingServiceConnection,
WaypointCreationRequest waypointCreationRequest) {
ITrackRecordingService trackRecordingService = trackRecordingServiceConnection
@@ -179,9 +179,10 @@ public class TrackRecordingServiceConnectionUtils {
Log.d(TAG, "Unable to add marker, no track recording service");
} else {
try {
if (trackRecordingService.insertWaypoint(waypointCreationRequest) != -1L) {
long markerId = trackRecordingService.insertWaypoint(waypointCreationRequest);
if (markerId != -1L) {
Toast.makeText(context, R.string.marker_add_success, Toast.LENGTH_SHORT).show();
return;
return markerId;
}
} catch (RemoteException e) {
Log.e(TAG, "Unable to add marker", e);
@@ -190,5 +191,6 @@ public class TrackRecordingServiceConnectionUtils {
}
}
Toast.makeText(context, R.string.marker_add_error, Toast.LENGTH_LONG).show();
return -1L;
}
}
@@ -363,7 +363,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN);
int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE);
int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE);
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
Waypoint waypoint = new Waypoint();
if (!cursor.isNull(idIndex)) {
@@ -468,6 +469,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (hasTripStatistics) {
waypoint.setTripStatistics(tripStatistics);
}
if (!cursor.isNull(photoUrlIndex)) {
waypoint.setPhotoUrl(cursor.getString(photoUrlIndex));
}
return waypoint;
}
@@ -672,6 +677,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade());
}
values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl());
return values;
}
@@ -47,12 +47,13 @@ public final class Waypoint implements Parcelable {
private long stopId = -1L;
private Location location = null;
private TripStatistics tripStatistics = null;
private String photoUrl = "";
public Waypoint() {}
public Waypoint(String name, String description, String category, String icon, long trackId,
WaypointType type, double length, long duration, long startId, long stopId, Location location,
TripStatistics tripStatistics) {
TripStatistics tripStatistics, String photoUrl) {
this.name = name;
this.description = description;
this.category = category;
@@ -65,6 +66,7 @@ public final class Waypoint implements Parcelable {
this.stopId = stopId;
this.location = location;
this.tripStatistics = tripStatistics;
this.photoUrl = photoUrl;
}
private Waypoint(Parcel source) {
@@ -89,6 +91,7 @@ public final class Waypoint implements Parcelable {
if (hasStats > 0) {
tripStatistics = source.readParcelable(classLoader);
}
photoUrl = source.readString();
}
@Override
@@ -117,6 +120,7 @@ public final class Waypoint implements Parcelable {
if (tripStatistics != null) {
dest.writeParcelable(tripStatistics, 0);
}
dest.writeString(photoUrl);
}
public static final Parcelable.Creator<Waypoint> CREATOR = new Parcelable.Creator<Waypoint>() {
@@ -233,5 +237,13 @@ public final class Waypoint implements Parcelable {
public void setTripStatistics(TripStatistics tripStatistics) {
this.tripStatistics = tripStatistics;
}
public String getPhotoUrl() {
return photoUrl;
}
public void setPhotoUrl(String photoUrl) {
this.photoUrl = photoUrl;
}
}
@@ -34,6 +34,7 @@ public class WaypointCreationRequest implements Parcelable {
private String category;
private String description;
private String iconUrl;
private String photoUrl;
public final static WaypointCreationRequest DEFAULT_WAYPOINT = new WaypointCreationRequest(
WaypointType.WAYPOINT, false);
@@ -43,17 +44,18 @@ public class WaypointCreationRequest implements Parcelable {
WaypointType.STATISTICS, true);
private WaypointCreationRequest(WaypointType type, boolean isTrackStatistics) {
this(type, isTrackStatistics, null, null, null, null);
this(type, isTrackStatistics, null, null, null, null, null);
}
public WaypointCreationRequest(WaypointType type, boolean isTrackStatistics, String name,
String category, String description, String iconUrl) {
String category, String description, String iconUrl, String photoUrl) {
this.type = type;
this.isTrackStatistics = isTrackStatistics;
this.name = name;
this.category = category;
this.description = description;
this.iconUrl = iconUrl;
this.photoUrl = photoUrl;
}
public static class Creator implements Parcelable.Creator<WaypointCreationRequest> {
@@ -66,6 +68,7 @@ public class WaypointCreationRequest implements Parcelable {
source.readString(),
source.readString(),
source.readString(),
source.readString(),
source.readString());
return request;
}
@@ -90,6 +93,7 @@ public class WaypointCreationRequest implements Parcelable {
parcel.writeString(category);
parcel.writeString(description);
parcel.writeString(iconUrl);
parcel.writeString(photoUrl);
}
public WaypointType getType() {
@@ -115,4 +119,8 @@ public class WaypointCreationRequest implements Parcelable {
public String getIconUrl() {
return iconUrl;
}
public String getPhotoUrl() {
return photoUrl;
}
}
@@ -70,6 +70,8 @@ public interface WaypointsColumns extends BaseColumns {
public static final String ELEVATIONGAIN = "elevationgain"; // elevation gain
public static final String MINGRADE = "mingrade"; // min grade
public static final String MAXGRADE = "maxgrade"; // max grade
public static final String PHOTOURL = "photoUrl"; // url for the photo
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@@ -101,7 +103,8 @@ public interface WaypointsColumns extends BaseColumns {
+ MAXELEVATION + " FLOAT, "
+ ELEVATIONGAIN + " FLOAT, "
+ MINGRADE + " FLOAT, "
+ MAXGRADE + " FLOAT"
+ MAXGRADE + " FLOAT, "
+ PHOTOURL + " STRING"
+ ");";
public static final String[] COLUMNS = {
@@ -134,7 +137,8 @@ public interface WaypointsColumns extends BaseColumns {
MAXELEVATION,
ELEVATIONGAIN,
MINGRADE,
MAXGRADE
MAXGRADE,
PHOTOURL
};
public static final byte[] COLUMN_TYPES = {
@@ -167,6 +171,7 @@ public interface WaypointsColumns extends BaseColumns {
FLOAT_TYPE_ID, // max elevation
FLOAT_TYPE_ID, // elevation gain
FLOAT_TYPE_ID, // min grade
FLOAT_TYPE_ID // max grade
FLOAT_TYPE_ID, // max grade
STRING_TYPE_ID // photo url
};
}
@@ -43,7 +43,7 @@ public class WaypointCreationRequestTest extends AndroidTestCase {
public void testAllAttributesParceling() {
WaypointCreationRequest original = new WaypointCreationRequest(
WaypointType.WAYPOINT, false, "name", "category", "description", "img.png");
WaypointType.WAYPOINT, false, "name", "category", "description", "img.png", null);
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
p.setDataPosition(0);