Home | History | Annotate | Download | only in calllog
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dialer.calllog;
     18 
     19 import android.database.Cursor;
     20 import android.provider.CallLog.Calls;
     21 import android.telephony.PhoneNumberUtils;
     22 import android.text.format.Time;
     23 
     24 import com.android.contacts.common.util.DateUtils;
     25 import com.android.contacts.common.util.PhoneNumberHelper;
     26 
     27 import com.google.common.annotations.VisibleForTesting;
     28 
     29 import java.util.Objects;
     30 
     31 /**
     32  * Groups together calls in the call log.  The primary grouping attempts to group together calls
     33  * to and from the same number into a single row on the call log.
     34  * A secondary grouping assigns calls, grouped via the primary grouping, to "day groups".  The day
     35  * groups provide a means of identifying the calls which occurred "Today", "Yesterday", "Last week",
     36  * or "Other".
     37  * <p>
     38  * This class is meant to be used in conjunction with {@link GroupingListAdapter}.
     39  */
     40 public class CallLogGroupBuilder {
     41     public interface GroupCreator {
     42 
     43         /**
     44          * Defines the interface for adding a group to the call log.
     45          * The primary group for a call log groups the calls together based on the number which was
     46          * dialed.
     47          * @param cursorPosition The starting position of the group in the cursor.
     48          * @param size The size of the group.
     49          * @param expanded Whether the group is expanded; always false for the call log.
     50          */
     51         public void addGroup(int cursorPosition, int size, boolean expanded);
     52 
     53         /**
     54          * Defines the interface for tracking the day group each call belongs to.  Calls in a call
     55          * group are assigned the same day group as the first call in the group.  The day group
     56          * assigns calls to the buckets: Today, Yesterday, Last week, and Other
     57          *
     58          * @param rowId The row Id of the current call.
     59          * @param dayGroup The day group the call belongs in.
     60          */
     61         public void setDayGroup(long rowId, int dayGroup);
     62 
     63         /**
     64          * Defines the interface for clearing the day groupings information on rebind/regroup.
     65          */
     66         public void clearDayGroups();
     67     }
     68 
     69     /**
     70      * Day grouping for call log entries used to represent no associated day group.  Used primarily
     71      * when retrieving the previous day group, but there is no previous day group (i.e. we are at
     72      * the start of the list).
     73      */
     74     public static final int DAY_GROUP_NONE = -1;
     75 
     76     /** Day grouping for calls which occurred today. */
     77     public static final int DAY_GROUP_TODAY = 0;
     78 
     79     /** Day grouping for calls which occurred yesterday. */
     80     public static final int DAY_GROUP_YESTERDAY = 1;
     81 
     82     /** Day grouping for calls which occurred before last week. */
     83     public static final int DAY_GROUP_OTHER = 2;
     84 
     85     /** Instance of the time object used for time calculations. */
     86     private static final Time TIME = new Time();
     87 
     88     /** The object on which the groups are created. */
     89     private final GroupCreator mGroupCreator;
     90 
     91     public CallLogGroupBuilder(GroupCreator groupCreator) {
     92         mGroupCreator = groupCreator;
     93     }
     94 
     95     /**
     96      * Finds all groups of adjacent entries in the call log which should be grouped together and
     97      * calls {@link GroupCreator#addGroup(int, int, boolean)} on {@link #mGroupCreator} for each of
     98      * them.
     99      * <p>
    100      * For entries that are not grouped with others, we do not need to create a group of size one.
    101      * <p>
    102      * It assumes that the cursor will not change during its execution.
    103      *
    104      * @see GroupingListAdapter#addGroups(Cursor)
    105      */
    106     public void addGroups(Cursor cursor) {
    107         final int count = cursor.getCount();
    108         if (count == 0) {
    109             return;
    110         }
    111 
    112         // Clear any previous day grouping information.
    113         mGroupCreator.clearDayGroups();
    114 
    115         // Get current system time, used for calculating which day group calls belong to.
    116         long currentTime = System.currentTimeMillis();
    117 
    118         int currentGroupSize = 1;
    119         cursor.moveToFirst();
    120         // The number of the first entry in the group.
    121         String firstNumber = cursor.getString(CallLogQuery.NUMBER);
    122         // This is the type of the first call in the group.
    123         int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
    124 
    125         // The account information of the first entry in the group.
    126         String firstAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
    127         String firstAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
    128 
    129         // Determine the day group for the first call in the cursor.
    130         final long firstDate = cursor.getLong(CallLogQuery.DATE);
    131         final long firstRowId = cursor.getLong(CallLogQuery.ID);
    132         int currentGroupDayGroup = getDayGroup(firstDate, currentTime);
    133         mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup);
    134 
    135         while (cursor.moveToNext()) {
    136             // The number of the current row in the cursor.
    137             final String currentNumber = cursor.getString(CallLogQuery.NUMBER);
    138             final int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
    139             final String currentAccountComponentName = cursor.getString(
    140                     CallLogQuery.ACCOUNT_COMPONENT_NAME);
    141             final String currentAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
    142 
    143             final boolean sameNumber = equalNumbers(firstNumber, currentNumber);
    144             final boolean sameAccountComponentName = Objects.equals(
    145                     firstAccountComponentName,
    146                     currentAccountComponentName);
    147             final boolean sameAccountId = Objects.equals(
    148                     firstAccountId,
    149                     currentAccountId);
    150             final boolean sameAccount = sameAccountComponentName && sameAccountId;
    151 
    152             final boolean shouldGroup;
    153             final long currentCallId = cursor.getLong(CallLogQuery.ID);
    154             final long date = cursor.getLong(CallLogQuery.DATE);
    155 
    156             if (!sameNumber || !sameAccount) {
    157                 // Should only group with calls from the same number.
    158                 shouldGroup = false;
    159             } else if (firstCallType == Calls.VOICEMAIL_TYPE) {
    160                 // never group voicemail.
    161                 shouldGroup = false;
    162             } else {
    163                 // Incoming, outgoing, and missed calls group together.
    164                 shouldGroup = callType != Calls.VOICEMAIL_TYPE;
    165             }
    166 
    167             if (shouldGroup) {
    168                 // Increment the size of the group to include the current call, but do not create
    169                 // the group until we find a call that does not match.
    170                 currentGroupSize++;
    171             } else {
    172                 // The call group has changed, so determine the day group for the new call group.
    173                 // This ensures all calls grouped together in the call log are assigned the same
    174                 // day group.
    175                 currentGroupDayGroup = getDayGroup(date, currentTime);
    176 
    177                 // Create a group for the previous set of calls, excluding the current one, but do
    178                 // not create a group for a single call.
    179                 if (currentGroupSize > 1) {
    180                     addGroup(cursor.getPosition() - currentGroupSize, currentGroupSize);
    181                 }
    182                 // Start a new group; it will include at least the current call.
    183                 currentGroupSize = 1;
    184                 // The current entry is now the first in the group.
    185                 firstNumber = currentNumber;
    186                 firstCallType = callType;
    187                 firstAccountComponentName = currentAccountComponentName;
    188                 firstAccountId = currentAccountId;
    189             }
    190 
    191             // Save the day group associated with the current call.
    192             mGroupCreator.setDayGroup(currentCallId, currentGroupDayGroup);
    193         }
    194         // If the last set of calls at the end of the call log was itself a group, create it now.
    195         if (currentGroupSize > 1) {
    196             addGroup(count - currentGroupSize, currentGroupSize);
    197         }
    198     }
    199 
    200     /**
    201      * Creates a group of items in the cursor.
    202      * <p>
    203      * The group is always unexpanded.
    204      *
    205      * @see CallLogAdapter#addGroup(int, int, boolean)
    206      */
    207     private void addGroup(int cursorPosition, int size) {
    208         mGroupCreator.addGroup(cursorPosition, size, false);
    209     }
    210 
    211     @VisibleForTesting
    212     boolean equalNumbers(String number1, String number2) {
    213         if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) {
    214             return compareSipAddresses(number1, number2);
    215         } else {
    216             return PhoneNumberUtils.compare(number1, number2);
    217         }
    218     }
    219 
    220     @VisibleForTesting
    221     boolean compareSipAddresses(String number1, String number2) {
    222         if (number1 == null || number2 == null) return number1 == number2;
    223 
    224         int index1 = number1.indexOf('@');
    225         final String userinfo1;
    226         final String rest1;
    227         if (index1 != -1) {
    228             userinfo1 = number1.substring(0, index1);
    229             rest1 = number1.substring(index1);
    230         } else {
    231             userinfo1 = number1;
    232             rest1 = "";
    233         }
    234 
    235         int index2 = number2.indexOf('@');
    236         final String userinfo2;
    237         final String rest2;
    238         if (index2 != -1) {
    239             userinfo2 = number2.substring(0, index2);
    240             rest2 = number2.substring(index2);
    241         } else {
    242             userinfo2 = number2;
    243             rest2 = "";
    244         }
    245 
    246         return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2);
    247     }
    248 
    249     /**
    250      * Given a call date and the current date, determine which date group the call belongs in.
    251      *
    252      * @param date The call date.
    253      * @param now The current date.
    254      * @return The date group the call belongs in.
    255      */
    256     private int getDayGroup(long date, long now) {
    257         int days = DateUtils.getDayDifference(TIME, date, now);
    258 
    259         if (days == 0) {
    260             return DAY_GROUP_TODAY;
    261         } else if (days == 1) {
    262             return DAY_GROUP_YESTERDAY;
    263         } else {
    264             return DAY_GROUP_OTHER;
    265         }
    266     }
    267 }
    268