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. */ 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 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 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 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 79 private static Uri buildUri(String query) { 80 return Phone.CONTENT_FILTER_URI.buildUpon().appendPath(query).build(); 81 } 82 83 @Override 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 92 private Cursor regularSearchLoadInBackground() { 93 return RegularSearchCursor.newInstance(getContext(), super.loadInBackground()); 94 } 95 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 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 117 private SmartDialCursor(Cursor[] cursors) { 118 super(cursors); 119 } 120 121 @Override 122 public boolean isHeader() { 123 return isFirst(); 124 } 125 126 @Override 127 public boolean updateQuery(@Nullable String query) { 128 return false; 129 } 130 131 @Override 132 public long getDirectoryId() { 133 return Directory.DEFAULT; 134 } 135 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 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 187 public RegularSearchCursor(Cursor[] cursors) { 188 super(cursors); 189 } 190 191 @Override 192 public boolean isHeader() { 193 return isFirst(); 194 } 195 196 @Override 197 public boolean updateQuery(@NonNull String query) { 198 return false; // no-op 199 } 200 201 @Override 202 public long getDirectoryId() { 203 return 0; // no-op 204 } 205 } 206 } 207