diff --git a/MyTracks/res/layout/list_item.xml b/MyTracks/res/layout/list_item.xml index f3522dda8..b30288fd3 100644 --- a/MyTracks/res/layout/list_item.xml +++ b/MyTracks/res/layout/list_item.xml @@ -24,9 +24,9 @@ limitations under the License. android:shrinkColumns="1" android:stretchColumns="1" style="@style/ListItem" > - + + + - + - - - - - - - - - - + android:layout_width="match_parent" > + + + + + + + + + + + + + + + + + + + + + - + + displayWidth) { - widthRatio = Math.round((float) imageWidth / (float) displayWidth); - } - - int heightRatio = 1; - if (displayHeight != 0 && imageHeight > displayHeight) { - heightRatio = Math.round((float) imageHeight / (float) displayHeight); - } - - /* - * Return the larger sample ratio so the image will not be larger than the - * displayWidth and displayHeight. - */ - return Math.max(widthRatio, heightRatio); - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MarkerListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MarkerListActivity.java index 55223f799..ce8d68aeb 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MarkerListActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MarkerListActivity.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/SearchListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SearchListActivity.java index bf42d0a26..0e76ca04e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SearchListActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SearchListActivity.java @@ -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); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java index dd20ae758..b0aba0b1a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java @@ -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); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ListItemUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/ListItemUtils.java index f34cd58e7..75463ff7a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/ListItemUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/ListItemUtils.java @@ -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,32 +69,47 @@ 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), - View.INVISIBLE); + 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], View.GONE); + setTextView(dateTextView, startTimeDisplay[0]); TextView timeTextView = (TextView) view.findViewById(R.id.list_item_time); - setTextView(timeTextView, startTimeDisplay[1], View.GONE); + setTextView(timeTextView, startTimeDisplay[1]); + // Set category/description TextView descriptionTextView = (TextView) view.findViewById(R.id.list_item_description); - setTextView(descriptionTextView, getDescription(isRecording, category, description), View.GONE); + setTextView(descriptionTextView, getDescription(isRecording, category, description)); } /** @@ -194,11 +214,10 @@ public class ListItemUtils { * * @param textView the text view * @param value the value for the text view - * @param visibility visibility when value is not available */ - private static void setTextView(TextView textView, String value, int visibility) { + private static void setTextView(TextView textView, String value) { if (value == null || value.length() == 0) { - textView.setVisibility(visibility); + textView.setVisibility(View.GONE); } else { textView.setVisibility(View.VISIBLE); textView.setText(value); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java new file mode 100644 index 000000000..b333fb932 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java @@ -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)); + } +}