Home | History | Annotate | Download | only in speeddial
      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.speeddial;
     18 
     19 import android.annotation.SuppressLint;
     20 import android.database.Cursor;
     21 import android.database.MatrixCursor;
     22 import android.database.MergeCursor;
     23 import android.support.annotation.IntDef;
     24 import android.support.annotation.StringRes;
     25 import com.android.dialer.common.Assert;
     26 import java.lang.annotation.Retention;
     27 import java.lang.annotation.RetentionPolicy;
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 
     31 /** Cursor for favorites contacts. */
     32 final class SpeedDialCursor extends MergeCursor {
     33 
     34   /**
     35    * Caps the speed dial list to contain at most 20 contacts, including favorites and suggestions.
     36    * It is only a soft limit though, for the case that there are more than 20 favorite contacts.
     37    */
     38   private static final int SPEED_DIAL_CONTACT_LIST_SOFT_LIMIT = 20;
     39 
     40   private static final String[] HEADER_CURSOR_PROJECTION = {"header"};
     41   private static final int HEADER_COLUMN_POSITION = 0;
     42   private boolean hasFavorites;
     43 
     44   @Retention(RetentionPolicy.SOURCE)
     45   @IntDef({RowType.HEADER, RowType.STARRED, RowType.SUGGESTION})
     46   @interface RowType {
     47     int HEADER = 0;
     48     int STARRED = 1;
     49     int SUGGESTION = 2;
     50   }
     51 
     52   public static SpeedDialCursor newInstance(Cursor strequentCursor) {
     53     if (strequentCursor == null || strequentCursor.getCount() == 0) {
     54       return null;
     55     }
     56     SpeedDialCursor cursor = new SpeedDialCursor(buildCursors(strequentCursor));
     57     strequentCursor.close();
     58     return cursor;
     59   }
     60 
     61   private static Cursor[] buildCursors(Cursor strequentCursor) {
     62     MatrixCursor starred = new MatrixCursor(StrequentContactsCursorLoader.PHONE_PROJECTION);
     63     MatrixCursor suggestions = new MatrixCursor(StrequentContactsCursorLoader.PHONE_PROJECTION);
     64 
     65     strequentCursor.moveToPosition(-1);
     66     while (strequentCursor.moveToNext()) {
     67       if (strequentCursor.getPosition() != 0) {
     68         long contactId = strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID);
     69         int position = strequentCursor.getPosition();
     70         boolean duplicate = false;
     71         // Iterate backwards through the cursor to check that this isn't a duplicate contact
     72         // TODO(calderwoodra): improve this algorithm (currently O(n^2)).
     73         while (strequentCursor.moveToPrevious() && !duplicate) {
     74           duplicate |=
     75               strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID) == contactId;
     76         }
     77         strequentCursor.moveToPosition(position);
     78         if (duplicate) {
     79           continue;
     80         }
     81       }
     82 
     83       if (strequentCursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) {
     84         StrequentContactsCursorLoader.addToCursor(starred, strequentCursor);
     85       } else if (starred.getCount() + suggestions.getCount() < SPEED_DIAL_CONTACT_LIST_SOFT_LIMIT) {
     86         // Since all starred contacts come before each non-starred contact, it's safe to assume that
     87         // this list will never exceed the soft limit unless there are more starred contacts than
     88         // the limit permits.
     89         StrequentContactsCursorLoader.addToCursor(suggestions, strequentCursor);
     90       }
     91     }
     92 
     93     List<Cursor> cursorList = new ArrayList<>();
     94     if (starred.getCount() > 0) {
     95       cursorList.add(createHeaderCursor(R.string.favorites_header));
     96       cursorList.add(starred);
     97     }
     98     if (suggestions.getCount() > 0) {
     99       cursorList.add(createHeaderCursor(R.string.suggestions_header));
    100       cursorList.add(suggestions);
    101     }
    102     return cursorList.toArray(new Cursor[cursorList.size()]);
    103   }
    104 
    105   private static Cursor createHeaderCursor(@StringRes int header) {
    106     MatrixCursor cursor = new MatrixCursor(HEADER_CURSOR_PROJECTION);
    107     cursor.newRow().add(HEADER_CURSOR_PROJECTION[HEADER_COLUMN_POSITION], header);
    108     return cursor;
    109   }
    110 
    111   @RowType
    112   int getRowType(int position) {
    113     moveToPosition(position);
    114     if (getColumnCount() == 1) {
    115       return RowType.HEADER;
    116     } else if (getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) {
    117       return RowType.STARRED;
    118     } else {
    119       return RowType.SUGGESTION;
    120     }
    121   }
    122 
    123   @SuppressLint("DefaultLocale")
    124   @StringRes
    125   int getHeader() {
    126     if (getRowType(getPosition()) != RowType.HEADER) {
    127       throw Assert.createIllegalStateFailException(
    128           String.format("Current position (%d) is not a header.", getPosition()));
    129     }
    130     return getInt(HEADER_COLUMN_POSITION);
    131   }
    132 
    133   public boolean hasFavorites() {
    134     return hasFavorites;
    135   }
    136 
    137   private SpeedDialCursor(Cursor[] cursors) {
    138     super(cursors);
    139     for (Cursor cursor : cursors) {
    140       cursor.moveToFirst();
    141       if (cursor.getColumnCount() != 1
    142           && cursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) {
    143         hasFavorites = true;
    144         break;
    145       }
    146     }
    147   }
    148 }
    149