From e838ba2b69cf85b0d938467477e47cce4af2bfb6 Mon Sep 17 00:00:00 2001 From: Nancy Chen Date: Fri, 2 Oct 2015 10:51:50 -0700 Subject: Add location information with business caller ID. Display business address and distance when available when calling businesses. This information is only displayed during outgoing calls and while in the call. Bug: 23351559 Change-Id: Ifb9197cc3abf6d865e13de10862c610e8aa15fba --- .../src/com/android/incallui/CallCardFragment.java | 29 ++++----- .../com/android/incallui/CallCardPresenter.java | 74 +++++++++++++++++++++- .../incallui/ConferenceParticipantListAdapter.java | 3 + .../src/com/android/incallui/ContactInfoCache.java | 38 +++++++++-- .../src/com/android/incallui/ContactUtils.java | 42 ++++++++++++ .../src/com/android/incallui/DistanceHelper.java | 37 +++++++++++ .../incallui/InCallContactInteractions.java | 57 +++++++++++++---- .../com/android/incallui/StatusBarNotifier.java | 3 + .../incallui/service/PhoneNumberService.java | 1 + .../com/android/incalluibind/ObjectFactory.java | 11 ++++ 10 files changed, 260 insertions(+), 35 deletions(-) create mode 100644 InCallUI/src/com/android/incallui/ContactUtils.java create mode 100644 InCallUI/src/com/android/incallui/DistanceHelper.java (limited to 'InCallUI/src') diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index ffc7cb6c6..b276a9569 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -46,15 +46,13 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; import com.android.contacts.common.widget.FloatingActionButtonController; -import com.android.incallui.InCallContactInteractions.BusinessContextInfo; -import com.android.incallui.InCallContactInteractions.PersonContextInfo; -import com.android.incallui.InCallContactInteractions.ContactContextInfo; import com.android.phone.common.animation.AnimUtils; import java.util.List; @@ -336,23 +334,18 @@ public class CallCardFragment extends BaseFragment info) { - mPhoto.setVisibility(View.GONE); - mPrimaryCallCardContainer.setElevation(0); - mContactContext.setVisibility(View.VISIBLE); - - if (mInCallContactInteractions == null) { - mInCallContactInteractions = - new InCallContactInteractions(getView().getContext(), isBusiness); - } else { - mInCallContactInteractions.setIsBusiness(isBusiness); - } - - mContactContextTitle.setText(mInCallContactInteractions.getContactContextTitle()); - mInCallContactInteractions.setData(info); - mContactContextListView.setAdapter(mInCallContactInteractions.getListAdapter()); + public void setContactContext(String title, ListAdapter listAdapter) { + mContactContextTitle.setText(title); + mContactContextListView.setAdapter(listAdapter); } + @Override + public void showContactContext(boolean show) { + mPhoto.setVisibility(show ? View.GONE : View.VISIBLE); + mPrimaryCallCardContainer.setElevation( + show ? 0 : getResources().getDimension(R.dimen.primary_call_elevation)); + mContactContext.setVisibility(show ? View.VISIBLE : View.GONE); + } /** * Sets the visibility of the primary call card. diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index 2cd6b6ed1..67747b827 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -36,8 +36,10 @@ import android.telecom.VideoProfile; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.accessibility.AccessibilityManager; +import android.widget.ListAdapter; import com.android.incallui.Call.LogState; +import com.android.incallui.Call.State; import com.android.incallui.InCallContactInteractions.ContactContextInfo; import com.android.incallui.ContactInfoCache.ContactCacheEntry; import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; @@ -48,6 +50,7 @@ import com.android.incallui.InCallPresenter.InCallStateListener; import com.android.incallui.InCallPresenter.IncomingCallListener; import com.android.incalluibind.ObjectFactory; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; @@ -60,7 +63,7 @@ import com.google.common.base.Preconditions; */ public class CallCardPresenter extends Presenter implements InCallStateListener, IncomingCallListener, InCallDetailsListener, - InCallEventListener, CallList.CallUpdateListener { + InCallEventListener, CallList.CallUpdateListener, DistanceHelper.Listener { public interface EmergencyCallListener { public void onCallUpdated(BaseFragment fragment, boolean isEmergency); @@ -71,6 +74,7 @@ public class CallCardPresenter extends Presenter private final EmergencyCallListener mEmergencyCallListener = ObjectFactory.newEmergencyCallListener(); + private DistanceHelper mDistanceHelper; private Call mPrimary; private Call mSecondary; @@ -80,6 +84,7 @@ public class CallCardPresenter extends Presenter private Context mContext; private boolean mSpinnerShowing = false; private boolean mHasShownToast = false; + private InCallContactInteractions mInCallContactInteractions; public static class ContactLookupCallback implements ContactInfoCacheCallback { private final WeakReference mCallCardPresenter; @@ -106,6 +111,13 @@ public class CallCardPresenter extends Presenter } } + @Override + public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) { + CallCardPresenter presenter = mCallCardPresenter.get(); + if (presenter != null) { + presenter.onContactInteractionsInfoComplete(callId, entry); + } + } } public CallCardPresenter() { @@ -120,6 +132,7 @@ public class CallCardPresenter extends Presenter public void init(Context context, Call call) { mContext = Preconditions.checkNotNull(context); + mDistanceHelper = ObjectFactory.newDistanceHelper(mContext, this); // Call may be null if disconnect happened already. if (call != null) { @@ -172,6 +185,10 @@ public class CallCardPresenter extends Presenter CallList.getInstance().removeCallUpdateListener(mPrimary.getId(), this); } + if (mDistanceHelper != null) { + mDistanceHelper.cleanUp(); + } + mPrimary = null; mPrimaryContactInfo = null; mSecondaryContactInfo = null; @@ -210,6 +227,10 @@ public class CallCardPresenter extends Presenter secondary = getCallToDisplay(callList, primary, true); } + if (mInCallContactInteractions != null) { + ui.showContactContext(newState != InCallState.INCOMING); + } + Log.d(this, "Primary call: " + primary); Log.d(this, "Secondary call: " + secondary); @@ -543,6 +564,54 @@ public class CallCardPresenter extends Presenter } } + private void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) { + if (getUi() == null) { + return; + } + + if (mPrimary != null && callId.equals(mPrimary.getId())) { + mPrimaryContactInfo.locationAddress = entry.locationAddress; + updateContactInteractions(); + } + } + + @Override + public void onLocationReady() { + // This will only update the contacts interactions data if the location returns after + // the contact information is found. + updateContactInteractions(); + } + + private void updateContactInteractions() { + if (mPrimary != null && mPrimaryContactInfo != null + && mPrimaryContactInfo.locationAddress != null) { + setInCallContactInteractionsType(true); + + mInCallContactInteractions.setBusinessInfo( + mPrimaryContactInfo.locationAddress, + mDistanceHelper.calculateDistance(mPrimaryContactInfo.locationAddress)); + getUi().setContactContext( + null, + mInCallContactInteractions.getListAdapter()); + getUi().showContactContext(mPrimary.getState() != State.INCOMING); + } + } + + /** + * Update the contact interactions type so that the correct UI is shown. + * + * @param isBusiness {@code true} if the interaction is a business interaction, {@code false} if + * it is a personal contact. + */ + private void setInCallContactInteractionsType(boolean isBusiness) { + if (mInCallContactInteractions == null) { + mInCallContactInteractions = + new InCallContactInteractions(mContext, isBusiness); + } else { + mInCallContactInteractions.setIsBusiness(isBusiness); + } + } + private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) { if (isPrimary) { mPrimaryContactInfo = entry; @@ -954,7 +1023,8 @@ public class CallCardPresenter extends Presenter public interface CallCardUi extends Ui { void setVisible(boolean on); - void setContactContext(boolean isBusiness, List info); + void setContactContext(String title, ListAdapter listAdapter); + void showContactContext(boolean show); void setCallCardVisible(boolean visible); void setPrimary(String number, String name, boolean nameIsNumber, String label, Drawable photo, boolean isSipCall, boolean isContactPhotoShown); diff --git a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java index 0e6d6e9c8..a0588a11e 100644 --- a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java +++ b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java @@ -137,6 +137,9 @@ public class ConferenceParticipantListAdapter extends BaseAdapter { update(callId, entry); } + @Override + public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) {} + /** * Updates the contact information for a participant. * diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java index 7de16486e..0e6a3d4e0 100644 --- a/InCallUI/src/com/android/incallui/ContactInfoCache.java +++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.location.Address; import android.net.Uri; import android.os.AsyncTask; import android.os.Looper; @@ -44,7 +45,7 @@ import org.json.JSONObject; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.google.common.base.Objects; +import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import java.util.HashMap; @@ -71,6 +72,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete private Drawable mDefaultContactPhotoDrawable; private Drawable mConferencePhotoDrawable; + private ContactUtils mContactUtils; public static synchronized ContactInfoCache getInstance(Context mContext) { if (sCache == null) { @@ -84,6 +86,8 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete mPhoneNumberService = ObjectFactory.newPhoneNumberService(context); mCachedNumberLookupService = com.android.dialerbind.ObjectFactory.newCachedNumberLookupService(); + mContactUtils = ObjectFactory.getContactUtilsInstance(context); + } public ContactCacheEntry getInfo(String callId) { @@ -249,7 +253,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete } class PhoneNumberServiceListener implements PhoneNumberService.NumberLookupListener, - PhoneNumberService.ImageLookupListener { + PhoneNumberService.ImageLookupListener, ContactUtils.Listener { private final String mCallId; PhoneNumberServiceListener(String callId) { @@ -295,12 +299,18 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete entry.photo = mContext.getResources().getDrawable(R.drawable.img_business); } + String address = null; + if (mContactUtils != null) { + // This method will callback "onAddressDetailsFound". + address = mContactUtils.getAddressFromLookupKey(info.getLookupKey(), this); + } + // Add the contact info to the cache. mInfoMap.put(mCallId, entry); sendInfoNotifications(mCallId, entry); // If there is no image then we should not expect another callback. - if (info.getImageUrl() == null) { + if (info.getImageUrl() == null && address == null) { // We're done, so clear callbacks clearCallbacks(mCallId); } @@ -310,6 +320,14 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete public void onImageFetchComplete(Bitmap bitmap) { onImageLoadComplete(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, null, bitmap, mCallId); } + + @Override + public void onAddressDetailsFound(Address address) { + final ContactCacheEntry entry = mInfoMap.get(mCallId); + entry.locationAddress = address; + sendContactInteractionsNotifications(mCallId, entry); + clearCallbacks(mCallId); + } } /** @@ -531,6 +549,15 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete } } + private void sendContactInteractionsNotifications(String callId, ContactCacheEntry entry) { + final Set callBacks = mCallBacks.get(callId); + if (callBacks != null) { + for (ContactInfoCacheCallback callBack : callBacks) { + callBack.onContactInteractionsInfoComplete(callId, entry); + } + } + } + private void clearCallbacks(String callId) { mCallBacks.remove(callId); } @@ -570,6 +597,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete public interface ContactInfoCacheCallback { public void onContactInfoComplete(String callId, ContactCacheEntry entry); public void onImageLoadComplete(String callId, ContactCacheEntry entry); + public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry); } public static class ContactCacheEntry { @@ -585,11 +613,12 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete public Uri displayPhotoUri; public Uri lookupUri; // Sent to NotificationMananger public String lookupKey; + public Address locationAddress; public int contactLookupResult = LogState.LOOKUP_NOT_FOUND; @Override public String toString() { - return Objects.toStringHelper(this) + return MoreObjects.toStringHelper(this) .add("name", MoreStrings.toSafeString(name)) .add("number", MoreStrings.toSafeString(number)) .add("location", MoreStrings.toSafeString(location)) @@ -598,6 +627,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete .add("isSipCall", isSipCall) .add("contactUri", contactUri) .add("displayPhotoUri", displayPhotoUri) + .add("locationAddress", locationAddress) .add("contactLookupResult", contactLookupResult) .toString(); } diff --git a/InCallUI/src/com/android/incallui/ContactUtils.java b/InCallUI/src/com/android/incallui/ContactUtils.java new file mode 100644 index 000000000..eac748494 --- /dev/null +++ b/InCallUI/src/com/android/incallui/ContactUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 The Android Open Source 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.android.incallui; + +import android.content.Context; +import android.location.Address; + +import com.android.incalluibind.ObjectFactory; + +/** + * Utility functions to help manipulate contact data. + */ +public abstract class ContactUtils { + protected Context mContext; + + public static ContactUtils getInstance(Context context) { + return ObjectFactory.getContactUtilsInstance(context); + } + + protected ContactUtils(Context context) { + mContext = context; + } + + public interface Listener { + public void onAddressDetailsFound(Address address); + } + + public abstract String getAddressFromLookupKey(String lookupKey, Listener listener); +} diff --git a/InCallUI/src/com/android/incallui/DistanceHelper.java b/InCallUI/src/com/android/incallui/DistanceHelper.java new file mode 100644 index 000000000..a4db5fed3 --- /dev/null +++ b/InCallUI/src/com/android/incallui/DistanceHelper.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 The Android Open Source 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.android.incallui; + +import android.location.Address; + +/** + * Superclass for a helper class to get the current location and distance to other locations. + */ +public abstract class DistanceHelper { + public static final float DISTANCE_NOT_FOUND = -1; + public static final float MILES_PER_METER = (float) 0.000621371192; + public static final float KILOMETERS_PER_METER = (float) 0.001; + + public interface Listener { + public void onLocationReady(); + } + + public void cleanUp() {} + + public float calculateDistance(Address address) { + return DISTANCE_NOT_FOUND; + } +} diff --git a/InCallUI/src/com/android/incallui/InCallContactInteractions.java b/InCallUI/src/com/android/incallui/InCallContactInteractions.java index 0ed8b2012..6f30720c2 100644 --- a/InCallUI/src/com/android/incallui/InCallContactInteractions.java +++ b/InCallUI/src/com/android/incallui/InCallContactInteractions.java @@ -17,6 +17,8 @@ package com.android.incallui; import android.content.Context; +import android.location.Address; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -27,7 +29,9 @@ import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; +import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * Wrapper class for objects that are used in generating the context about the contact in the InCall @@ -64,13 +68,40 @@ public class InCallContactInteractions { } } - /** - * Set the data for the list adapter. - * @param data The data to add to the list adapter. This completely replaces any previous data. - */ - public void setData(List data) { + public void setBusinessInfo(Address address, float distance) { mListAdapter.clear(); - mListAdapter.addAll(data); + mListAdapter.addAll(constructBusinessContextInfo(address, distance)); + } + + private List constructBusinessContextInfo(Address address, float distance) { + List info = new ArrayList(); + + BusinessContextInfo headerInfo = new BusinessContextInfo(); + headerInfo.iconId = R.drawable.ic_business_white_24dp; + headerInfo.heading = getContactContextTitle(); + info.add(headerInfo); + + //TODO: hours of operation information + + // Location information + BusinessContextInfo distanceInfo = new BusinessContextInfo(); + distanceInfo.iconId = R.drawable.ic_location_on_white_24dp; + if (distance != DistanceHelper.DISTANCE_NOT_FOUND) { + //TODO: add a setting to allow the user to select "KM" or "MI" as their distance units. + if (Locale.US.equals(Locale.getDefault())) { + distanceInfo.heading = mContext.getString(R.string.distance_imperial_away, + distance * DistanceHelper.MILES_PER_METER); + } else { + distanceInfo.heading = mContext.getString(R.string.distance_metric_away, + distance * DistanceHelper.KILOMETERS_PER_METER); + } + } + if (address != null) { + distanceInfo.detail = address.getAddressLine(0); + } + info.add(distanceInfo); + + return info; } /** @@ -99,13 +130,20 @@ public class InCallContactInteractions { TextView headingTextView = (TextView) listItem.findViewById(R.id.heading); TextView detailTextView = (TextView) listItem.findViewById(R.id.detail); - if (this.iconId == 0 || this.heading == null || this.detail == null) { + if (this.iconId == 0 || (this.heading == null && this.detail == null)) { return; } imageView.setImageDrawable(listItem.getContext().getDrawable(this.iconId)); + headingTextView.setText(this.heading); + headingTextView.setVisibility(TextUtils.isEmpty(this.heading) + ? View.GONE : View.VISIBLE); + detailTextView.setText(this.detail); + detailTextView.setVisibility(TextUtils.isEmpty(this.detail) + ? View.GONE : View.VISIBLE); + } } @@ -157,9 +195,7 @@ public class InCallContactInteractions { LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View listItem; - listItem = inflater.inflate(mResId, null); - + View listItem = inflater.inflate(mResId, null); ContactContextInfo item = getItem(position); if (item == null) { @@ -167,7 +203,6 @@ public class InCallContactInteractions { } item.bindView(listItem); - return listItem; } } diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java index 35a22c9d8..f46b0d30c 100644 --- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java +++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java @@ -188,6 +188,9 @@ public class StatusBarNotifier implements InCallPresenter.InCallStateListener, buildAndSendNotification(call, entry); } } + + @Override + public void onContactInteractionsInfoComplete(String callId, ContactCacheEntry entry) {} }); } diff --git a/InCallUI/src/com/android/incallui/service/PhoneNumberService.java b/InCallUI/src/com/android/incallui/service/PhoneNumberService.java index cddc478cf..70da4ef3a 100644 --- a/InCallUI/src/com/android/incallui/service/PhoneNumberService.java +++ b/InCallUI/src/com/android/incallui/service/PhoneNumberService.java @@ -60,6 +60,7 @@ public interface PhoneNumberService { public String getPhoneLabel(); public String getNormalizedNumber(); public String getImageUrl(); + public String getLookupKey(); public boolean isBusiness(); public int getLookupSource(); } diff --git a/InCallUI/src/com/android/incalluibind/ObjectFactory.java b/InCallUI/src/com/android/incalluibind/ObjectFactory.java index d8a2e2977..7e9423acf 100644 --- a/InCallUI/src/com/android/incalluibind/ObjectFactory.java +++ b/InCallUI/src/com/android/incalluibind/ObjectFactory.java @@ -20,6 +20,8 @@ import android.content.Context; import android.content.Intent; import com.android.incallui.CallCardPresenter.EmergencyCallListener; +import com.android.incallui.ContactUtils; +import com.android.incallui.DistanceHelper; import com.android.incallui.service.PhoneNumberService; public class ObjectFactory { @@ -45,4 +47,13 @@ public class ObjectFactory { public static Intent getCallStateButtonBroadcastIntent(Context context) { return null; } + + public static DistanceHelper newDistanceHelper(Context context, + DistanceHelper.Listener listener) { + return null; + } + + public static ContactUtils getContactUtilsInstance(Context context) { + return null; + } } -- cgit v1.2.3