From d2fda4ab69205fc672c02141f9b62c1958f73ace Mon Sep 17 00:00:00 2001 From: Nancy Chen Date: Tue, 20 Oct 2015 17:12:01 -0700 Subject: Add hours of operation info to incall business context. Display hours of operation information if it is available. If hours of operation are available, also determine whether it is currently open or closed. Display in the InCallUI when making a business call. Also add tests to make sure that the business context object is constructed correctly. Bug: 23351559 Change-Id: Ic2846e54e15ade37ccf0b916651cc3388da3cc23 --- .../com/android/incallui/CallCardPresenter.java | 7 +- .../src/com/android/incallui/ContactInfoCache.java | 15 ++- .../src/com/android/incallui/ContactUtils.java | 6 +- .../incallui/InCallContactInteractions.java | 127 ++++++++++++++++++--- 4 files changed, 129 insertions(+), 26 deletions(-) (limited to 'InCallUI/src') diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index aa022f448..e7d6f0c3e 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -595,8 +595,8 @@ public class CallCardPresenter extends Presenter private void updateContactInteractions() { if (mPrimary != null && mPrimaryContactInfo != null - && mPrimaryContactInfo.locationAddress != null) { - + && (mPrimaryContactInfo.locationAddress != null + || mPrimaryContactInfo.openingHours != null)) { // TODO: This is hardcoded to "isBusiness" because functionality to differentiate // between business and personal has not yet been added. if (setInCallContactInteractionsType(true /* isBusiness */)) { @@ -606,7 +606,8 @@ public class CallCardPresenter extends Presenter mInCallContactInteractions.setBusinessInfo( mPrimaryContactInfo.locationAddress, - mDistanceHelper.calculateDistance(mPrimaryContactInfo.locationAddress)); + mDistanceHelper.calculateDistance(mPrimaryContactInfo.locationAddress), + mPrimaryContactInfo.openingHours); getUi().setContactContextContent(mInCallContactInteractions.getListAdapter()); getUi().showContactContext(mPrimary.getState() != State.INCOMING); } diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java index 0e6a3d4e0..e3457d5cb 100644 --- a/InCallUI/src/com/android/incallui/ContactInfoCache.java +++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java @@ -30,6 +30,7 @@ import android.provider.ContactsContract.DisplayNameSources; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.telecom.TelecomManager; import android.text.TextUtils; +import android.util.Pair; import com.android.contacts.common.util.PhoneNumberHelper; import com.android.dialer.calllog.ContactInfo; @@ -299,10 +300,11 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete entry.photo = mContext.getResources().getDrawable(R.drawable.img_business); } - String address = null; + boolean hasContactInteractions = false; if (mContactUtils != null) { - // This method will callback "onAddressDetailsFound". - address = mContactUtils.getAddressFromLookupKey(info.getLookupKey(), this); + // This method will callback "onContactInteractionsFound". + hasContactInteractions = mContactUtils.retrieveContactInteractionsFromLookupKey( + info.getLookupKey(), this); } // Add the contact info to the cache. @@ -310,7 +312,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete sendInfoNotifications(mCallId, entry); // If there is no image then we should not expect another callback. - if (info.getImageUrl() == null && address == null) { + if (info.getImageUrl() == null && !hasContactInteractions) { // We're done, so clear callbacks clearCallbacks(mCallId); } @@ -322,9 +324,10 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete } @Override - public void onAddressDetailsFound(Address address) { + public void onContactInteractionsFound(Address address, Pair openingHours) { final ContactCacheEntry entry = mInfoMap.get(mCallId); entry.locationAddress = address; + entry.openingHours = openingHours; sendContactInteractionsNotifications(mCallId, entry); clearCallbacks(mCallId); } @@ -614,6 +617,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete public Uri lookupUri; // Sent to NotificationMananger public String lookupKey; public Address locationAddress; + public Pair openingHours; public int contactLookupResult = LogState.LOOKUP_NOT_FOUND; @Override @@ -628,6 +632,7 @@ public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadComplete .add("contactUri", contactUri) .add("displayPhotoUri", displayPhotoUri) .add("locationAddress", locationAddress) + .add("openingHours", openingHours) .add("contactLookupResult", contactLookupResult) .toString(); } diff --git a/InCallUI/src/com/android/incallui/ContactUtils.java b/InCallUI/src/com/android/incallui/ContactUtils.java index eac748494..dfacade8a 100644 --- a/InCallUI/src/com/android/incallui/ContactUtils.java +++ b/InCallUI/src/com/android/incallui/ContactUtils.java @@ -17,6 +17,7 @@ package com.android.incallui; import android.content.Context; import android.location.Address; +import android.util.Pair; import com.android.incalluibind.ObjectFactory; @@ -35,8 +36,9 @@ public abstract class ContactUtils { } public interface Listener { - public void onAddressDetailsFound(Address address); + public void onContactInteractionsFound(Address address, Pair openingHours); } - public abstract String getAddressFromLookupKey(String lookupKey, Listener listener); + public abstract boolean retrieveContactInteractionsFromLookupKey(String lookupKey, + Listener listener); } diff --git a/InCallUI/src/com/android/incallui/InCallContactInteractions.java b/InCallUI/src/com/android/incallui/InCallContactInteractions.java index 04caecc13..6d1c9fc8f 100644 --- a/InCallUI/src/com/android/incallui/InCallContactInteractions.java +++ b/InCallUI/src/com/android/incallui/InCallContactInteractions.java @@ -16,9 +16,13 @@ package com.android.incallui; +import com.google.common.annotations.VisibleForTesting; + import android.content.Context; import android.location.Address; import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -29,7 +33,11 @@ import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.Locale; @@ -41,6 +49,7 @@ import java.util.Locale; * is a business contact or not and logic for the manipulation of data for the call context. */ public class InCallContactInteractions { + private static final String TAG = InCallContactInteractions.class.getSimpleName(); private Context mContext; private InCallContactInteractionsListAdapter mListAdapter; private boolean mIsBusiness; @@ -77,11 +86,6 @@ public class InCallContactInteractions { return false; } - public void setBusinessInfo(Address address, float distance) { - mListAdapter.clear(); - mListAdapter.addAll(constructBusinessContextInfo(address, distance)); - } - public View getBusinessListHeaderView() { if (mBusinessHeaderView == null) { mBusinessHeaderView = mInflater.inflate( @@ -90,30 +94,121 @@ public class InCallContactInteractions { return mBusinessHeaderView; } - private List constructBusinessContextInfo(Address address, float distance) { + public void setBusinessInfo(Address address, float distance, + Pair openingHours) { + mListAdapter.clear(); List info = new ArrayList(); - //TODO: hours of operation information + // Hours of operation + if (openingHours != null) { + BusinessContextInfo hoursInfo = constructHoursInfo(openingHours); + if (hoursInfo != null) { + info.add(hoursInfo); + } + } // Location information - BusinessContextInfo distanceInfo = new BusinessContextInfo(); - distanceInfo.iconId = R.drawable.ic_location_on_white_24dp; + if (address != null) { + BusinessContextInfo locationInfo = constructLocationInfo(address, distance); + info.add(locationInfo); + } + + mListAdapter.addAll(info); + } + + /** + * Construct a BusinessContextInfo object containing hours of operation information. + * The format is: + * [Open now/Closed now] + * [Hours] + * + * @param openingHours + * @return BusinessContextInfo object with the schedule icon, the heading set to whether the + * business is open or not and the details set to the hours of operation. + */ + private BusinessContextInfo constructHoursInfo(Pair openingHours) { + return constructHoursInfoByTime(Calendar.getInstance(), openingHours); + } + + /** + * Pass in arbitrary current calendar time. + */ + @VisibleForTesting + BusinessContextInfo constructHoursInfoByTime( + Calendar currentTime, Pair openingHours) { + BusinessContextInfo hoursInfo = new BusinessContextInfo(); + hoursInfo.iconId = R.drawable.ic_schedule_white_24dp; + + Calendar openTime = getCalendarFromTime(currentTime, openingHours.first); + Calendar closeTime = getCalendarFromTime(currentTime, openingHours.second); + + if (openTime == null || closeTime == null) { + return null; + } + + if (currentTime.after(openTime) && currentTime.before(closeTime)) { + hoursInfo.heading = mContext.getString(R.string.open_now); + } else { + hoursInfo.heading = mContext.getString(R.string.closed_now); + } + + hoursInfo.detail = mContext.getString( + R.string.opening_hours, + DateFormat.getTimeFormat(mContext).format(openTime.getTime()), + DateFormat.getTimeFormat(mContext).format(closeTime.getTime())); + return hoursInfo; + } + + /** + * Construct a BusinessContextInfo object with the location information of the business. + * The format is: + * [Straight line distance in miles or kilometers] + * [Address without state/country/etc.] + * + * @param address An Address object containing address details of the business + * @param distance The distance to the location in meters + * @return A BusinessContextInfo object with the location icon, the heading as the distance to + * the business and the details containing the address. + */ + @VisibleForTesting + BusinessContextInfo constructLocationInfo(Address address, float distance) { + if (address == null) { + return null; + } + + BusinessContextInfo locationInfo = new BusinessContextInfo(); + locationInfo.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, + locationInfo.heading = mContext.getString(R.string.distance_imperial_away, distance * DistanceHelper.MILES_PER_METER); } else { - distanceInfo.heading = mContext.getString(R.string.distance_metric_away, + locationInfo.heading = mContext.getString(R.string.distance_metric_away, distance * DistanceHelper.KILOMETERS_PER_METER); } } - if (address != null) { - distanceInfo.detail = address.getAddressLine(0); - } - info.add(distanceInfo); + locationInfo.detail = address.getAddressLine(0); + return locationInfo; + } - return info; + /** + * Get a calendar object set to the current calendar date and the time set to the "hhmm" string + * passed in. + */ + private Calendar getCalendarFromTime(Calendar currentTime, String time) { + try { + Calendar newCalendar = Calendar.getInstance(); + newCalendar.setTime(new SimpleDateFormat("hhmm").parse(time)); + newCalendar.set( + currentTime.get(Calendar.YEAR), + currentTime.get(Calendar.MONTH), + currentTime.get(Calendar.DATE)); + return newCalendar; + } catch (ParseException e) { + Log.w(TAG, "Could not parse time string" + time); + } + return null; } /** -- cgit v1.2.3