Merge "DO NOT MERGE Ensure call subject is hidden if disabled for carrier." into ub-contactsdialer-b-dev
diff --git a/InCallUI/res/layout-land/call_card_fragment.xml b/InCallUI/res/layout-land/call_card_fragment.xml
index b0a534e..c71cf07 100644
--- a/InCallUI/res/layout-land/call_card_fragment.xml
+++ b/InCallUI/res/layout-land/call_card_fragment.xml
@@ -63,7 +63,7 @@
             android:layout_width="match_parent"
             android:id="@+id/call_card_content">
 
-            <ImageView android:id="@+id/photo"
+            <ImageView android:id="@+id/photoLarge"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:scaleType="centerCrop"
@@ -71,12 +71,35 @@
                 android:background="@color/incall_photo_background_color"
                 android:src="@drawable/img_no_image_automirrored" />
 
+            <!-- Call context -->
+            <LinearLayout
+                android:id="@+id/contact_context"
+                android:layout_height="match_parent"
+                android:layout_width="match_parent"
+                android:orientation="vertical"
+                android:background="@color/incall_background_color"
+                android:visibility="gone">
+                <TextView android:id="@+id/contactContextTitle"
+                    android:textSize="@dimen/contact_context_title_text_size"
+                    android:textColor="@color/contact_context_title_text_color"
+                    android:fontFamily="sans-serif-medium"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:layout_marginBottom="@dimen/contact_context_title_margin_bottom" />
+                <ListView android:id="@+id/contactContextInfo"
+                     android:layout_width="match_parent"
+                     android:layout_height="match_parent"
+                     android:divider="@null"
+                     android:dividerHeight="@dimen/contact_context_list_item_padding" />
+            </LinearLayout>
+
         </FrameLayout>
 
         <include layout="@layout/manage_conference_call_button"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_alignTop="@id/photo" />
+            android:layout_alignTop="@id/photoLarge" />
 
         <!-- Progress spinner, useful for indicating pending operations such as upgrade to video. -->
         <FrameLayout
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/Call.java b/InCallUI/src/com/android/incallui/Call.java
index c82b7a5..7b2724a 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -209,12 +209,16 @@
         public static final int INITIATION_VOICEMAIL_LOG = 9;
         public static final int INITIATION_CALL_DETAILS = 10;
         public static final int INITIATION_QUICK_CONTACTS = 11;
+        public static final int INITIATION_EXTERNAL = 12;
 
         public DisconnectCause disconnectCause;
         public boolean isIncoming = false;
         public int contactLookupResult = LOOKUP_UNKNOWN;
-        public int callInitiationMethod = INITIATION_UNKNOWN;
+        public int callInitiationMethod = INITIATION_EXTERNAL;
+        // If this was a conference call, the total number of calls involved in the conference.
+        public int conferencedCalls = 0;
         public long duration = 0;
+        public boolean isLogged = false;
 
         @Override
         public String toString() {
@@ -441,12 +445,17 @@
         }
 
         mChildCallIds.clear();
-        for (int i = 0; i < mTelecomCall.getChildren().size(); i++) {
+        final int numChildCalls = mTelecomCall.getChildren().size();
+        for (int i = 0; i < numChildCalls; i++) {
             mChildCallIds.add(
                     CallList.getInstance().getCallByTelecomCall(
                             mTelecomCall.getChildren().get(i)).getId());
         }
 
+        // The number of conferenced calls can change over the course of the call, so use the
+        // maximum number of conferenced child calls as the metric for conference call usage.
+        mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
+
         Bundle callExtras = mTelecomCall.getDetails().getExtras();
         if (callExtras != null) {
             // Child address arrives when the call is first set up, so we do not need to notify the
@@ -814,7 +823,7 @@
         } else if (getIntentExtras() != null) {
             getLogState().callInitiationMethod =
                 getIntentExtras().getInt(IntentUtil.EXTRA_CALL_INITIATION_TYPE,
-                        LogState.INITIATION_UNKNOWN);
+                        LogState.INITIATION_EXTERNAL);
         }
     }
 
diff --git a/InCallUI/src/com/android/incallui/CallerInfo.java b/InCallUI/src/com/android/incallui/CallerInfo.java
index 3ddeb71..2ed5722 100644
--- a/InCallUI/src/com/android/incallui/CallerInfo.java
+++ b/InCallUI/src/com/android/incallui/CallerInfo.java
@@ -31,6 +31,7 @@
 
 import com.android.contacts.common.util.PhoneNumberHelper;
 import com.android.contacts.common.util.TelephonyManagerUtils;
