Merge "Add hours of operation info to incall business context." into ub-contactsdialer-a-dev
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);
+    }
+}