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 com.google.common.annotations.VisibleForTesting;
     20 
     21 import android.database.Cursor;
     22 import android.telephony.PhoneNumberUtils;
     23 import android.text.TextUtils;
     24 import android.text.format.Time;
     25 
     26 import com.android.contacts.common.compat.CompatUtils;
     27 import com.android.contacts.common.util.DateUtils;
     28 import com.android.contacts.common.util.PhoneNumberHelper;
     29 import com.android.dialer.util.AppCompatConstants;
     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          */
     50         public void addGroup(int cursorPosition, int size);
     51 
     52         /**
     53          * Defines the interface for tracking the day group each call belongs to.  Calls in a call
     54          * group are assigned the same day group as the first call in the group.  The day group
     55          * assigns calls to the buckets: Today, Yesterday, Last week, and Other
     56          *
     57          * @param rowId The row Id of the current call.
     58          * @param dayGroup The day group the call belongs in.
     59          */
     60         public void setDayGroup(long rowId, int dayGroup);
     61 
     62         /**
     63          * Defines the interface for clearing the day groupings information on rebind/regroup.
     64          */
     65         public void clearDayGroups();
     66     }
     67 
     68     /**
     69      * Day grouping for call log entries used to represent no associated day group.  Used primarily
     70      * when retrieving the previous day group, but there is no previous day group (i.e. we are at
     71      * the start of the list).
     72      */
     73     public static final int DAY_GROUP_NONE = -1;
     74 
     75     /** Day grouping for calls which occurred today. */
     76     public static final int DAY_GROUP_TODAY = 0;
     77 
     78     /** Day grouping for calls which occurred yesterday. */
     79     public static final int DAY_GROUP_YESTERDAY = 1;
     80 
     81     /** Day grouping for calls which occurred before last week. */
     82     public static final int DAY_GROUP_OTHER = 2;
     83 
     84     /** Instance of the time object used for time calculations. */
     85     private static final Time TIME = new Time();
     86 
     87     /** The object on which the groups are created. */
     88     private final GroupCreator mGroupCreator;
     89 
     90     public CallLogGroupBuilder(GroupCreator groupCreator) {
     91         mGroupCreator = groupCreator;
     92     }
     93 
     94     /**
     95      * Finds all groups of adjacent entries in the call log which should be grouped together and
     96      * calls {@link GroupCreator#addGroup(int, int)} on {@link #mGroupCreator} for each of
     97      * them.
     98      * <p>
     99      * For entries that are not grouped with others, we do not need to create a group of size one.
    100      * <p>
    101      * It assumes that the cursor will not change during its execution.
    102      *
    103      * @see GroupingListAdapter#addGroups(Cursor)
    104      */
    105     public void addGroups(Cursor cursor) {
    106         final int count = cursor.getCount();
    107         if (count == 0) {
    108             return;
    109         }
    110 
    111         // Clear any previous day grouping information.
    112         mGroupCreator.clearDayGroups();
    113 
    114         // Get current system time, used for calculating which day group calls belong to.
    115         long currentTime = System.currentTimeMillis();
    116         cursor.moveToFirst();
    117 
    118         // Determine the day group for the first call in the cursor.
    119         final long firstDate = cursor.getLong(CallLogQuery.DATE);
    120         final long firstRowId = cursor.getLong(CallLogQuery.ID);
    121         int groupDayGroup = getDayGroup(firstDate, currentTime);
    122         mGroupCreator.setDayGroup(firstRowId, groupDayGroup);
    123 
    124         // Instantiate the group values to those of the first call in the cursor.
    125         String groupNumber = cursor.getString(CallLogQuery.NUMBER);
    126         String groupPostDialDigits = CompatUtils.isNCompatible()
    127                 ? cursor.getString(CallLogQuery.POST_DIAL_DIGITS) : "";
    128         String groupViaNumbers = CompatUtils.isNCompatible()
    129                 ? cursor.getString(CallLogQuery.VIA_NUMBER) : "";
    130         int groupCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
    131         String groupAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
    132         String groupAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
    133         int groupSize = 1;
    134 
    135         String number;
    136         String numberPostDialDigits;
    137         String numberViaNumbers;
    138         int callType;
    139         String accountComponentName;
    140         String accountId;
    141 
    142         while (cursor.moveToNext()) {
    143             // Obtain the values for the current call to group.
    144             number = cursor.getString(CallLogQuery.NUMBER);
    145             numberPostDialDigits = CompatUtils.isNCompatible()
    146                     ? cursor.getString(CallLogQuery.POST_DIAL_DIGITS) : "";
    147             numberViaNumbers = CompatUtils.isNCompatible()
    148                     ? cursor.getString(CallLogQuery.VIA_NUMBER) : "";
    149             callType = cursor.getInt(CallLogQuery.CALL_TYPE);
    150             accountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
    151             accountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
    152 
    153             final boolean isSameNumber = equalNumbers(groupNumber, number);
    154             final boolean isSamePostDialDigits = groupPostDialDigits.equals(numberPostDialDigits);
    155             final boolean isSameViaNumbers = groupViaNumbers.equals(numberViaNumbers);
    156             final boolean isSameAccount = isSameAccount(
    157                     groupAccountComponentName, accountComponentName, groupAccountId, accountId);
    158 
    159             // Group with the same number and account. Never group voicemails. Only group blocked
    160             // calls with other blocked calls.
    161             if (isSameNumber && isSameAccount && isSamePostDialDigits && isSameViaNumbers
    162                     && areBothNotVoicemail(callType, groupCallType)
    163                     && (areBothNotBlocked(callType, groupCallType)
    164                             || areBothBlocked(callType, groupCallType))) {
    165                 // Increment the size of the group to include the current call, but do not create
    166                 // the group until finding a call that does not match.
    167                 groupSize++;
    168             } else {
    169                 // The call group has changed. Determine the day group for the new call group.
    170                 final long date = cursor.getLong(CallLogQuery.DATE);
    171                 groupDayGroup = getDayGroup(date, currentTime);
    172 
    173                 // Create a group for the previous group of calls, which does not include the
    174                 // current call.
    175                 mGroupCreator.addGroup(cursor.getPosition() - groupSize, groupSize);
    176 
    177                 // Start a new group; it will include at least the current call.
    178                 groupSize = 1;
    179 
    180                 // Update the group values to those of the current call.
    181                 groupNumber = number;
    182                 groupPostDialDigits = numberPostDialDigits;
    183                 groupViaNumbers = numberViaNumbers;
    184                 groupCallType = callType;
    185                 groupAccountComponentName = accountComponentName;
    186                 groupAccountId = accountId;
    187             }
    188 
    189             // Save the day group associated with the current call.
    190             final long currentCallId = cursor.getLong(CallLogQuery.ID);
    191             mGroupCreator.setDayGroup(currentCallId, groupDayGroup);
    192         }
    193 
    194         // Create a group for the last set of calls.
    195         mGroupCreator.addGroup(count - groupSize, groupSize);
    196     }
    197 
    198     /**
    199      * Group cursor entries by date, with only one entry per group. This is used for listing
    200      * voicemails in the archive tab.
    201      */
    202     public void addVoicemailGroups(Cursor cursor) {
    203         if (cursor.getCount() == 0) {
    204             return;
    205         }
    206 
    207         // Clear any previous day grouping information.
    208         mGroupCreator.clearDayGroups();
    209 
    210         // Get current system time, used for calculating which day group calls belong to.
    211         long currentTime = System.currentTimeMillis();
    212 
    213         // Reset cursor to start before the first row
    214         cursor.moveToPosition(-1);
    215 
    216         // Create an individual group for each voicemail
    217         while (cursor.moveToNext()) {
    218             mGroupCreator.addGroup(cursor.getPosition(), 1);
    219             mGroupCreator.setDayGroup(cursor.getLong(CallLogQuery.ID),
    220                     getDayGroup(cursor.getLong(CallLogQuery.DATE), currentTime));
    221 
    222         }
    223     }
    224 
    225     @VisibleForTesting
    226     boolean equalNumbers(String number1, String number2) {
    227         if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) {
    228             return compareSipAddresses(number1, number2);
    229         } else {
    230             return PhoneNumberUtils.compare(number1, number2);
    231         }
    232     }
    233 
    234     private boolean isSameAccount(String name1, String name2, String id1, String id2) {
    235         return TextUtils.equals(name1, name2) && TextUtils.equals(id1, id2);
    236     }
    237 
    238     @VisibleForTesting
    239     boolean compareSipAddresses(String number1, String number2) {
    240         if (number1 == null || number2 == null) return number1 == number2;
    241 
    242         int index1 = number1.indexOf('@');
    243         final String userinfo1;
    244         final String rest1;
    245         if (index1 != -1) {
    246             userinfo1 = number1.substring(0, index1);
    247             rest1 = number1.substring(index1);
    248         } else {
    249             userinfo1 = number1;
    250             rest1 = "";
    251         }
    252 
    253         int index2 = number2.indexOf('@');
    254         final String userinfo2;
    255         final String rest2;
    256         if (index2 != -1) {
    257             userinfo2 = number2.substring(0, index2);
    258             rest2 = number2.substring(index2);
    259         } else {
    260             userinfo2 = number2;
    261             rest2 = "";
    262         }
    263 
    264         return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2);
    265     }
    266 
    267     /**
    268      * Given a call date and the current date, determine which date group the call belongs in.
    269      *
    270      * @param date The call date.
    271      * @param now The current date.
    272      * @return The date group the call belongs in.
    273      */
    274     private int getDayGroup(long date, long now) {
    275         int days = DateUtils.getDayDifference(TIME, date, now);
    276 
    277         if (days == 0) {
    278             return DAY_GROUP_TODAY;
    279         } else if (days == 1) {
    280             return DAY_GROUP_YESTERDAY;
    281         } else {
    282             return DAY_GROUP_OTHER;
    283         }
    284     }
    285 
    286     private boolean areBothNotVoicemail(int callType, int groupCallType) {
    287         return callType != AppCompatConstants.CALLS_VOICEMAIL_TYPE
    288                 && groupCallType != AppCompatConstants.CALLS_VOICEMAIL_TYPE;
    289     }
    290 
    291     private boolean areBothNotBlocked(int callType, int groupCallType) {
    292         return callType != AppCompatConstants.CALLS_BLOCKED_TYPE
    293                 && groupCallType != AppCompatConstants.CALLS_BLOCKED_TYPE;
    294     }
    295 
    296     private boolean areBothBlocked(int callType, int groupCallType) {
    297         return callType == AppCompatConstants.CALLS_BLOCKED_TYPE
    298                 && groupCallType == AppCompatConstants.CALLS_BLOCKED_TYPE;
    299     }
    300 }
    301