Home | History | Annotate | Download | only in list
      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.list;
     18 
     19 import android.database.MatrixCursor;
     20 import android.support.annotation.IntDef;
     21 import android.support.annotation.Nullable;
     22 import android.support.annotation.VisibleForTesting;
     23 import com.android.dialer.common.Assert;
     24 import com.android.dialer.searchfragment.common.SearchCursor;
     25 import java.lang.annotation.Retention;
     26 import java.lang.annotation.RetentionPolicy;
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 
     30 /**
     31  * Manages all of the cursors needed for {@link SearchAdapter}.
     32  *
     33  * <p>This class accepts four data sources:
     34  *
     35  * <ul>
     36  *   <li>A contacts cursor {@link #setContactsCursor(SearchCursor)}
     37  *   <li>A google search results cursor {@link #setNearbyPlacesCursor(SearchCursor)}
     38  *   <li>A work directory cursor {@link #setCorpDirectoryCursor(SearchCursor)}
     39  *   <li>A list of action to be performed on a number {@link #setSearchActions(List)}
     40  * </ul>
     41  *
     42  * <p>The key purpose of this class is to compose three aforementioned cursors together to function
     43  * as one cursor. The key methods needed to utilize this class as a cursor are:
     44  *
     45  * <ul>
     46  *   <li>{@link #getCursor(int)}
     47  *   <li>{@link #getCount()}
     48  *   <li>{@link #getRowType(int)}
     49  * </ul>
     50  */
     51 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     52 public final class SearchCursorManager {
     53 
     54   /** IntDef for the different types of rows that can be shown when searching. */
     55   @Retention(RetentionPolicy.SOURCE)
     56   @IntDef({
     57     SearchCursorManager.RowType.INVALID,
     58     SearchCursorManager.RowType.CONTACT_HEADER,
     59     SearchCursorManager.RowType.CONTACT_ROW,
     60     SearchCursorManager.RowType.NEARBY_PLACES_HEADER,
     61     SearchCursorManager.RowType.NEARBY_PLACES_ROW,
     62     SearchCursorManager.RowType.DIRECTORY_HEADER,
     63     SearchCursorManager.RowType.DIRECTORY_ROW,
     64     SearchCursorManager.RowType.SEARCH_ACTION,
     65     SearchCursorManager.RowType.LOCATION_REQUEST
     66   })
     67   @interface RowType {
     68     int INVALID = 0;
     69     // TODO(calderwoodra) add suggestions header and list
     70     /** Header to mark the start of contact rows. */
     71     int CONTACT_HEADER = 1;
     72     /** A row containing contact information for contacts stored locally on device. */
     73     int CONTACT_ROW = 2;
     74     /** Header to mark the end of contact rows and start of nearby places rows. */
     75     int NEARBY_PLACES_HEADER = 3;
     76     /** A row containing nearby places information/search results. */
     77     int NEARBY_PLACES_ROW = 4;
     78     /** Header to mark the end of the previous row set and start of directory rows. */
     79     int DIRECTORY_HEADER = 5;
     80     /** A row containing contact information for contacts stored externally in corp directories. */
     81     int DIRECTORY_ROW = 6;
     82     /** A row containing a search action */
     83     int SEARCH_ACTION = 7;
     84     /** A row which requests location permission */
     85     int LOCATION_REQUEST = 8;
     86   }
     87 
     88   private static final LocationPermissionCursor LOCATION_PERMISSION_CURSOR =
     89       new LocationPermissionCursor(new String[0]);
     90 
     91   private SearchCursor contactsCursor = null;
     92   private SearchCursor nearbyPlacesCursor = null;
     93   private SearchCursor corpDirectoryCursor = null;
     94   private List<Integer> searchActions = new ArrayList<>();
     95 
     96   private boolean showLocationPermissionRequest;
     97 
     98   /** Returns true if the cursor changed. */
     99   boolean setContactsCursor(@Nullable SearchCursor cursor) {
    100     if (cursor == contactsCursor) {
    101       return false;
    102     }
    103 
    104     if (cursor != null) {
    105       contactsCursor = cursor;
    106     } else {
    107       contactsCursor = null;
    108     }
    109     return true;
    110   }
    111 
    112   /** Returns true if the cursor changed. */
    113   boolean setNearbyPlacesCursor(@Nullable SearchCursor cursor) {
    114     if (cursor == nearbyPlacesCursor) {
    115       return false;
    116     }
    117 
    118     if (cursor != null) {
    119       nearbyPlacesCursor = cursor;
    120     } else {
    121       nearbyPlacesCursor = null;
    122     }
    123     return true;
    124   }
    125 
    126   /** Returns true if the value changed. */
    127   boolean showLocationPermissionRequest(boolean enabled) {
    128     if (showLocationPermissionRequest == enabled) {
    129       return false;
    130     }
    131     showLocationPermissionRequest = enabled;
    132     return true;
    133   }
    134 
    135   /** Returns true if a cursor changed. */
    136   boolean setCorpDirectoryCursor(@Nullable SearchCursor cursor) {
    137     if (cursor == corpDirectoryCursor) {
    138       return false;
    139     }
    140 
    141     if (cursor != null) {
    142       corpDirectoryCursor = cursor;
    143     } else {
    144       corpDirectoryCursor = null;
    145     }
    146     return true;
    147   }
    148 
    149   boolean setQuery(String query) {
    150     boolean updated = false;
    151     if (contactsCursor != null) {
    152       updated = contactsCursor.updateQuery(query);
    153     }
    154 
    155     if (nearbyPlacesCursor != null) {
    156       updated |= nearbyPlacesCursor.updateQuery(query);
    157     }
    158 
    159     if (corpDirectoryCursor != null) {
    160       updated |= corpDirectoryCursor.updateQuery(query);
    161     }
    162     return updated;
    163   }
    164 
    165   /** Sets search actions, returning true if different from existing actions. */
    166   boolean setSearchActions(List<Integer> searchActions) {
    167     if (!this.searchActions.equals(searchActions)) {
    168       this.searchActions = searchActions;
    169       return true;
    170     }
    171     return false;
    172   }
    173 
    174   /** Returns {@link SearchActionViewHolder.Action}. */
    175   int getSearchAction(int position) {
    176     return searchActions.get(position - getCount() + searchActions.size());
    177   }
    178 
    179   /** Returns the sum of counts of all cursors, including headers. */
    180   int getCount() {
    181     int count = 0;
    182     if (contactsCursor != null) {
    183       count += contactsCursor.getCount();
    184     }
    185 
    186     if (showLocationPermissionRequest) {
    187       count++;
    188     } else if (nearbyPlacesCursor != null) {
    189       count += nearbyPlacesCursor.getCount();
    190     }
    191 
    192     if (corpDirectoryCursor != null) {
    193       count += corpDirectoryCursor.getCount();
    194     }
    195 
    196     return count + searchActions.size();
    197   }
    198 
    199   @RowType
    200   int getRowType(int position) {
    201     int cursorCount = getCount();
    202     if (position >= cursorCount) {
    203       throw Assert.createIllegalStateFailException(
    204           String.format("Invalid position: %d, cursor count: %d", position, cursorCount));
    205     } else if (position >= cursorCount - searchActions.size()) {
    206       return RowType.SEARCH_ACTION;
    207     }
    208 
    209     SearchCursor cursor = getCursor(position);
    210     if (cursor == contactsCursor) {
    211       return cursor.isHeader() ? RowType.CONTACT_HEADER : RowType.CONTACT_ROW;
    212     }
    213 
    214     if (cursor == LOCATION_PERMISSION_CURSOR) {
    215       return RowType.LOCATION_REQUEST;
    216     }
    217 
    218     if (cursor == nearbyPlacesCursor) {
    219       return cursor.isHeader() ? RowType.NEARBY_PLACES_HEADER : RowType.NEARBY_PLACES_ROW;
    220     }
    221 
    222     if (cursor == corpDirectoryCursor) {
    223       return cursor.isHeader() ? RowType.DIRECTORY_HEADER : RowType.DIRECTORY_ROW;
    224     }
    225     throw Assert.createIllegalStateFailException("No valid row type.");
    226   }
    227 
    228   /**
    229    * Gets cursor corresponding to position in coalesced list of search cursors.
    230    *
    231    * @param position in coalesced list of search cursors
    232    * @return Cursor moved to position specific to passed in position.
    233    */
    234   SearchCursor getCursor(int position) {
    235     if (showLocationPermissionRequest) {
    236       if (position == 0) {
    237         return LOCATION_PERMISSION_CURSOR;
    238       }
    239       position--;
    240     }
    241 
    242     if (contactsCursor != null) {
    243       int count = contactsCursor.getCount();
    244 
    245       if (position - count < 0) {
    246         contactsCursor.moveToPosition(position);
    247         return contactsCursor;
    248       }
    249       position -= count;
    250     }
    251 
    252     if (!showLocationPermissionRequest && nearbyPlacesCursor != null) {
    253       int count = nearbyPlacesCursor.getCount();
    254 
    255       if (position - count < 0) {
    256         nearbyPlacesCursor.moveToPosition(position);
    257         return nearbyPlacesCursor;
    258       }
    259       position -= count;
    260     }
    261 
    262     if (corpDirectoryCursor != null) {
    263       int count = corpDirectoryCursor.getCount();
    264 
    265       if (position - count < 0) {
    266         corpDirectoryCursor.moveToPosition(position);
    267         return corpDirectoryCursor;
    268       }
    269       position -= count;
    270     }
    271 
    272     throw Assert.createIllegalStateFailException("No valid cursor.");
    273   }
    274 
    275   /** removes all cursors. */
    276   void clear() {
    277     contactsCursor = null;
    278     nearbyPlacesCursor = null;
    279     corpDirectoryCursor = null;
    280   }
    281 
    282   /**
    283    * No-op implementation of {@link android.database.Cursor} and {@link SearchCursor} for
    284    * representing location permission request row elements.
    285    */
    286   private static class LocationPermissionCursor extends MatrixCursor implements SearchCursor {
    287 
    288     LocationPermissionCursor(String[] columnNames) {
    289       super(columnNames);
    290     }
    291 
    292     @Override
    293     public boolean isHeader() {
    294       return false;
    295     }
    296 
    297     @Override
    298     public boolean updateQuery(String query) {
    299       return false;
    300     }
    301 
    302     @Override
    303     public long getDirectoryId() {
    304       return 0;
    305     }
    306   }
    307 }
    308