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
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 0000000..f3581d1
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_schedule_white_24dp.png
Binary files 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 0000000..501ee84
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_schedule_white_24dp.png
Binary files 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 0000000..2e27936
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/ic_schedule_white_24dp.png
Binary files 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 0000000..bfc7273
--- /dev/null
+++ b/InCallUI/res/drawable-xxhdpi/ic_schedule_white_24dp.png
Binary files 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 0000000..b94f4df
--- /dev/null
+++ b/InCallUI/res/drawable-xxxhdpi/ic_schedule_white_24dp.png
Binary files differ
diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml
index 7a90953..20a724c 100644
--- a/InCallUI/res/values/strings.xml
+++ b/InCallUI/res/values/strings.xml
@@ -477,4 +477,10 @@
<string name="distance_imperial_away"><xliff:g id="distance">%.1f</xliff:g> mi away</string>
<!-- Used to inform the user how far away a location is in kilometers. [CHAR LIMIT=NONE] -->
<string name="distance_metric_away"><xliff:g id="distance">%.1f</xliff:g> km away</string>
+ <!-- Used to indicate the opening hours for a location as a time span. [CHAR LIMIT=NONE] -->
+ <string name="opening_hours"><xliff:g id="open_time">%s</xliff:g> - <xliff:g id="close_time">%s</xliff:g></string>
+ <!-- Displayed when a place is open. -->
+ <string name="open_now">Open now</string>
+ <!-- Displayed when a place is closed. -->
+ <string name="closed_now">Closed now</string>
</resources>
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index aa022f4..e7d6f0c 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -595,8 +595,8 @@
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 @@
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 0e6a3d4..e3457d5 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.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 @@
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 @@
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 @@
}
@Override
- public void onAddressDetailsFound(Address address) {
+ public void onContactInteractionsFound(Address address, Pair<String, String> openingHours) {
final ContactCacheEntry entry = mInfoMap.get(mCallId);
entry.locationAddress = address;
+ entry.openingHours = openingHours;
sendContactInteractionsNotifications(mCallId, entry);
clearCallbacks(mCallId);
}
@@ -614,6 +617,7 @@
public Uri lookupUri; // Sent to NotificationMananger
public String lookupKey;
public Address locationAddress;
+ public Pair<String, String> openingHours;
public int contactLookupResult = LogState.LOOKUP_NOT_FOUND;
@Override
@@ -628,6 +632,7 @@
.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 eac7484..dfacade 100644
--- a/InCallUI/src/com/android/incallui/ContactUtils.java
+++ b/InCallUI/src/com/android/incallui/ContactUtils.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.location.Address;
+import android.util.Pair;
import com.android.incalluibind.ObjectFactory;
@@ -35,8 +36,9 @@
}
public interface Listener {
- public void onAddressDetailsFound(Address address);
+ public void onContactInteractionsFound(Address address, Pair<String, String> 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 04caecc..6d1c9fc 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.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 @@
* 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 @@
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 @@
return mBusinessHeaderView;
}
- private List<ContactContextInfo> constructBusinessContextInfo(Address address, float distance) {
+ public void setBusinessInfo(Address address, float distance,
+ Pair<String, String> openingHours) {
+ mListAdapter.clear();
List<ContactContextInfo> info = new ArrayList<ContactContextInfo>();
- //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<String, String> openingHours) {
+ return constructHoursInfoByTime(Calendar.getInstance(), openingHours);
+ }
+
+ /**
+ * Pass in arbitrary current calendar time.
+ */
+ @VisibleForTesting
+ BusinessContextInfo constructHoursInfoByTime(
+ Calendar currentTime, Pair<String, String> 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 0000000..c3ec08d
--- /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);
+ }
+}