diff options
author | Yvonne Wong <ywong@cyngn.com> | 2016-05-11 14:02:12 -0700 |
---|---|---|
committer | Stephen Bird <sbird@cyngn.com> | 2016-09-13 15:07:21 -0700 |
commit | 1a3b89e56b63270c0394f5b50cd4ce3081c1f7ee (patch) | |
tree | 381c5a92a0f438e1ec69ab5726cd7cd1bebb5bdb | |
parent | 2bb428b3a4319a20a52c9c6fa27308501858d3f0 (diff) | |
download | packages_apps_Messaging-1a3b89e56b63270c0394f5b50cd4ce3081c1f7ee.tar.gz packages_apps_Messaging-1a3b89e56b63270c0394f5b50cd4ce3081c1f7ee.tar.bz2 packages_apps_Messaging-1a3b89e56b63270c0394f5b50cd4ce3081c1f7ee.zip |
Add maps preview for addresses and integrate ridesharing request into Messaging
- Uses the first map link found in a message to get a static map view
- The static map view is shown as a preview and can be clicked to go into
the default maps app
- Clicking on request ride will lead the user to RideCoreUI with pickup
location prefilled based on the address in the map link
- Clicking on the directions button will lead the user to google maps's
navigation
- Display placeholder background when map is loading or offline with no cached
image
- Use RoundedCornerTransformation to transform the map into a bitmap with rounded
corners
- Show brand bitmap from ridesharing services if an active provider is chosen,
otherwise display the generic ridesharing icon
Change-Id: I8e74c6d9beeaa58f01f0a51e5a831c6697c890ea
Issue-Id: RIDE-75, RIDE-76, RIDE-77
22 files changed, 574 insertions, 24 deletions
@@ -51,6 +51,7 @@ LOCAL_STATIC_JAVA_LIBRARIES += guava LOCAL_STATIC_JAVA_LIBRARIES += libchips LOCAL_STATIC_JAVA_LIBRARIES += libphotoviewer LOCAL_STATIC_JAVA_LIBRARIES += libphonenumber +LOCAL_STATIC_JAVA_LIBRARIES += play LOCAL_STATIC_JAVA_LIBRARIES += colorpicker LOCAL_STATIC_JAVA_LIBRARIES += contacts-picaso @@ -67,6 +68,7 @@ LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.photo LOCAL_AAPT_FLAGS += --extra-packages com.android.colorpicker LOCAL_AAPT_FLAGS += --extra-packages com.android.contacts.common LOCAL_AAPT_FLAGS += --extra-packages com.android.phone.common +LOCAT_AAPT_FLAGS += --extra-packages com.google.android.gms ifdef TARGET_BUILD_APPS LOCAL_JNI_SHARED_LIBRARIES := libframesequence libgiftranscode @@ -75,11 +77,7 @@ else endif # utilize ContactsCommon's phone-number-based contact-info lookup -ifeq ($(contacts_common_dir),) - contacts_common_dir := ../ContactsCommon -endif -CONTACTS_COMMON_LOOKUP_PROVIDER ?= $(LOCAL_PATH)/$(contacts_common_dir)/info_lookup -include $(CONTACTS_COMMON_LOOKUP_PROVIDER)/phonenumber_lookup_provider.mk +include $(LOCAL_PATH)/../ContactsCommon/info_lookup/phonenumber_lookup_provider.mk LOCAL_PROGUARD_FLAGS := -ignorewarnings -include build/core/proguard_basic_keeps.flags diff --git a/res/drawable-hdpi/ic_map_placeholder.png b/res/drawable-hdpi/ic_map_placeholder.png Binary files differnew file mode 100644 index 0000000..4b40b16 --- /dev/null +++ b/res/drawable-hdpi/ic_map_placeholder.png diff --git a/res/drawable-mdpi/ic_map_placeholder.png b/res/drawable-mdpi/ic_map_placeholder.png Binary files differnew file mode 100644 index 0000000..bd551a5 --- /dev/null +++ b/res/drawable-mdpi/ic_map_placeholder.png diff --git a/res/drawable-xhdpi/ic_map_placeholder.png b/res/drawable-xhdpi/ic_map_placeholder.png Binary files differnew file mode 100644 index 0000000..b6d5bf9 --- /dev/null +++ b/res/drawable-xhdpi/ic_map_placeholder.png diff --git a/res/drawable-xxhdpi/ic_map_placeholder.png b/res/drawable-xxhdpi/ic_map_placeholder.png Binary files differnew file mode 100644 index 0000000..8bba6c2 --- /dev/null +++ b/res/drawable-xxhdpi/ic_map_placeholder.png diff --git a/res/drawable/directions_button_background.xml b/res/drawable/directions_button_background.xml new file mode 100644 index 0000000..a40b30f --- /dev/null +++ b/res/drawable/directions_button_background.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:bottomRightRadius="2dp" android:topRightRadius="2dp" /> + <solid android:color="@color/white_54" /> +</shape>
\ No newline at end of file diff --git a/res/drawable/ic_generic_transport_icon.xml b/res/drawable/ic_generic_transport_icon.xml new file mode 100644 index 0000000..7bd5e2e --- /dev/null +++ b/res/drawable/ic_generic_transport_icon.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (c) 2016 The CyanogenMod Project + + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="#000000" + android:pathData="M18.9,6c-0.2-0.6-0.8-1-1.4-1h-11C5.8,5,5.3,5.4,5.1,6L3,12v8c0,0.5,0.5,1,1,1h1c0.6,0,1-0.5,1-1v-1h12v1c0,0.5,0.5,1,1,1h1 +c0.5,0,1-0.5,1-1v-8L18.9,6z +M6.5,16C5.7,16,5,15.3,5,14.5S5.7,13,6.5,13S8,13.7,8,14.5S7.3,16,6.5,16z M17.5,16 +c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5S18.3,16,17.5,16z +M5,11l1.5-4.5h11L19,11H5z M11,9.5 +c0,0.8-0.7,1.5-1.5,1.5S8,10.3,8,9.5S8.7,8,9.5,8S11,8.7,11,9.5z +M16,9.5c0,0.8-0.7,1.5-1.5,1.5S13,10.3,13,9.5S13.7,8,14.5,8 S16,8.7,16,9.5z" /> +</vector>
\ No newline at end of file diff --git a/res/drawable/ic_get_directions.xml b/res/drawable/ic_get_directions.xml new file mode 100755 index 0000000..46c6fb6 --- /dev/null +++ b/res/drawable/ic_get_directions.xml @@ -0,0 +1,7 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="#000000" android:fillAlpha=".54" android:pathData="M14,14.5V12H10V15H8V11A1,1 0 0,1 9,10H14V7.5L17.5,11M21.71,11.29L12.71,2.29H12.7C12.31,1.9 11.68,1.9 11.29,2.29L2.29,11.29C1.9,11.68 1.9,12.32 2.29,12.71L11.29,21.71C11.68,22.09 12.31,22.1 12.71,21.71L21.71,12.71C22.1,12.32 22.1,11.68 21.71,11.29Z" />
+</vector>
\ No newline at end of file diff --git a/res/drawable/maps_button_container_background.xml b/res/drawable/maps_button_container_background.xml new file mode 100644 index 0000000..6e3a3a1 --- /dev/null +++ b/res/drawable/maps_button_container_background.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="2dp" /> + <solid android:color="@android:color/transparent" /> +</shape>
\ No newline at end of file diff --git a/res/drawable/request_ride_button_background.xml b/res/drawable/request_ride_button_background.xml new file mode 100644 index 0000000..ff3ba2e --- /dev/null +++ b/res/drawable/request_ride_button_background.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:bottomLeftRadius="2dp" android:topLeftRadius="2dp" /> + <solid android:color="@color/white_54" /> +</shape>
\ No newline at end of file diff --git a/res/layout/attachment_maps.xml b/res/layout/attachment_maps.xml new file mode 100644 index 0000000..4cb691f --- /dev/null +++ b/res/layout/attachment_maps.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 The CyanogenMod Project (DvTonder) + + 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. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:visibility="gone" + android:layout_marginBottom="@dimen/conversation_maps_margin" + android:layout_width="@dimen/conversation_maps_width" + android:layout_height="@dimen/conversation_maps_height"> + <ImageView + android:id="@+id/maps_image" + android:layout_width="@dimen/conversation_maps_width" + android:layout_height="@dimen/conversation_maps_height" + android:minWidth="@dimen/conversation_maps_width" + android:minHeight="@dimen/conversation_maps_height" + android:adjustViewBounds="true" + android:scaleType="centerInside" + android:layout_gravity="center_vertical"/> + <RelativeLayout + android:background="@drawable/maps_button_container_background" + android:elevation="@dimen/maps_button_elevation" + android:layout_width="@dimen/maps_button_width" + android:layout_height="@dimen/maps_button_height" + android:layout_alignParentBottom="true" + android:layout_marginLeft="@dimen/conversation_maps_margin" + android:layout_marginBottom="@dimen/conversation_maps_margin" + android:layout_marginRight="@dimen/conversation_maps_margin"> + <Button + style="?android:attr/borderlessButtonStyle" + android:id="@+id/request_ride_button" + android:background="@drawable/request_ride_button_background" + android:layout_alignParentLeft="true" + android:layout_width="@dimen/request_ride_button_width" + android:layout_height="match_parent" + android:paddingLeft="28dp" + android:paddingRight="50dp" + android:textColor="@color/black_54" + android:textSize="@dimen/request_ride_text_size" + android:fontFamily="sans-serif-medium" + android:text="@string/request_ride"/> + <ImageView + android:id="@+id/brand_image" + android:layout_width="@dimen/brand_image_width" + android:layout_height="@dimen/brand_image_height" + android:layout_alignParentLeft="true" + android:layout_marginLeft="@dimen/brand_image_margin" + android:layout_marginRight="@dimen/brand_image_margin" + android:layout_centerVertical="true" + android:focusable="false" + android:clickable="false" + android:src="@drawable/ic_generic_transport_icon"/> + <ImageView + android:id="@+id/directions_button" + android:layout_width="@dimen/directions_button_width" + android:layout_height="match_parent" + android:background="@drawable/directions_button_background" + android:layout_alignParentRight="true" + android:padding="@dimen/directions_button_padding" + android:layout_centerVertical="true" + android:src="@drawable/ic_get_directions"/> + <View + android:id="@+id/button_divider" + android:layout_toRightOf="@id/request_ride_button" + android:layout_toLeftOf="@id/directions_button" + android:layout_width="@dimen/maps_button_divider_width" + android:layout_height="match_parent" + android:background="@color/transparent_gray" /> + </RelativeLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/conversation_message_view.xml b/res/layout/conversation_message_view.xml index 8d10c84..004028d 100644 --- a/res/layout/conversation_message_view.xml +++ b/res/layout/conversation_message_view.xml @@ -66,6 +66,10 @@ app:cornerRadius="@dimen/attachment_rounded_corner_radius" android:contentDescription="@string/message_image_content_description" /> + <include + android:id="@+id/message_maps" + layout="@layout/attachment_maps" /> + </LinearLayout> <com.android.messaging.ui.conversation.MessageBubbleBackground diff --git a/res/values/cm_colors.xml b/res/values/cm_colors.xml index 0342f85..3af3a08 100755 --- a/res/values/cm_colors.xml +++ b/res/values/cm_colors.xml @@ -56,4 +56,9 @@ <color name="snack_bar_action_text_color">#EEFF41</color> <color name="mic_recording_color">#ffffff</color> <color name="mic_background_color">#ffffff</color> + + <!-- Ridesharing --> + <color name="transparent_gray">#1F000000</color> + <color name="white_54">#8BFFFFFF</color> + <color name="black_54">#8B000000</color> </resources> diff --git a/res/values/cm_dimens.xml b/res/values/cm_dimens.xml index 2dc13c2..f367e0c 100644 --- a/res/values/cm_dimens.xml +++ b/res/values/cm_dimens.xml @@ -19,4 +19,23 @@ <dimen name="convolist_attribution_logo_marginRight">13dp</dimen> <dimen name="convo_attribution_logo_marginRight">5dp</dimen> <dimen name="convo_attribution_logo_marginBottom">5dp</dimen> + + <!-- Ride sharing --> + <dimen name="conversation_maps_width">221dp</dimen> + <dimen name="conversation_maps_height">144dp</dimen> + <dimen name="conversation_maps_margin">8dp</dimen> + <dimen name="maps_button_elevation">2dp</dimen> + <dimen name="maps_button_width">205dp</dimen> + <dimen name="maps_button_height">32dp</dimen> + <dimen name="request_ride_button_width">172dp</dimen> + <dimen name="request_ride_text_size">12sp</dimen> + <dimen name="brand_image_width">18dp</dimen> + <dimen name="brand_image_height">18dp</dimen> + <dimen name="brand_image_margin">7dp</dimen> + <dimen name="directions_button_width">32dp</dimen> + <dimen name="directions_button_padding">4dp</dimen> + <dimen name="maps_button_divider_width">1dp</dimen> + <dimen name="maps_error_icon_width">1dp</dimen> + <dimen name="maps_error_icon_height">1dp</dimen> + <dimen name="maps_corner_radius">3dp</dimen> </resources> diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index 4d6d424..e48c45c 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -155,4 +155,7 @@ <string name="mediapicker_audio_list_item_selected_content_description">The audio is selected.</string> <string name="mediapicker_audio_list_item_unselected_content_description">The audio is unselected.</string> <string name="mediapicker_audio_list_title_selection"><xliff:g id="count">%d</xliff:g> selected</string> + + <!-- Ride sharing --> + <string name="request_ride">Request Ride</string> </resources> diff --git a/res/values/google_maps_api.xml b/res/values/google_maps_api.xml new file mode 100644 index 0000000..f165a09 --- /dev/null +++ b/res/values/google_maps_api.xml @@ -0,0 +1,3 @@ +<resources> + <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false"></string> +</resources> diff --git a/src/com/android/messaging/ui/AsyncImageView.java b/src/com/android/messaging/ui/AsyncImageView.java index e18d677..f2394d0 100644 --- a/src/com/android/messaging/ui/AsyncImageView.java +++ b/src/com/android/messaging/ui/AsyncImageView.java @@ -113,8 +113,6 @@ public class AsyncImageView extends ImageView implements MediaResourceLoadListen attr.recycle(); } - - /** * The main entrypoint for AsyncImageView to load image resource given an ImageRequestDescriptor * @param descriptor the request descriptor, or null if no image should be displayed diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java index 3d50252..f740f39 100644 --- a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java +++ b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java @@ -28,6 +28,7 @@ import com.android.messaging.ui.CursorRecyclerAdapter; import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader; import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost; import com.android.messaging.util.Assert; +import com.cyanogenmod.messaging.util.RidesharingUtil; import java.util.HashSet; import java.util.List; @@ -40,6 +41,7 @@ public class ConversationMessageAdapter extends CursorRecyclerAdapter<ConversationMessageAdapter.ConversationMessageViewHolder> { private final ConversationMessageViewHost mHost; + private final RidesharingUtil mRidesharingUtil; private final AsyncImageViewDelayLoader mImageViewDelayLoader; private final View.OnClickListener mViewClickListener; private final View.OnLongClickListener mViewLongClickListener; @@ -54,6 +56,7 @@ public class ConversationMessageAdapter extends final View.OnLongClickListener longClickListener) { super(context, cursor, 0); mHost = host; + mRidesharingUtil = new RidesharingUtil(context); mViewClickListener = viewClickListener; mViewLongClickListener = longClickListener; mImageViewDelayLoader = imageViewDelayLoader; @@ -91,6 +94,7 @@ public class ConversationMessageAdapter extends final ConversationMessageView conversationMessageView = (ConversationMessageView) layoutInflater.inflate(R.layout.conversation_message_view, null); conversationMessageView.setHost(mHost); + conversationMessageView.setRidesharingUtil(mRidesharingUtil); conversationMessageView.setImageViewDelayLoader(mImageViewDelayLoader); return new ConversationMessageViewHolder(conversationMessageView, mViewClickListener, mViewLongClickListener); diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java index 22128bc..fae36c3 100644 --- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java +++ b/src/com/android/messaging/ui/conversation/ConversationMessageView.java @@ -17,6 +17,7 @@ package com.android.messaging.ui.conversation; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Rect; @@ -31,15 +32,19 @@ import android.text.style.URLSpan; import android.text.util.Linkify; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.Button; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import com.android.messaging.BugleApplication; @@ -73,11 +78,19 @@ import com.android.messaging.util.OsUtil; import com.android.messaging.util.PhoneUtils; import com.android.messaging.util.UiUtils; import com.android.messaging.util.YouTubeUtil; +import com.cyanogen.ambient.ridesharing.core.RidesharingContract; import com.cyanogen.lookup.phonenumber.response.LookupResponse; import com.cyanogenmod.messaging.lookup.LookupProviderManager.LookupProviderListener; import com.cyanogenmod.messaging.ui.AttributionContactIconView; +import com.cyanogenmod.messaging.util.GoogleStaticMapsUtil; +import com.cyanogenmod.messaging.util.RidesharingUtil; +import com.cyanogenmod.messaging.util.RoundedCornerTransformation; import com.google.common.base.Predicate; +import com.squareup.picasso.*; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -95,13 +108,22 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick boolean excludeDefault); } + private static final String TAG = ConversationMessageView.class.getSimpleName(); + private final ConversationMessageData mData; private LinearLayout mMessageAttachmentsView; private MultiAttachmentLayout mMultiAttachmentView; private AsyncImageView mMessageImageView; + private RelativeLayout mMessageMapsView; + private ImageView mMessageMapsImageView; + private Button mRequestRideButton; + private ImageView mBrandImageView; + private ImageView mDirectionsButton; + private View mButtonDivider; private TextView mMessageTextView; private boolean mMessageTextHasLinks; + private boolean mMessageHasMapsLink; private boolean mMessageHasYouTubeLink; private TextView mStatusTextView; private TextView mTitleTextView; @@ -120,11 +142,13 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick private boolean mOneOnOne; private ConversationMessageViewHost mHost; + private RidesharingUtil mRidesharingUtil; public ConversationMessageView(final Context context, final AttributeSet attrs) { super(context, attrs); // TODO: we should switch to using Binding and DataModel factory methods. mData = new ConversationMessageData(); + mRidesharingUtil = new RidesharingUtil(context); } @Override @@ -146,6 +170,13 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick mMessageImageView.setOnClickListener(this); mMessageImageView.setOnLongClickListener(this); + mMessageMapsView = (RelativeLayout) findViewById(R.id.message_maps); + mMessageMapsImageView = (ImageView) findViewById(R.id.maps_image); + mRequestRideButton = (Button) findViewById(R.id.request_ride_button); + mBrandImageView = (ImageView) findViewById(R.id.brand_image); + mDirectionsButton= (ImageView) findViewById(R.id.directions_button); + mButtonDivider = findViewById(R.id.button_divider); + mMessageTextView = (TextView) findViewById(R.id.message_text); mMessageTextView.setOnClickListener(this); IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this); @@ -288,6 +319,13 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick } /** + * Sets a ridesharing util to get data from ridesharing services + */ + public void setRidesharingUtil(final RidesharingUtil ridesharingUtil) { + mRidesharingUtil = ridesharingUtil; + } + + /** * Sets a delay loader instance to manage loading / resuming of image attachments. */ public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) { @@ -314,7 +352,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick */ private boolean shouldShowMessageBubbleArrow() { return !shouldShowSimplifiedVisualStyle() - && !(mData.hasAttachments() || mMessageHasYouTubeLink); + && !(mData.hasAttachments() || mMessageHasMapsLink || mMessageHasYouTubeLink); } /** @@ -543,6 +581,11 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick mMultiAttachmentView.setVisibility(View.GONE); } + // In the case that we have no image attachments and a maps url then we will show a map + // preview + String mapsUrlString = null; + final String mapsUrlPrefix = "geo:0,0?q="; + // In the case that we have no image attachments and exactly one youtube link in a message // then we will show a preview. String youtubeThumbnailUrl = null; @@ -553,28 +596,35 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick messageTextWithSpans.length(), URLSpan.class); for (URLSpan span : spans) { String url = span.getURL(); - String youtubeLinkForUrl = YouTubeUtil.getYoutubePreviewImageLink(url); - if (!TextUtils.isEmpty(youtubeLinkForUrl)) { - if (TextUtils.isEmpty(youtubeThumbnailUrl)) { - // Save the youtube link if we don't already have one - youtubeThumbnailUrl = youtubeLinkForUrl; - originalYoutubeLink = url; - } else { - // We already have a youtube link. This means we have two youtube links so - // we shall show none. - youtubeThumbnailUrl = null; - originalYoutubeLink = null; - break; + if (url.startsWith(mapsUrlPrefix)) { + mapsUrlString = url; + break; + } else { + String youtubeLinkForUrl = YouTubeUtil.getYoutubePreviewImageLink(url); + if (!TextUtils.isEmpty(youtubeLinkForUrl)) { + if (TextUtils.isEmpty(youtubeThumbnailUrl)) { + // Save the youtube link if we don't already have one + youtubeThumbnailUrl = youtubeLinkForUrl; + originalYoutubeLink = url; + } else { + // We already have a youtube link. This means we have two youtube links so + // we shall show none. + youtubeThumbnailUrl = null; + originalYoutubeLink = null; + break; + } } } } } // We need to keep track if we have a youtube link in the message so that we will not show // the arrow + mMessageHasMapsLink = !TextUtils.isEmpty(mapsUrlString); mMessageHasYouTubeLink = !TextUtils.isEmpty(youtubeThumbnailUrl); - // We will show the message image view if there is one attachment or one youtube link - if (imageParts.size() == 1 || mMessageHasYouTubeLink) { + // We will show the message image view if there is one attachment or one maps link or + // one youtube link + if (imageParts.size() == 1 || mMessageHasMapsLink || mMessageHasYouTubeLink) { // Get the display metrics for a hint for how large to pull the image data into final WindowManager windowManager = (WindowManager) getContext(). getSystemService(Context.WINDOW_SERVICE); @@ -597,6 +647,10 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick adjustImageViewBounds(imagePart); mMessageImageView.setImageResourceId(imageRequest); mMessageImageView.setTag(imagePart); + mMessageImageView.setVisibility(View.VISIBLE); + } else if (mMessageHasMapsLink) { + // Maps image and buttons + showMapsPreview(mapsUrlString, mapsUrlPrefix, desiredWidth); } else { // Youtube Thumbnail image final ImageRequestDescriptor imageRequest = @@ -607,9 +661,11 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */); mMessageImageView.setImageResourceId(imageRequest); mMessageImageView.setTag(originalYoutubeLink); + mMessageImageView.setVisibility(View.VISIBLE); } - mMessageImageView.setVisibility(View.VISIBLE); } else { + mMessageMapsView.setVisibility(View.GONE); + mMessageImageView.setImageResourceId(null); mMessageImageView.setVisibility(View.GONE); } @@ -626,6 +682,103 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick mMessageAttachmentsView.setVisibility(attachmentsVisible ? View.VISIBLE : View.GONE); } + /** + * Shows a maps preview and provides buttons for requesting a ride to the location and + * directions to the location + * @param mapsUrlString Encoded map url string + * @param mapsUrlPrefix Prefix for the map url string (such as geo:0,0?q=) + * @param desiredWidth Desired width of the maps preview + */ + private void showMapsPreview(String mapsUrlString, String mapsUrlPrefix, int desiredWidth) { + final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int mapsMeasureSpec = MeasureSpec.makeMeasureSpec(desiredWidth, + MeasureSpec.EXACTLY); + mMessageMapsView.measure(mapsMeasureSpec, unspecifiedMeasureSpec); + + // Reset view visibility to gone + mRequestRideButton.setVisibility(View.GONE); + mBrandImageView.setVisibility(View.GONE); + mDirectionsButton.setVisibility(View.GONE); + mButtonDivider.setVisibility(View.GONE); + + final String encodedAddress = mapsUrlString.substring(mapsUrlPrefix.length()); + final int height = getResources() + .getDimensionPixelSize(R.dimen.conversation_maps_height); + String staticMapsUrl = GoogleStaticMapsUtil.getStaticMapsUrl(mContext, desiredWidth, + height, encodedAddress); + RoundedCornerTransformation transformation = + new RoundedCornerTransformation(mContext.getApplicationContext(), staticMapsUrl); + Picasso.with(mContext.getApplicationContext()) + .load(staticMapsUrl) + .placeholder(R.drawable.ic_map_placeholder) + .transform(transformation) + .into(mMessageMapsImageView, new ImageLoadedCallback()); + final Uri mapsUri = Uri.parse(mapsUrlString); + mMessageMapsImageView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW, mapsUri); + mContext.startActivity(intent); + } + }); + + final String directionsPrefix = "google.navigation:q="; + final Uri directionsUri = Uri.parse(directionsPrefix + encodedAddress); + mDirectionsButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW, directionsUri); + mContext.startActivity(intent); + } + }); + + final String encodingFormat = "UTF-8"; + mRequestRideButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + String decodedAddress = null; + try { + decodedAddress = URLDecoder.decode(encodedAddress, encodingFormat); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Unable to decode address: " + encodedAddress, e); + } + + if (!TextUtils.isEmpty(decodedAddress)) { + RidesharingContract.RideRequest.Builder builder = + new RidesharingContract.RideRequest.Builder(); + builder.addDropoffLocation(decodedAddress); + Intent intent = builder.build(); + mContext.startActivity(intent); + } + } + }); + + mRidesharingUtil.setBrandBitmap(mBrandImageView); + + final int dividerWidth = getResources() + .getDimensionPixelSize(R.dimen.maps_button_divider_width); + final int dividerWidthMeasureSpec = MeasureSpec.makeMeasureSpec(dividerWidth, + MeasureSpec.EXACTLY); + mButtonDivider.measure(dividerWidthMeasureSpec, unspecifiedMeasureSpec); + + mMessageMapsView.setVisibility(View.VISIBLE); + } + + private class ImageLoadedCallback implements com.squareup.picasso.Callback { + @Override + public void onSuccess() { + mRequestRideButton.setVisibility(View.VISIBLE); + mBrandImageView.setVisibility(View.VISIBLE); + mDirectionsButton.setVisibility(View.VISIBLE); + mButtonDivider.setVisibility(View.VISIBLE); + } + + @Override + public void onError() { + Log.e(TAG, "Unable to load static map"); + } + } + private void bindAttachmentsOfSameType(final Predicate<MessagePartData> attachmentTypeFilter, final int attachmentViewLayoutRes, final AttachmentViewBinder viewBinder, final Class<?> attachmentViewClass) { @@ -885,6 +1038,16 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick // Tint image/video attachments when selected final int selectedImageTint = getResources().getColor(R.color.message_image_selected_tint); + if (mMessageMapsImageView.getVisibility() == View.VISIBLE && + mDirectionsButton.getVisibility() == View.VISIBLE) { + if (isSelected()) { + mMessageMapsImageView.setColorFilter(selectedImageTint); + mDirectionsButton.setColorFilter(selectedImageTint); + } else { + mMessageMapsImageView.clearColorFilter(); + mDirectionsButton.clearColorFilter(); + } + } if (mMessageImageView.getVisibility() == View.VISIBLE) { if (isSelected()) { mMessageImageView.setColorFilter(selectedImageTint); @@ -1264,5 +1427,4 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick } } } - } diff --git a/src/com/cyanogenmod/messaging/util/GoogleStaticMapsUtil.java b/src/com/cyanogenmod/messaging/util/GoogleStaticMapsUtil.java new file mode 100644 index 0000000..a88dfb0 --- /dev/null +++ b/src/com/cyanogenmod/messaging/util/GoogleStaticMapsUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * + * 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.cyanogenmod.messaging.util; + +import android.content.Context; +import com.android.messaging.R; + +public class GoogleStaticMapsUtil { + private static final String STATIC_MAPS_URL = "https://maps.googleapis.com/maps/api/staticmap?"; + + private static final String AND = "&"; + private static final String PIPE = "|"; + private static final String X = "x"; + + private static final String SIZE = "size="; + private static final String ZOOM = "zoom=18"; + private static final String SCALE = "scale=4"; + private static final String API_KEY = "key="; + + private static final String PICK_UP_MARKER = + "markers=icon:http://cdn.cyngn.com/ridesharing/pickup_marker.png"; + + public static String getStaticMapsUrl(Context context, int width, int height, String encodedAddress) { + final StringBuilder sb = new StringBuilder(STATIC_MAPS_URL); + sb.append(AND); + sb.append(SIZE); + sb.append(width); + sb.append(X); + sb.append(height); + sb.append(AND); + sb.append(ZOOM); + sb.append(AND); + sb.append(SCALE); + sb.append(AND); + sb.append(API_KEY); + sb.append(context.getString(R.string.google_maps_key)); + sb.append(AND); + sb.append(PICK_UP_MARKER); + sb.append(PIPE); + sb.append(encodedAddress); + + return sb.toString(); + } +} diff --git a/src/com/cyanogenmod/messaging/util/RidesharingUtil.java b/src/com/cyanogenmod/messaging/util/RidesharingUtil.java new file mode 100644 index 0000000..5c2ef05 --- /dev/null +++ b/src/com/cyanogenmod/messaging/util/RidesharingUtil.java @@ -0,0 +1,112 @@ +package com.cyanogenmod.messaging.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.util.Log; +import android.widget.ImageView; +import com.cyanogen.ambient.common.ConnectionResult; +import com.cyanogen.ambient.common.api.AmbientApiClient; +import com.cyanogen.ambient.common.api.ComponentNameResult; +import com.cyanogen.ambient.common.api.PendingResult; +import com.cyanogen.ambient.common.api.ResultCallback; +import com.cyanogen.ambient.ridesharing.RideSharingApi; +import com.cyanogen.ambient.ridesharing.RideSharingServices; +import com.cyanogen.ambient.ridesharing.results.ProviderInfoResult; + +/** + * Ridesharing services util + */ +public class RidesharingUtil { + private static final String TAG = RidesharingUtil.class.getSimpleName(); + + private Context mContext; + private AmbientApiClient mAmbientApiClient; + private RideSharingApi mRideSharingApi; + + private Bitmap mBrandBitmap; + + public RidesharingUtil(Context context) { + mContext = context; + connectAmbientApiClientIfNeeded(); + } + + /** + * Sets brand bitmap from the active ridesharing provider to the specified {@link ImageView} + * @param imageView ImageView to set the brand bitmap on + */ + public void setBrandBitmap(final ImageView imageView) { + if (mBrandBitmap == null) { + connectAmbientApiClientIfNeeded(); + + PendingResult<ComponentNameResult> pendingComponentResult = + mRideSharingApi.getActivePlugin(mAmbientApiClient); + pendingComponentResult.setResultCallback(new ResultCallback<ComponentNameResult>() { + @Override + public void onResult(final ComponentNameResult componentResult) { + if (componentResult != null && componentResult.component != null) { + PendingResult<ProviderInfoResult> pendingInfoResult = + mRideSharingApi.getPluginInfo(mAmbientApiClient, + componentResult.component); + pendingInfoResult.setResultCallback(new ResultCallback<ProviderInfoResult>() { + @Override + public void onResult(ProviderInfoResult infoResult) { + if (infoResult != null && infoResult.providerInfo != null) { + mBrandBitmap = infoResult.providerInfo.getBrandIcon(); + imageView.setImageBitmap(mBrandBitmap); + } else { + Log.e(TAG, "Unable to get provider info for active plugin: " + + componentResult.component); + } + } + }); + } else { + Log.e(TAG, "Unable to get active plugin"); + } + } + }); + } else { + imageView.setImageBitmap(mBrandBitmap); + } + } + + /** + * Helper method to initialize AmbientApiClient if it is null and connect AmbientApiClient if + * it is not connected and not currently trying to connect + */ + private void connectAmbientApiClientIfNeeded() { + if (mAmbientApiClient == null) { + Log.d(TAG, "mAmbientApiClient is null, initializing"); + mAmbientApiClient = new AmbientApiClient.Builder(mContext) + .addApi(RideSharingServices.API) + .build(); + if (mAmbientApiClient == null) { + Log.e(TAG, "AmbientApiClient couldn't initialize, returning"); + return; + } + mAmbientApiClient.registerConnectionFailedListener(new AmbientApiClient.OnConnectionFailedListener() { + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + Log.e(TAG, "Unable to connect with Ambient. ConnectionResult.ErrorCode : " + connectionResult.getErrorCode()); + } + }); + mAmbientApiClient.registerConnectionCallbacks(new AmbientApiClient.ConnectionCallbacks() { + @Override + public void onConnected(Bundle bundle) { + Log.d(TAG, "Connected with Ambient."); + } + + @Override + public void onConnectionSuspended(int i) { + Log.d(TAG, "Ambient client disconnected."); + } + }); + } + + if (!mAmbientApiClient.isConnected() && !mAmbientApiClient.isConnecting()) { + Log.d(TAG, "ambientApiClient is not connect or connecting, attempting to connect now"); + mAmbientApiClient.connect(); + mRideSharingApi = RideSharingServices.getInstance(); + } + } +} diff --git a/src/com/cyanogenmod/messaging/util/RoundedCornerTransformation.java b/src/com/cyanogenmod/messaging/util/RoundedCornerTransformation.java new file mode 100644 index 0000000..197a774 --- /dev/null +++ b/src/com/cyanogenmod/messaging/util/RoundedCornerTransformation.java @@ -0,0 +1,46 @@ +package com.cyanogenmod.messaging.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.support.v7.appcompat.R; + +public class RoundedCornerTransformation implements com.squareup.picasso.Transformation { + private Context mContext; + private String mTag; + + public RoundedCornerTransformation(Context context, String tag) { + mContext = context; + mTag = tag; + } + + @Override + public Bitmap transform(Bitmap source) { + int width = source.getWidth(); + int height = source.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + final RectF rect = new RectF(0, 0, width, height); + + final int radius = + mContext.getResources().getDimensionPixelSize(R.dimen.maps_corner_radius); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + BitmapShader shader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + paint.setShader(shader); + canvas.drawRoundRect(rect, radius, radius, paint); + + source.recycle(); + + return bitmap; + } + + @Override + public String key() { + return mTag; + } +} |