Merge "Account for multiple open times for business contacts." into ub-contactsdialer-a-dev am: c3e0717a4b
am: 839ce5427c

* commit '839ce5427c6440d41925b0833db9497f5833c1c2':
  Account for multiple open times for business contacts.
diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml
index 2f8ef8a..54d68c9 100644
--- a/InCallUI/res/values/strings.xml
+++ b/InCallUI/res/values/strings.xml
@@ -479,8 +479,14 @@
     <string name="distance_metric_away"><xliff:g id="distance">%.1f</xliff:g> km away</string>
     <!-- A shortened way to display a business address. Formatted [street address], [city/locality]. -->
     <string name="display_address"><xliff:g id="street_address">%1$s</xliff:g>, <xliff:g id="locality">%2$s</xliff:g></string>
-    <!-- Used to indicate the opening hours for a location as a time span. e.g. "11 am - 9 pm" [CHAR LIMIT=NONE] -->
-    <string name="opening_hours"><xliff:g id="open_time">%1$s</xliff:g> - <xliff:g id="close_time">%2$s</xliff:g></string>
+    <!-- Used to indicate hours of operation for a location as a time span. e.g. "11 am - 9 pm" [CHAR LIMIT=NONE] -->
+    <string name="open_time_span"><xliff:g id="open_time">%1$s</xliff:g> - <xliff:g id="close_time">%2$s</xliff:g></string>
+    <!-- Used to indicate a series of opening hours for a location.
+         This first argument may be one or more time spans. e.g. "11 am - 9 pm, 9 pm - 11 pm"
+         The second argument is an additional time span. e.g. "11 pm - 1 am"
+         The string is used to build a list of opening hours.
+         [CHAR LIMIT=NONE] -->
+    <string name="opening_hours"><xliff:g id="earlier_times">%1$s</xliff:g>, <xliff:g id="later_time">%2$s</xliff:g></string>
     <!-- Displayed when a place is open. -->
     <string name="open_now">Open now</string>
     <!-- Displayed when a place is closed. -->
diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java
index 90284d5..54152eb 100644
--- a/InCallUI/src/com/android/incallui/ContactInfoCache.java
+++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java
@@ -49,7 +49,9 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.Calendar;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -324,7 +326,8 @@
         }
 
         @Override
