• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.speeddial;
18 
19 import android.annotation.SuppressLint;
20 import android.database.Cursor;
21 import android.database.MatrixCursor;
22 import android.database.MergeCursor;
23 import android.support.annotation.IntDef;
24 import android.support.annotation.StringRes;
25 import com.android.dialer.common.Assert;
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 import java.util.ArrayList;
29 import java.util.List;
30 
31 /** Cursor for favorites contacts. */
32 final class SpeedDialCursor extends MergeCursor {
33 
34   /**
35    * Caps the speed dial list to contain at most 20 contacts, including favorites and suggestions.
36    * It is only a soft limit though, for the case that there are more than 20 favorite contacts.
37    */
38   private static final int SPEED_DIAL_CONTACT_LIST_SOFT_LIMIT = 20;
39 
40   private static final String[] HEADER_CURSOR_PROJECTION = {"header"};
41   private static final int HEADER_COLUMN_POSITION = 0;
42   private boolean hasFavorites;
43 
44   @Retention(RetentionPolicy.SOURCE)
45   @IntDef({RowType.HEADER, RowType.STARRED, RowType.SUGGESTION})
46   @interface RowType {
47     int HEADER = 0;
48     int STARRED = 1;
49     int SUGGESTION = 2;
50   }
51 
newInstance(Cursor strequentCursor)52   public static SpeedDialCursor newInstance(Cursor strequentCursor) {
53     if (strequentCursor == null || strequentCursor.getCount() == 0) {
54       return null;
55     }
56     SpeedDialCursor cursor = new SpeedDialCursor(buildCursors(strequentCursor));
57     strequentCursor.close();
58     return cursor;
59   }
60 
buildCursors(Cursor strequentCursor)61   private static Cursor[] buildCursors(Cursor strequentCursor) {
62     MatrixCursor starred = new MatrixCursor(StrequentContactsCursorLoader.PHONE_PROJECTION);
63     MatrixCursor suggestions = new MatrixCursor(StrequentContactsCursorLoader.PHONE_PROJECTION);
64 
65     strequentCursor.moveToPosition(-1);
66     while (strequentCursor.moveToNext()) {
67       if (strequentCursor.getPosition() != 0) {
68         long contactId = strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID);
69         int position = strequentCursor.getPosition();
70         boolean duplicate = false;
71         // Iterate backwards through the cursor to check that this isn't a duplicate contact
72         // TODO(calderwoodra): improve this algorithm (currently O(n^2)).
73         while (strequentCursor.moveToPrevious() && !duplicate) {
74           duplicate |=
75               strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID) == contactId;
76         }
77         strequentCursor.moveToPosition(position);
78         if (duplicate) {
79           continue;
80         }
81       }
82 
83       if (strequentCursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) {
84         StrequentContactsCursorLoader.addToCursor(starred, strequentCursor);
85       } else if (starred.getCount() + suggestions.getCount() < SPEED_DIAL_CONTACT_LIST_SOFT_LIMIT) {
86         // Since all starred contacts come before each non-starred contact, it's safe to assume that
87         // this list will never exceed the soft limit unless there are more starred contacts than
88         // the limit permits.
89         StrequentContactsCursorLoader.addToCursor(suggestions, strequentCursor);
90       }
91     }
92 
93     List<Cursor> cursorList = new ArrayList<>();
94     if (starred.getCount() > 0) {
95       cursorList.add(createHeaderCursor(R.string.favorites_header));
96       cursorList.add(starred);
97     }
98     if (suggestions.getCount() > 0) {
99       cursorList.add(createHeaderCursor(R.string.suggestions_header));
100       cursorList.add(suggestions);
101     }
102     return cursorList.toArray(new Cursor[cursorList.size()]);
103   }
104 
createHeaderCursor(@tringRes int header)105   private static Cursor createHeaderCursor(@StringRes int header) {
106     MatrixCursor cursor = new MatrixCursor(HEADER_CURSOR_PROJECTION);
107     cursor.newRow().add(HEADER_CURSOR_PROJECTION[HEADER_COLUMN_POSITION], header);
108     return cursor;
109   }
110 
111   @RowType
getRowType(int position)112   int getRowType(int position) {
113     moveToPosition(position);
114     if (getColumnCount() == 1) {
115       return RowType.HEADER;
116     } else if (getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) {
117       return RowType.STARRED;
118     } else {
119       return RowType.SUGGESTION;
120     }
121   }
122 
123   @SuppressLint("DefaultLocale")
124   @StringRes
getHeader()125   int getHeader() {
126     if (getRowType(getPosition()) != RowType.HEADER) {
127       throw Assert.createIllegalStateFailException(
128           String.format("Current position (%d) is not a header.", getPosition()));
129     }
130     return getInt(HEADER_COLUMN_POSITION);
131   }
132 
hasFavorites()133   public boolean hasFavorites() {
134     return hasFavorites;
135   }
136 
SpeedDialCursor(Cursor[] cursors)137   private SpeedDialCursor(Cursor[] cursors) {
138     super(cursors);
139     for (Cursor cursor : cursors) {
140       cursor.moveToFirst();
141       if (cursor.getColumnCount() != 1
142           && cursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) {
143         hasFavorites = true;
144         break;
145       }
146     }
147   }
148 }
149