Home | History | Annotate | Download | only in cp2
      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