-        public void onContactInteractionsFound(Address address, Pair<String, String> openingHours) {
+        public void onContactInteractionsFound(Address address,
+                List<Pair<Calendar, Calendar>> openingHours) {
             final ContactCacheEntry entry = mInfoMap.get(mCallId);
             entry.locationAddress = address;
             entry.openingHours = openingHours;
@@ -629,7 +632,7 @@
         public Uri lookupUri; // Sent to NotificationMananger
         public String lookupKey;
         public Address locationAddress;
-        public Pair<String, String> openingHours;
+        public List<Pair<Calendar, Calendar>> openingHours;
         public int contactLookupResult = LogState.LOOKUP_NOT_FOUND;
 
         @Override
diff --git a/InCallUI/src/com/android/incallui/ContactUtils.java b/InCallUI/src/com/android/incallui/ContactUtils.java
index dfacade..0750af7 100644
--- a/InCallUI/src/com/android/incallui/ContactUtils.java
+++ b/InCallUI/src/com/android/incallui/ContactUtils.java
@@ -21,6 +21,9 @@
 
 import com.android.incalluibind.ObjectFactory;
 
+import java.util.Calendar;
+import java.util.List;
+
 /**
  * Utility functions to help manipulate contact data.
  */
@@ -36,7 +39,8 @@
     }
 
     public interface Listener {
-        public void onContactInteractionsFound(Address address, Pair<String, String> openingHours);
+        public void onContactInteractionsFound(Address address,
+                List<Pair<Calendar, Calendar>> openingHours);
     }
 
     public abstract boolean retrieveContactInteractionsFromLookupKey(String lookupKey,
diff --git a/InCallUI/src/com/android/incallui/InCallContactInteractions.java b/InCallUI/src/com/android/incallui/InCallContactInteractions.java
index 21660cb..275243e 100644
--- a/InCallUI/src/com/android/incallui/InCallContactInteractions.java
+++ b/InCallUI/src/com/android/incallui/InCallContactInteractions.java
@@ -49,7 +49,6 @@
  * 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;
@@ -95,7 +94,7 @@
     }
 
     public void setBusinessInfo(Address address, float distance,
-            Pair<String, String> openingHours) {
+            List<Pair<Calendar, Calendar>> openingHours) {
         mListAdapter.clear();
         List<ContactContextInfo> info = new ArrayList<ContactContextInfo>();
 
@@ -126,7 +125,7 @@
      * @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) {
+    private BusinessContextInfo constructHoursInfo(List<Pair<Calendar, Calendar>> openingHours) {
         return constructHoursInfo(Calendar.getInstance(), openingHours);
     }
 
@@ -135,26 +134,38 @@
      */
     @VisibleForTesting
     BusinessContextInfo constructHoursInfo(Calendar currentTime,
-            Pair<String, String> openingHours) {
-        BusinessContextInfo hoursInfo = new BusinessContextInfo();
-        hoursInfo.iconId = R.drawable.ic_schedule_white_24dp;
-
-        // Note: the date of these {@link Date}s are set to January 1, 1970. The object is just
-        // used as a storage for the time.
-        Date openingDateTime = getSimpleDateTime(openingHours.first);
-        Date closingDateTime = getSimpleDateTime(openingHours.second);
-
-        if (openingDateTime == null || closingDateTime == null) {
+            List<Pair<Calendar, Calendar>> openingHours) {
+        if (currentTime == null || openingHours == null || openingHours.size() == 0) {
             return null;
         }
 
-        hoursInfo.heading = isOpen(openingDateTime, closingDateTime, currentTime)
-                ? mContext.getString(R.string.open_now) : mContext.getString(R.string.closed_now);
+        BusinessContextInfo hoursInfo = new BusinessContextInfo();
+        hoursInfo.iconId = R.drawable.ic_schedule_white_24dp;
 
-        hoursInfo.detail = mContext.getString(
-                R.string.opening_hours,
-                DateFormat.getTimeFormat(mContext).format(openingDateTime),
-                DateFormat.getTimeFormat(mContext).format(closingDateTime));
+        boolean isOpen = false;
+        for (Pair<Calendar, Calendar> hours : openingHours) {
+            if (hours.first.compareTo(currentTime) <= 0
+                    && currentTime.compareTo(hours.second) < 0) {
+                // If the current time is on or after the opening time and strictly before the
+                // closing time, then this business is open.
+                isOpen = true;
+            }
+
+            String openTimeSpan = mContext.getString(R.string.open_time_span,
+                    DateFormat.getTimeFormat(mContext).format(hours.first.getTime()),
+                    DateFormat.getTimeFormat(mContext).format(hours.second.getTime()));
+
+            if (TextUtils.isEmpty(hoursInfo.detail)) {
+                hoursInfo.detail = openTimeSpan;
+            } else {
+                hoursInfo.detail = mContext.getString(R.string.opening_hours, hoursInfo.detail,
+                        openTimeSpan);
+            }
+        }
+
+        hoursInfo.heading = isOpen ? mContext.getString(R.string.open_now)
+                : mContext.getString(R.string.closed_now);
+
         return hoursInfo;
     }
 
@@ -204,57 +215,6 @@
     }
 
     /**
-     * Get a {@link Date} object corresponding to a particular time.
-     *
-     * @param time A string containing a time in the format "hhmm".
-     * @return A {@link Date} object with the time set to the parsed value of the "time" parameter
-     * and the date set to January 1, 1970. Or {@code null} if the input string is not able to be
-     * parsed.
-     */
-    private Date getSimpleDateTime(String time) {
-        try {
-            return new SimpleDateFormat("hhmm").parse(time);
-        } catch (ParseException e) {
-            Log.w(TAG, "Could not parse time string " + time);
-        }
-        return null;
-    }
-
-    /**
-     * Check whether the current time falls between the opening time and the closing time.
-     *
-     * @param openingTime A {@link Date} object with the time set to the opening time and the date
-     * set to January 1, 1970.
-     * @param closingTime A {@link Date} object with the time set to the closing time and the date
-     * set to January 1, 1970.
-     * @param currentDateTime A {@link Calendar} object with the current date and time.
-     * @return {@code true} if the current time falls within the opening and closing time bounds and
-     * {@code false} otherwise.
-     */
-    private boolean isOpen(Date openingTime, Date closingTime, Calendar currentDateTime) {
-        Calendar openTimeCalendar = Calendar.getInstance();
-        openTimeCalendar.setTime(openingTime);
-
-        Calendar closeTimeCalendar = Calendar.getInstance();
-        closeTimeCalendar.setTime(closingTime);
-
-        if (openTimeCalendar.compareTo(closeTimeCalendar) >= 0) {
-            // If the open time is the same or after the close time, add a day to the close time
-            // calendar.
-            closeTimeCalendar.add(Calendar.DATE, 1);
-        }
-
-        // Since the date doesn't actually matter, it's easier to set the current date to the
-        // opening date rather than change both the calendars for the open time and the close time.
-        currentDateTime.set(
-                openTimeCalendar.get(Calendar.YEAR),
-                openTimeCalendar.get(Calendar.MONTH),
-                openTimeCalendar.get(Calendar.DATE));
-
-        return currentDateTime.after(openTimeCalendar) && currentDateTime.before(closeTimeCalendar);
-    }
-
-    /**
      * Get the appropriate title for the context.
      * @return The "Business info" title for a business contact and the "Recent messages" title for
      *         personal contacts.
diff --git a/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java b/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java
index 50d0aaf..0d03848 100644
--- a/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java
+++ b/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java
@@ -22,9 +22,18 @@
 
 import com.android.incallui.InCallContactInteractions.BusinessContextInfo;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Locale;
 
+/**
+ * Tests for InCallContactInteractions class methods for formatting info for display.
+ *
+ * NOTE: tests assume system settings are set to 12hr time format and US locale. This means that
+ * the output of InCallContactInteractions methods are compared against strings in 12hr time format
+ * and US locale address formatting unless otherwise specified.
+ */
 public class InCallContactInteractionsTest extends AndroidTestCase {
     private InCallContactInteractions mInCallContactInteractions;
     private static final float TEST_DISTANCE = (float) 1234.56;
@@ -34,64 +43,147 @@
         mInCallContactInteractions = new InCallContactInteractions(mContext, true /* isBusiness */);
     }
 
-    public void testIsOpenNow() {
-        Calendar currentTimeForTest = Calendar.getInstance();
-        currentTimeForTest.set(Calendar.HOUR_OF_DAY, 10);
-        BusinessContextInfo info =
+    public void testIsOpenNow_NowMatchesOpenTime() {
+        assertEquals(mContext.getString(R.string.open_now),
                 mInCallContactInteractions.constructHoursInfo(
-                        currentTimeForTest,
-                        Pair.create("0800", "2000"));
-        assertEquals(mContext.getString(R.string.open_now), info.heading);
+                        getTestCalendarWithHour(8),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(20))))
+                .heading);
     }
 
     public void testIsOpenNow_ClosingAfterMidnight() {
-        Calendar currentTimeForTest = Calendar.getInstance();
-        currentTimeForTest.set(Calendar.HOUR_OF_DAY, 10);
-        BusinessContextInfo info =
+        assertEquals(mContext.getString(R.string.open_now),
                 mInCallContactInteractions.constructHoursInfo(
-                        currentTimeForTest,
-                        Pair.create("0800", "0100"));
-        assertEquals(mContext.getString(R.string.open_now), info.heading);
+                        getTestCalendarWithHour(10),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHourAndDaysFromToday(1, 1))))
+                .heading);
     }
 
     public void testIsOpenNow_Open24Hours() {
-        Calendar currentTimeForTest = Calendar.getInstance();
-        currentTimeForTest.set(Calendar.HOUR_OF_DAY, 10);
-        BusinessContextInfo info =
+        assertEquals(mContext.getString(R.string.open_now),
                 mInCallContactInteractions.constructHoursInfo(
-                        currentTimeForTest,
-                        Pair.create("0800", "0800"));
-        assertEquals(mContext.getString(R.string.open_now), info.heading);
+                        getTestCalendarWithHour(10),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHourAndDaysFromToday(8, 1))))
+                .heading);
+    }
+
+    public void testIsOpenNow_AfterMiddayBreak() {
+        assertEquals(mContext.getString(R.string.open_now),
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(13),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(10)),
+                                Pair.create(
+                                        getTestCalendarWithHour(12),
+                                        getTestCalendarWithHour(15))))
+                .heading);
+    }
+
+    public void testIsClosedNow_DuringMiddayBreak() {
+        assertEquals(mContext.getString(R.string.closed_now),
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(11),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(10)),
+                                Pair.create(
+                                        getTestCalendarWithHour(12),
+                                        getTestCalendarWithHour(15))))
+                .heading);
     }
 
     public void testIsClosedNow_BeforeOpen() {
-        Calendar currentTimeForTest = Calendar.getInstance();
-        currentTimeForTest.set(Calendar.HOUR_OF_DAY, 6);
-        BusinessContextInfo info =
+        assertEquals(mContext.getString(R.string.closed_now),
                 mInCallContactInteractions.constructHoursInfo(
-                        currentTimeForTest,
-                        Pair.create("0800", "2000"));
-        assertEquals(mContext.getString(R.string.closed_now), info.heading);
+                        getTestCalendarWithHour(6),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(20))))
+                .heading);
+    }
+
+    public void testIsClosedNow_NowMatchesClosedTime() {
+        assertEquals(mContext.getString(R.string.closed_now),
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(20),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(20))))
+                .heading);
     }
 
     public void testIsClosedNow_AfterClosed() {
-        Calendar currentTimeForTest = Calendar.getInstance();
-        currentTimeForTest.set(Calendar.HOUR_OF_DAY, 21);
-        BusinessContextInfo info =
+        assertEquals(mContext.getString(R.string.closed_now),
                 mInCallContactInteractions.constructHoursInfo(
-                        currentTimeForTest,
-                        Pair.create("0800", "2000"));
-        assertEquals(mContext.getString(R.string.closed_now), info.heading);
+                        getTestCalendarWithHour(21),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(20))))
+                .heading);
+    }
+
+    public void testOpeningHours_SingleOpenRange() {
+        assertEquals("8:00 AM - 8:00 PM",
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(21),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHour(20))))
+                .detail);
+    }
+
+    public void testOpeningHours_TwoOpenRanges() {
+        assertEquals("8:00 AM - 10:00 AM, 12:00 PM - 3:00 PM",
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(13),
+                        Arrays.asList(
+                                Pair.create(
+                                    getTestCalendarWithHour(8),
+                                    getTestCalendarWithHour(10)),
+                                Pair.create(
+                                        getTestCalendarWithHour(12),
+                                        getTestCalendarWithHour(15))))
+                .detail);
+    }
+
+    public void testOpeningHours_MultipleOpenRanges() {
+        assertEquals("8:00 AM - 10:00 AM, 12:00 PM - 3:00 PM, 5:00 PM - 9:00 PM",
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(13),
+                        Arrays.asList(
+                                Pair.create(
+                                    getTestCalendarWithHour(8),
+                                    getTestCalendarWithHour(10)),
+                                Pair.create(
+                                        getTestCalendarWithHour(12),
+                                        getTestCalendarWithHour(15)),
+                                Pair.create(
+                                        getTestCalendarWithHour(17),
+                                        getTestCalendarWithHour(21))))
+                .detail);
     }
 
     public void testInvalidOpeningHours() {
-        Calendar currentTimeForTest = Calendar.getInstance();
-        currentTimeForTest.set(Calendar.HOUR_OF_DAY, 21);
-        BusinessContextInfo info =
+        assertEquals(null,
                 mInCallContactInteractions.constructHoursInfo(
-                        currentTimeForTest,
-                        Pair.create("", "2000"));
-        assertEquals(null, info);
+                        getTestCalendarWithHour(21),
+                        new ArrayList<Pair<Calendar, Calendar>>()));
     }
 
     public void testLocationInfo_ForUS() {
@@ -150,4 +242,19 @@
         address.setLocality("Test locality");
         return address;
     }
+
+    private Calendar getTestCalendarWithHour(int hour) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.HOUR_OF_DAY, hour);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+        return calendar;
+    }
+
+    private Calendar getTestCalendarWithHourAndDaysFromToday(int hour, int daysFromToday) {
+        Calendar calendar = getTestCalendarWithHour(hour);
+        calendar.add(Calendar.DATE, daysFromToday);
+        return calendar;
+    }
 }