192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda/*
292a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * Copyright (C) 2011 The Android Open Source Project
392a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda *
492a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * Licensed under the Apache License, Version 2.0 (the "License");
592a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * you may not use this file except in compliance with the License.
692a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * You may obtain a copy of the License at
792a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda *
892a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda *      http://www.apache.org/licenses/LICENSE-2.0
992a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda *
1092a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * Unless required by applicable law or agreed to in writing, software
1192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * distributed under the License is distributed on an "AS IS" BASIS,
1292a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1392a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * See the License for the specific language governing permissions and
1492a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * limitations under the License.
1592a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda */
1692a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
1792a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerdapackage com.android.contacts.calllog;
1892a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
1992a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerdaimport android.database.Cursor;
2092a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerdaimport android.provider.CallLog.Calls;
2192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerdaimport android.telephony.PhoneNumberUtils;
2292a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
23e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.common.widget.GroupingListAdapter;
24e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.annotations.VisibleForTesting;
25e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng
2692a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda/**
2792a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda * Groups together calls in the call log.
286fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda * <p>
296fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda * This class is meant to be used in conjunction with {@link GroupingListAdapter}.
3092a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda */
3192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerdapublic class CallLogGroupBuilder {
32de4f16aa0ff53c2756a3f9bf67ad7cb59b4d1aa3Flavio Lerda    public interface GroupCreator {
33de4f16aa0ff53c2756a3f9bf67ad7cb59b4d1aa3Flavio Lerda        public void addGroup(int cursorPosition, int size, boolean expanded);
34de4f16aa0ff53c2756a3f9bf67ad7cb59b4d1aa3Flavio Lerda    }
35de4f16aa0ff53c2756a3f9bf67ad7cb59b4d1aa3Flavio Lerda
366fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda    /** The object on which the groups are created. */
37de4f16aa0ff53c2756a3f9bf67ad7cb59b4d1aa3Flavio Lerda    private final GroupCreator mGroupCreator;
3892a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
39de4f16aa0ff53c2756a3f9bf67ad7cb59b4d1aa3Flavio Lerda    public CallLogGroupBuilder(GroupCreator groupCreator) {
406fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        mGroupCreator = groupCreator;
4192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda    }
4292a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
436fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda    /**
446fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * Finds all groups of adjacent entries in the call log which should be grouped together and
454885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda     * calls {@link GroupCreator#addGroup(int, int, boolean)} on {@link #mGroupCreator} for each of
464885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda     * them.
476fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * <p>
486fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * For entries that are not grouped with others, we do not need to create a group of size one.
496fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * <p>
506fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * It assumes that the cursor will not change during its execution.
516fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     *
526fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * @see GroupingListAdapter#addGroups(Cursor)
536fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     */
5492a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda    public void addGroups(Cursor cursor) {
556fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        final int count = cursor.getCount();
5692a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda        if (count == 0) {
5792a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda            return;
5892a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda        }
5992a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
606fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        int currentGroupSize = 1;
6192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda        cursor.moveToFirst();
624885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda        // The number of the first entry in the group.
634885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda        String firstNumber = cursor.getString(CallLogQuery.NUMBER);
646fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        // This is the type of the first call in the group.
656fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
666fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        while (cursor.moveToNext()) {
674885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda            // The number of the current row in the cursor.
684885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda            final String currentNumber = cursor.getString(CallLogQuery.NUMBER);
696fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            final int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
70ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            final boolean sameNumber = equalNumbers(firstNumber, currentNumber);
716fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            final boolean shouldGroup;
7292a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
73568ad27e706d7c75dd7412e34c2236c772704a04Flavio Lerda            if (CallLogQuery.isSectionHeader(cursor)) {
746fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // Cannot group headers.
756fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                shouldGroup = false;
766fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            } else if (!sameNumber) {
776fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // Should only group with calls from the same number.
786fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                shouldGroup = false;
79e58bbc474eb60c403904b19259439fbf181a790eChiao Cheng            } else if (firstCallType == Calls.VOICEMAIL_TYPE) {
80e58bbc474eb60c403904b19259439fbf181a790eChiao Cheng                // never group voicemail.
81e58bbc474eb60c403904b19259439fbf181a790eChiao Cheng                shouldGroup = false;
8292a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda            } else {
83e58bbc474eb60c403904b19259439fbf181a790eChiao Cheng                // Incoming, outgoing, and missed calls group together.
84e58bbc474eb60c403904b19259439fbf181a790eChiao Cheng                shouldGroup = (callType == Calls.INCOMING_TYPE || callType == Calls.OUTGOING_TYPE ||
85e58bbc474eb60c403904b19259439fbf181a790eChiao Cheng                        callType == Calls.MISSED_TYPE);
866fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            }
8792a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
886fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            if (shouldGroup) {
896fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // Increment the size of the group to include the current call, but do not create
906fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // the group until we find a call that does not match.
916fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                currentGroupSize++;
926fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            } else {
936fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // Create a group for the previous set of calls, excluding the current one, but do
946fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // not create a group for a single call.
956fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                if (currentGroupSize > 1) {
966fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                    addGroup(cursor.getPosition() - currentGroupSize, currentGroupSize);
9792a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda                }
986fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                // Start a new group; it will include at least the current call.
996fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                currentGroupSize = 1;
1004885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda                // The current entry is now the first in the group.
1016fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda                firstNumber = currentNumber;
1024885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda                firstCallType = callType;
10392a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda            }
10492a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda        }
1056fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        // If the last set of calls at the end of the call log was itself a group, create it now.
1066fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        if (currentGroupSize > 1) {
1076fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda            addGroup(count - currentGroupSize, currentGroupSize);
10892a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda        }
10992a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda    }
11092a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
1116fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda    /**
1126fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * Creates a group of items in the cursor.
1136fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * <p>
1146fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     * The group is always unexpanded.
1156fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     *
116568ad27e706d7c75dd7412e34c2236c772704a04Flavio Lerda     * @see CallLogAdapter#addGroup(int, int, boolean)
1176fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda     */
1186fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda    private void addGroup(int cursorPosition, int size) {
1196fb088eca53d723edb4ea0803751eff484c9ae8bFlavio Lerda        mGroupCreator.addGroup(cursorPosition, size, false);
12092a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda    }
12192a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda
122ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda    @VisibleForTesting
1234885a0250ad6720450c9809814b851b7039f5e17Flavio Lerda    boolean equalNumbers(String number1, String number2) {
124ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        if (PhoneNumberUtils.isUriNumber(number1) || PhoneNumberUtils.isUriNumber(number2)) {
125ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            return compareSipAddresses(number1, number2);
126ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        } else {
127ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            return PhoneNumberUtils.compare(number1, number2);
128ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        }
129ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda    }
130ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda
131ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda    @VisibleForTesting
132ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda    boolean compareSipAddresses(String number1, String number2) {
133ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        if (number1 == null || number2 == null) return number1 == number2;
134ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda
135ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        int index1 = number1.indexOf('@');
136ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        final String userinfo1;
137ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        final String rest1;
138ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        if (index1 != -1) {
139ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            userinfo1 = number1.substring(0, index1);
140ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            rest1 = number1.substring(index1);
141ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        } else {
142ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            userinfo1 = number1;
143ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            rest1 = "";
144ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        }
145ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda
146ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        int index2 = number2.indexOf('@');
147ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        final String userinfo2;
148ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        final String rest2;
149ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        if (index2 != -1) {
150ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            userinfo2 = number2.substring(0, index2);
151ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            rest2 = number2.substring(index2);
152ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        } else {
153ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            userinfo2 = number2;
154ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda            rest2 = "";
155ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        }
156ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda
157ffa8963f6d5d0fd672c11dea2c2d16048da36577Flavio Lerda        return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2);
15892a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda    }
15992a26f3d90c3ef3fd24e45d6c6d594ea57054427Flavio Lerda}
160