/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.dialer.searchfragment.cp2; import android.content.Context; import android.content.CursorLoader; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Directory; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.contacts.ContactsComponent; import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences.DisplayOrder; import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences.SortOrder; import com.android.dialer.searchfragment.common.Projections; import com.android.dialer.searchfragment.common.SearchCursor; import com.android.dialer.smartdial.SmartDialCursorLoader; import com.android.dialer.util.PermissionsUtil; /** Cursor Loader for CP2 contacts. */ public final class SearchContactsCursorLoader extends CursorLoader { private final String query; private final boolean isRegularSearch; /** @param query Contacts cursor will be filtered based on this query. */ public SearchContactsCursorLoader( Context context, @Nullable String query, boolean isRegularSearch) { super( context, buildUri(query), getProjection(context), getWhere(context), null, getSortKey(context) + " ASC"); this.query = TextUtils.isEmpty(query) ? "" : query; this.isRegularSearch = isRegularSearch; } private static String[] getProjection(Context context) { boolean displayOrderPrimary = (ContactsComponent.get(context).contactDisplayPreferences().getDisplayOrder() == DisplayOrder.PRIMARY); return displayOrderPrimary ? Projections.CP2_PROJECTION : Projections.CP2_PROJECTION_ALTERNATIVE; } private static String getWhere(Context context) { String where = getProjection(context)[Projections.DISPLAY_NAME] + " IS NOT NULL"; where += " AND " + Phone.NUMBER + " IS NOT NULL"; return where; } private static String getSortKey(Context context) { boolean sortOrderPrimary = (ContactsComponent.get(context).contactDisplayPreferences().getSortOrder() == SortOrder.BY_PRIMARY); return sortOrderPrimary ? Phone.SORT_KEY_PRIMARY : Phone.SORT_KEY_ALTERNATIVE; } private static Uri buildUri(String query) { return Phone.CONTENT_FILTER_URI.buildUpon().appendPath(query).build(); } @Override public Cursor loadInBackground() { if (!PermissionsUtil.hasContactsReadPermissions(getContext())) { LogUtil.i("SearchContactsCursorLoader.loadInBackground", "Contacts permission denied."); return null; } return isRegularSearch ? regularSearchLoadInBackground() : dialpadSearchLoadInBackground(); } private Cursor regularSearchLoadInBackground() { return RegularSearchCursor.newInstance(getContext(), super.loadInBackground()); } private Cursor dialpadSearchLoadInBackground() { SmartDialCursorLoader loader = new SmartDialCursorLoader(getContext()); loader.configureQuery(query); Cursor cursor = loader.loadInBackground(); return SmartDialCursor.newInstance(getContext(), cursor); } static class SmartDialCursor extends MergeCursor implements SearchCursor { static SmartDialCursor newInstance(Context context, Cursor smartDialCursor) { if (smartDialCursor == null || smartDialCursor.getCount() == 0) { LogUtil.i("SmartDialCursor.newInstance", "Cursor was null or empty"); return new SmartDialCursor(new Cursor[] {new MatrixCursor(Projections.CP2_PROJECTION)}); } MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION); headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)}); return new SmartDialCursor( new Cursor[] {headerCursor, convertSmartDialCursorToSearchCursor(smartDialCursor)}); } private SmartDialCursor(Cursor[] cursors) { super(cursors); } @Override public boolean isHeader() { return isFirst(); } @Override public boolean updateQuery(@Nullable String query) { return false; } @Override public long getDirectoryId() { return Directory.DEFAULT; } private static MatrixCursor convertSmartDialCursorToSearchCursor(Cursor smartDialCursor) { MatrixCursor cursor = new MatrixCursor(Projections.CP2_PROJECTION); if (!smartDialCursor.moveToFirst()) { return cursor; } do { Object[] newRow = new Object[Projections.CP2_PROJECTION.length]; for (int i = 0; i < Projections.CP2_PROJECTION.length; i++) { String column = Projections.CP2_PROJECTION[i]; int index = smartDialCursor.getColumnIndex(column); if (index != -1) { switch (smartDialCursor.getType(index)) { case FIELD_TYPE_INTEGER: newRow[i] = smartDialCursor.getInt(index); break; case FIELD_TYPE_STRING: newRow[i] = smartDialCursor.getString(index); break; case FIELD_TYPE_FLOAT: newRow[i] = smartDialCursor.getFloat(index); break; case FIELD_TYPE_BLOB: newRow[i] = smartDialCursor.getBlob(index); break; case FIELD_TYPE_NULL: default: // No-op break; } } } cursor.addRow(newRow); } while (smartDialCursor.moveToNext()); return cursor; } } static class RegularSearchCursor extends MergeCursor implements SearchCursor { static RegularSearchCursor newInstance(Context context, Cursor regularSearchCursor) { if (regularSearchCursor == null || regularSearchCursor.getCount() == 0) { LogUtil.i("RegularSearchCursor.newInstance", "Cursor was null or empty"); return new RegularSearchCursor(new Cursor[] {new MatrixCursor(Projections.CP2_PROJECTION)}); } MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION); headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)}); return new RegularSearchCursor(new Cursor[] {headerCursor, regularSearchCursor}); } public RegularSearchCursor(Cursor[] cursors) { super(cursors); } @Override public boolean isHeader() { return isFirst(); } @Override public boolean updateQuery(@NonNull String query) { return false; // no-op } @Override public long getDirectoryId() { return 0; // no-op } } }