| /* |
| * 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.speeddial; |
| |
| import android.annotation.SuppressLint; |
| import android.database.Cursor; |
| import android.database.MatrixCursor; |
| import android.database.MergeCursor; |
| import android.support.annotation.IntDef; |
| import android.support.annotation.StringRes; |
| import com.android.dialer.common.Assert; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** Cursor for favorites contacts. */ |
| final class SpeedDialCursor extends MergeCursor { |
| |
| /** |
| * Caps the speed dial list to contain at most 20 contacts, including favorites and suggestions. |
| * It is only a soft limit though, for the case that there are more than 20 favorite contacts. |
| */ |
| private static final int SPEED_DIAL_CONTACT_LIST_SOFT_LIMIT = 20; |
| |
| private static final String[] HEADER_CURSOR_PROJECTION = {"header"}; |
| private static final int HEADER_COLUMN_POSITION = 0; |
| private boolean hasFavorites; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({RowType.HEADER, RowType.STARRED, RowType.SUGGESTION}) |
| @interface RowType { |
| int HEADER = 0; |
| int STARRED = 1; |
| int SUGGESTION = 2; |
| } |
| |
| public static SpeedDialCursor newInstance(Cursor strequentCursor) { |
| if (strequentCursor == null || strequentCursor.getCount() == 0) { |
| return null; |
| } |
| SpeedDialCursor cursor = new SpeedDialCursor(buildCursors(strequentCursor)); |
| strequentCursor.close(); |
| return cursor; |
| } |
| |
| private static Cursor[] buildCursors(Cursor strequentCursor) { |
| MatrixCursor starred = new MatrixCursor(StrequentContactsCursorLoader.PHONE_PROJECTION); |
| MatrixCursor suggestions = new MatrixCursor(StrequentContactsCursorLoader.PHONE_PROJECTION); |
| |
| strequentCursor.moveToPosition(-1); |
| while (strequentCursor.moveToNext()) { |
| if (strequentCursor.getPosition() != 0) { |
| long contactId = strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID); |
| int position = strequentCursor.getPosition(); |
| boolean duplicate = false; |
| // Iterate backwards through the cursor to check that this isn't a duplicate contact |
| // TODO(calderwoodra): improve this algorithm (currently O(n^2)). |
| while (strequentCursor.moveToPrevious() && !duplicate) { |
| duplicate |= |
| strequentCursor.getLong(StrequentContactsCursorLoader.PHONE_CONTACT_ID) == contactId; |
| } |
| strequentCursor.moveToPosition(position); |
| if (duplicate) { |
| continue; |
| } |
| } |
| |
| if (strequentCursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) { |
| StrequentContactsCursorLoader.addToCursor(starred, strequentCursor); |
| } else if (starred.getCount() + suggestions.getCount() < SPEED_DIAL_CONTACT_LIST_SOFT_LIMIT) { |
| // Since all starred contacts come before each non-starred contact, it's safe to assume that |
| // this list will never exceed the soft limit unless there are more starred contacts than |
| // the limit permits. |
| StrequentContactsCursorLoader.addToCursor(suggestions, strequentCursor); |
| } |
| } |
| |
| List<Cursor> cursorList = new ArrayList<>(); |
| if (starred.getCount() > 0) { |
| cursorList.add(createHeaderCursor(R.string.favorites_header)); |
| cursorList.add(starred); |
| } |
| if (suggestions.getCount() > 0) { |
| cursorList.add(createHeaderCursor(R.string.suggestions_header)); |
| cursorList.add(suggestions); |
| } |
| return cursorList.toArray(new Cursor[cursorList.size()]); |
| } |
| |
| private static Cursor createHeaderCursor(@StringRes int header) { |
| MatrixCursor cursor = new MatrixCursor(HEADER_CURSOR_PROJECTION); |
| cursor.newRow().add(HEADER_CURSOR_PROJECTION[HEADER_COLUMN_POSITION], header); |
| return cursor; |
| } |
| |
| @RowType |
| int getRowType(int position) { |
| moveToPosition(position); |
| if (getColumnCount() == 1) { |
| return RowType.HEADER; |
| } else if (getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) { |
| return RowType.STARRED; |
| } else { |
| return RowType.SUGGESTION; |
| } |
| } |
| |
| @SuppressLint("DefaultLocale") |
| @StringRes |
| int getHeader() { |
| if (getRowType(getPosition()) != RowType.HEADER) { |
| throw Assert.createIllegalStateFailException( |
| String.format("Current position (%d) is not a header.", getPosition())); |
| } |
| return getInt(HEADER_COLUMN_POSITION); |
| } |
| |
| public boolean hasFavorites() { |
| return hasFavorites; |
| } |
| |
| private SpeedDialCursor(Cursor[] cursors) { |
| super(cursors); |
| for (Cursor cursor : cursors) { |
| cursor.moveToFirst(); |
| if (cursor.getColumnCount() != 1 |
| && cursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1) { |
| hasFavorites = true; |
| break; |
| } |
| } |
| } |
| } |