• 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.searchfragment.cp2;
18 
19 import android.content.Context;
20 import android.content.CursorLoader;
21 import android.database.Cursor;
22 import android.database.MatrixCursor;
23 import android.database.MergeCursor;
24 import android.net.Uri;
25 import android.provider.ContactsContract.CommonDataKinds.Phone;
26 import android.provider.ContactsContract.Directory;
27 import android.support.annotation.NonNull;
28 import android.support.annotation.Nullable;
29 import android.text.TextUtils;
30 import com.android.contacts.common.preference.ContactsPreferences;
31 import com.android.dialer.common.LogUtil;
32 import com.android.dialer.searchfragment.common.Projections;
33 import com.android.dialer.searchfragment.common.SearchCursor;
34 import com.android.dialer.smartdial.SmartDialCursorLoader;
35 import com.android.dialer.util.PermissionsUtil;
36 
37 /** Cursor Loader for CP2 contacts. */
38 public final class SearchContactsCursorLoader extends CursorLoader {
39 
40   private final String query;
41   private final boolean isRegularSearch;
42 
43   /** @param query Contacts cursor will be filtered based on this query. */
SearchContactsCursorLoader( Context context, @Nullable String query, boolean isRegularSearch)44   public SearchContactsCursorLoader(
45       Context context, @Nullable String query, boolean isRegularSearch) {
46     super(
47         context,
48         buildUri(query),
49         getProjection(context),
50         getWhere(context),
51         null,
52         getSortKey(context) + " ASC");
53     this.query = TextUtils.isEmpty(query) ? "" : query;
54     this.isRegularSearch = isRegularSearch;
55   }
56 
getProjection(Context context)57   private static String[] getProjection(Context context) {
58     ContactsPreferences contactsPrefs = new ContactsPreferences(context);
59     boolean displayOrderPrimary =
60         (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY);
61     return displayOrderPrimary
62         ? Projections.CP2_PROJECTION
63         : Projections.CP2_PROJECTION_ALTERNATIVE;
64   }
65 
getWhere(Context context)66   private static String getWhere(Context context) {
67     String where = getProjection(context)[Projections.DISPLAY_NAME] + " IS NOT NULL";
68     where += " AND " + Phone.NUMBER + " IS NOT NULL";
69     return where;
70   }
71 
getSortKey(Context context)72   private static String getSortKey(Context context) {
73     ContactsPreferences contactsPrefs = new ContactsPreferences(context);
74     boolean sortOrderPrimary =
75         (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY);
76     return sortOrderPrimary ? Phone.SORT_KEY_PRIMARY : Phone.SORT_KEY_ALTERNATIVE;
77   }
78 
buildUri(String query)79   private static Uri buildUri(String query) {
80     return Phone.CONTENT_FILTER_URI.buildUpon().appendPath(query).build();
81   }
82 
83   @Override
loadInBackground()84   public Cursor loadInBackground() {
85     if (!PermissionsUtil.hasContactsReadPermissions(getContext())) {
86       LogUtil.i("SearchContactsCursorLoader.loadInBackground", "Contacts permission denied.");
87       return null;
88     }
89     return isRegularSearch ? regularSearchLoadInBackground() : dialpadSearchLoadInBackground();
90   }
91 
regularSearchLoadInBackground()92   private Cursor regularSearchLoadInBackground() {
93     return RegularSearchCursor.newInstance(getContext(), super.loadInBackground());
94   }
95 
dialpadSearchLoadInBackground()96   private Cursor dialpadSearchLoadInBackground() {
97     SmartDialCursorLoader loader = new SmartDialCursorLoader(getContext());
98     loader.configureQuery(query);
99     Cursor cursor = loader.loadInBackground();
100     return SmartDialCursor.newInstance(getContext(), cursor);
101   }
102 
103   static class SmartDialCursor extends MergeCursor implements SearchCursor {
104 
newInstance(Context context, Cursor smartDialCursor)105     static SmartDialCursor newInstance(Context context, Cursor smartDialCursor) {
106       if (smartDialCursor == null || smartDialCursor.getCount() == 0) {
107         LogUtil.i("SmartDialCursor.newInstance", "Cursor was null or empty");
108         return new SmartDialCursor(new Cursor[] {new MatrixCursor(Projections.CP2_PROJECTION)});
109       }
110 
111       MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION);
112       headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)});
113       return new SmartDialCursor(
114           new Cursor[] {headerCursor, convertSmartDialCursorToSearchCursor(smartDialCursor)});
115     }
116 
SmartDialCursor(Cursor[] cursors)117     private SmartDialCursor(Cursor[] cursors) {
118       super(cursors);
119     }
120 
121     @Override
isHeader()122     public boolean isHeader() {
123       return isFirst();
124     }
125 
126     @Override
updateQuery(@ullable String query)127     public boolean updateQuery(@Nullable String query) {
128       return false;
129     }
130 
131     @Override
getDirectoryId()132     public long getDirectoryId() {
133       return Directory.DEFAULT;
134     }
135 
convertSmartDialCursorToSearchCursor(Cursor smartDialCursor)136     private static MatrixCursor convertSmartDialCursorToSearchCursor(Cursor smartDialCursor) {
137       MatrixCursor cursor = new MatrixCursor(Projections.CP2_PROJECTION);
138       if (!smartDialCursor.moveToFirst()) {
139         return cursor;
140       }
141 
142       do {
143         Object[] newRow = new Object[Projections.CP2_PROJECTION.length];
144         for (int i = 0; i < Projections.CP2_PROJECTION.length; i++) {
145           String column = Projections.CP2_PROJECTION[i];
146           int index = smartDialCursor.getColumnIndex(column);
147           if (index != -1) {
148             switch (smartDialCursor.getType(index)) {
149               case FIELD_TYPE_INTEGER:
150                 newRow[i] = smartDialCursor.getInt(index);
151                 break;
152               case FIELD_TYPE_STRING:
153                 newRow[i] = smartDialCursor.getString(index);
154                 break;
155               case FIELD_TYPE_FLOAT:
156                 newRow[i] = smartDialCursor.getFloat(index);
157                 break;
158               case FIELD_TYPE_BLOB:
159                 newRow[i] = smartDialCursor.getBlob(index);
160                 break;
161               case FIELD_TYPE_NULL:
162               default:
163                 // No-op
164                 break;
165             }
166           }
167         }
168         cursor.addRow(newRow);
169       } while (smartDialCursor.moveToNext());
170       return cursor;
171     }
172   }
173 
174   static class RegularSearchCursor extends MergeCursor implements SearchCursor {
175 
newInstance(Context context, Cursor regularSearchCursor)176     static RegularSearchCursor newInstance(Context context, Cursor regularSearchCursor) {
177       if (regularSearchCursor == null || regularSearchCursor.getCount() == 0) {
178         LogUtil.i("RegularSearchCursor.newInstance", "Cursor was null or empty");
179         return new RegularSearchCursor(new Cursor[] {new MatrixCursor(Projections.CP2_PROJECTION)});
180       }
181 
182       MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION);
183       headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)});
184       return new RegularSearchCursor(new Cursor[] {headerCursor, regularSearchCursor});
185     }
186 
RegularSearchCursor(Cursor[] cursors)187     public RegularSearchCursor(Cursor[] cursors) {
188       super(cursors);
189     }
190 
191     @Override
isHeader()192     public boolean isHeader() {
193       return isFirst();
194     }
195 
196     @Override
updateQuery(@onNull String query)197     public boolean updateQuery(@NonNull String query) {
198       return false; // no-op
199     }
200 
201     @Override
getDirectoryId()202     public long getDirectoryId() {
203       return 0; // no-op
204     }
205   }
206 }
207