+import com.android.dialer.calllog.ContactInfoHelper;
 
 /**
  * Looks up caller information for the given phone number.
@@ -177,6 +178,12 @@
                     info.name = cursor.getString(columnIndex);
                 }
 
+                columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
+                if (columnIndex != -1) {
+                    info.nameAlternative = ContactInfoHelper.lookUpDisplayNameAlternative(
+                            context, cursor.getString(columnIndex));
+                }
+
                 // Look for the number
                 columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
                 if (columnIndex != -1) {
diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java
index 098f82b..c5176b1 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;
@@ -621,7 +624,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/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index aa150ad..7b53ed7 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -55,6 +55,8 @@
 import com.android.contacts.common.interactions.TouchPointManager;
 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.logging.ScreenEvent;
 import com.android.incallui.Call.State;
 
 import java.util.ArrayList;
@@ -695,6 +697,7 @@
             } else {
                 transaction.show(fragment);
             }
+            Logger.logScreenView(getScreenTypeForTag(tag), this);
         } else {
             transaction.hide(fragment);
         }
@@ -736,6 +739,21 @@
         throw new IllegalStateException("Unexpected fragment: " + tag);
     }
 
+    private int getScreenTypeForTag(String tag) {
+        switch (tag) {
+            case TAG_DIALPAD_FRAGMENT:
+                return ScreenEvent.INCALL_DIALPAD;
+            case TAG_CALLCARD_FRAGMENT:
+                return ScreenEvent.INCALL;
+            case TAG_CONFERENCE_FRAGMENT:
+                return ScreenEvent.CONFERENCE_MANAGEMENT;
+            case TAG_ANSWER_FRAGMENT:
+                return ScreenEvent.INCOMING_CALL;
+            default:
+                return ScreenEvent.UNKNOWN;
+        }
+    }
+
     private int getContainerIdForFragment(String tag) {
         if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
             return R.id.answer_and_dialpad_container;
diff --git a/InCallUI/src/com/android/incallui/InCallContactInteractions.java b/InCallUI/src/com/android/incallui/InCallContactInteractions.java
index 918d39b..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,27 +134,38 @@
      */
     @VisibleForTesting
     BusinessContextInfo constructHoursInfo(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) {
+            List<Pair<Calendar, Calendar>> openingHours) {
+        if (currentTime == null || openingHours == null || openingHours.size() == 0) {
             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);
+        BusinessContextInfo hoursInfo = new BusinessContextInfo();
+        hoursInfo.iconId = R.drawable.ic_schedule_white_24dp;
+
+        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.detail = mContext.getString(
-                R.string.opening_hours,
-                DateFormat.getTimeFormat(mContext).format(openTime.getTime()),
-                DateFormat.getTimeFormat(mContext).format(closeTime.getTime()));
+        hoursInfo.heading = isOpen ? mContext.getString(R.string.open_now)
+                : mContext.getString(R.string.closed_now);
+
         return hoursInfo;
     }
 
@@ -205,25 +215,6 @@
     }
 
     /**
-     * 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;
-    }
-
-    /**
      * 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/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 5ba4b70..de563f5 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -24,7 +24,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Point;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.CallLog;
@@ -33,7 +32,6 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
-import android.telephony.PhoneNumberUtils;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -51,6 +49,8 @@
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
 import com.android.dialer.filterednumber.FilteredNumbersUtil;
+import com.android.dialer.logging.InteractionEvent;
+import com.android.dialer.logging.Logger;
 import com.android.incallui.util.TelecomCallUtil;
 import com.android.incalluibind.ObjectFactory;
 import com.google.common.base.Preconditions;
@@ -544,6 +544,7 @@
                 } else {
                     call.reject(false, null);
                     Log.d(this, "checkForBlockedCall: " + Log.pii(number) + " blocked.");
+                    Logger.logInteraction(InteractionEvent.CALL_BLOCKED);
 
                     mFilteredQueryHandler.incrementFilteredCount(id);
 
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index 43fc95e..4b57278 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -225,9 +225,7 @@
         final String contentTitle = getContentTitle(contactInfo, call);
 
         final int notificationType;
-        if ((state == Call.State.INCOMING
-                || state == Call.State.CALL_WAITING) &&
-                        !InCallPresenter.getInstance().isShowingInCallUi()) {
+        if (state == Call.State.INCOMING || state == Call.State.CALL_WAITING) {
             notificationType = NOTIFICATION_INCOMING_CALL;
         } else {
             notificationType = NOTIFICATION_IN_CALL;
@@ -252,7 +250,8 @@
         builder.setContentIntent(inCallPendingIntent);
 
         // Set the intent as a full screen intent as well if a call is incoming
-        if (notificationType == NOTIFICATION_INCOMING_CALL) {
+        if (notificationType == NOTIFICATION_INCOMING_CALL
+                && !InCallPresenter.getInstance().isShowingInCallUi()) {
             configureFullScreenIntent(builder, inCallPendingIntent, call);
             // Set the notification category for incoming calls
             builder.setCategory(Notification.CATEGORY_CALL);
diff --git a/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java b/InCallUI/tests/src/com/android/incallui/InCallContactInteractionsTest.java
index b97be01..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,44 +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() {
+        assertEquals(mContext.getString(R.string.open_now),
+                mInCallContactInteractions.constructHoursInfo(
+                        getTestCalendarWithHour(10),
+                        Arrays.asList(
+                                Pair.create(
+                                        getTestCalendarWithHour(8),
+                                        getTestCalendarWithHourAndDaysFromToday(1, 1))))
+                .heading);
+    }
+
+    public void testIsOpenNow_Open24Hours() {
+        assertEquals(mContext.getString(R.string.open_now),
+                mInCallContactInteractions.constructHoursInfo(
+                        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() {
@@ -130,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;
+    }
 }