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 --- .../res/drawable-hdpi/ic_schedule_white_24dp.png | Bin 0 -> 575 bytes .../res/drawable-mdpi/ic_schedule_white_24dp.png | Bin 0 -> 377 bytes .../res/drawable-xhdpi/ic_schedule_white_24dp.png | Bin 0 -> 737 bytes .../res/drawable-xxhdpi/ic_schedule_white_24dp.png | Bin 0 -> 1107 bytes .../drawable-xxxhdpi/ic_schedule_white_24dp.png | Bin 0 -> 1478 bytes InCallUI/res/values/strings.xml | 6 + .../com/android/incallui/CallCardPresenter.java | 7 +- .../src/com/android/incallui/ContactInfoCache.java | 15 ++- .../src/com/android/incallui/ContactUtils.java | 6 +- .../incallui/InCallContactInteractions.java | 127 ++++++++++++++++++--- .../incallui/InCallContactInteractionsTest.java | 73 ++++++++++++ 11 files changed, 208 insertions(+), 26 deletions(-) create mode 100644 InCallUI/res/drawable-hdpi/ic_schedule_white_24dp.png create mode 100644 InCallUI/res/drawable-mdpi/ic_schedule_white_24dp.png create mode 100644 InCallUI/res/drawable-xhdpi/ic_schedule_white_24dp.png create mode 100644 InCallUI/res/drawable-xxhdpi/ic_schedule_white_24dp.png create mode 100644 InCallUI/res/drawable-xxxhdpi/ic_schedule_white_24dp.png create mode 100644 InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java diff --git a/InCallUI/res/drawable-hdpi/ic_schedule_white_24dp.png b/InCallUI/res/drawable-hdpi/ic_schedule_white_24dp.png new file mode 100644 index 000000000..f3581d104 Binary files /dev/null and b/InCallUI/res/drawable-hdpi/ic_schedule_white_24dp.png differ diff --git a/InCallUI/res/drawable-mdpi/ic_schedule_white_24dp.png b/InCallUI/res/drawable-mdpi/ic_schedule_white_24dp.png new file mode 100644 index 000000000..501ee842e Binary files /dev/null and b/InCallUI/res/drawable-mdpi/ic_schedule_white_24dp.png differ diff --git a/InCallUI/res/drawable-xhdpi/ic_schedule_white_24dp.png b/InCallUI/res/drawable-xhdpi/ic_schedule_white_24dp.png new file mode 100644 index 000000000..2e27936a4 Binary files /dev/null and b/InCallUI/res/drawable-xhdpi/ic_schedule_white_24dp.png differ diff --git a/InCallUI/res/drawable-xxhdpi/ic_schedule_white_24dp.png b/InCallUI/res/drawable-xxhdpi/ic_schedule_white_24dp.png new file mode 100644 index 000000000..bfc72736a Binary files /dev/null and b/InCallUI/res/drawable-xxhdpi/ic_schedule_white_24dp.png differ diff --git a/InCallUI/res/drawable-xxxhdpi/ic_schedule_white_24dp.png b/InCallUI/res/drawable-xxxhdpi/ic_schedule_white_24dp.png new file mode 100644 index 000000000..b94f4dfa1 Binary files /dev/null and b/InCallUI/res/drawable-xxxhdpi/ic_schedule_white_24dp.png differ diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml index 7a90953f4..20a724cd6 100644 --- a/InCallUI/res/values/strings.xml +++ b/InCallUI/res/values/strings.xml @@ -477,4 +477,10 @@ %.1f mi away %.1f km away + + %s - %s + + Open now + + Closed now 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; } /** diff --git a/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java b/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java new file mode 100644 index 000000000..c3ec08d69 --- /dev/null +++ b/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java @@ -0,0 +1,73 @@ +/* + * 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.test.AndroidTestCase; +import android.util.Pair; + +import com.android.incallui.InCallContactInteractions.BusinessContextInfo; + +import java.util.Calendar; + +public class InCallContactInteractionsTest extends AndroidTestCase { + private InCallContactInteractions mInCallContactInteractions; + + @Override + protected void setUp() { + mInCallContactInteractions = new InCallContactInteractions(mContext, true /* isBusiness */); + } + + public void testIsOpenNow() { + Calendar currentTimeForTest = Calendar.getInstance(); + currentTimeForTest.set(Calendar.HOUR_OF_DAY, 10); + BusinessContextInfo info = + mInCallContactInteractions.constructHoursInfoByTime( + currentTimeForTest, + Pair.create("0800", "2000")); + assertEquals(mContext.getString(R.string.open_now), info.heading); + } + + public void testIsClosedNow_BeforeOpen() { + Calendar currentTimeForTest = Calendar.getInstance(); + currentTimeForTest.set(Calendar.HOUR_OF_DAY, 6); + BusinessContextInfo info = + mInCallContactInteractions.constructHoursInfoByTime( + currentTimeForTest, + Pair.create("0800", "2000")); + assertEquals(mContext.getString(R.string.closed_now), info.heading); + } + + public void testIsClosedNow_AfterClosed() { + Calendar currentTimeForTest = Calendar.getInstance(); + currentTimeForTest.set(Calendar.HOUR_OF_DAY, 21); + BusinessContextInfo info = + mInCallContactInteractions.constructHoursInfoByTime( + currentTimeForTest, + Pair.create("0800", "2000")); + assertEquals(mContext.getString(R.string.closed_now), info.heading); + } + + public void testInvalidOpeningHours() { + Calendar currentTimeForTest = Calendar.getInstance(); + currentTimeForTest.set(Calendar.HOUR_OF_DAY, 21); + BusinessContextInfo info = + mInCallContactInteractions.constructHoursInfoByTime( + currentTimeForTest, + Pair.create("", "2000")); + assertEquals(null, info); + } +} -- cgit v1.2.3