Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7800e444 authored by calderwoodra's avatar calderwoodra Committed by android-build-merger
Browse files

Merge changes If9478ce2,I41d4910b am: 9acd1054 am: 1bd73670

am: d8e8553d

Change-Id: I849b7a045372b6b5cf467f1e7d109a08e04f5fa4
parents 2ce82fed d8e8553d
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -491,6 +491,11 @@ public class DialtactsActivity extends TransactionSafeActivity
    mP13nLogger = P13nLogging.get(getApplicationContext());
    mP13nRanker = P13nRanking.get(getApplicationContext());
    Trace.endSection();

    // Update the new search fragment to the correct position and the ActionBar's visibility.
    if (ConfigProviderBindings.get(this).getBoolean("enable_new_search_fragment", false)) {
      updateSearchFragmentPosition();
    }
  }

  @NonNull
@@ -1550,6 +1555,10 @@ public class DialtactsActivity extends TransactionSafeActivity

  @Override
  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    // FAB does not move with the new favorites UI
    if (newFavoritesIsEnabled()) {
      return;
    }
    int tabIndex = mListsFragment.getCurrentTabIndex();

    // Scroll the button from center to end when moving from the Speed Dial to Call History tab.
@@ -1610,7 +1619,8 @@ public class DialtactsActivity extends TransactionSafeActivity

  @VisibleForTesting
  public int getFabAlignment() {
    if (!mIsLandscape
    if (!newFavoritesIsEnabled()
        && !mIsLandscape
        && !isInSearchUi()
        && mListsFragment.getCurrentTabIndex() == DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL) {
      return FloatingActionButtonController.ALIGN_MIDDLE;
@@ -1741,4 +1751,8 @@ public class DialtactsActivity extends TransactionSafeActivity
  static void setVoiceSearchEnabledForTest(Optional<Boolean> enabled) {
    sVoiceSearchEnabledForTest = enabled;
  }

  private boolean newFavoritesIsEnabled() {
    return ConfigProviderBindings.get(this).getBoolean("enable_new_favorites_tab", false);
  }
}
+121 −0
Original line number Diff line number Diff line
/*
 * 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.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.FrameLayout;
import android.widget.QuickContactBadge;
import android.widget.TextView;
import com.android.dialer.common.Assert;
import com.android.dialer.contactphoto.ContactPhotoManager;
import com.android.dialer.lettertile.LetterTileDrawable;

/** ViewHolder for starred/favorite contacts in {@link SpeedDialFragment}. */
public class FavoritesViewHolder extends RecyclerView.ViewHolder
    implements OnClickListener, OnLongClickListener {

  private final FavoriteContactsListener listener;

  private final QuickContactBadge photoView;
  private final TextView nameView;
  private final TextView phoneType;
  private final FrameLayout videoCallIcon;

  private boolean isVideoCall;
  private String number;

  public FavoritesViewHolder(View view, FavoriteContactsListener listener) {
    super(view);
    photoView = view.findViewById(R.id.avatar);
    nameView = view.findViewById(R.id.name);
    phoneType = view.findViewById(R.id.phone_type);
    videoCallIcon = view.findViewById(R.id.video_call_container);
    view.setOnClickListener(this);
    view.setOnLongClickListener(this);
    photoView.setClickable(false);
    this.listener = listener;
  }

  public void bind(Context context, Cursor cursor) {
    Assert.checkArgument(cursor.getInt(StrequentContactsCursorLoader.PHONE_STARRED) == 1);
    isVideoCall = false; // TODO(calderwoodra): get from disambig data
    number = cursor.getString(StrequentContactsCursorLoader.PHONE_NUMBER);

    String name = cursor.getString(StrequentContactsCursorLoader.PHONE_DISPLAY_NAME);
    long contactId = cursor.getLong(StrequentContactsCursorLoader.PHONE_ID);
    String lookupKey = cursor.getString(StrequentContactsCursorLoader.PHONE_LOOKUP_KEY);
    Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);

    String photoUri = cursor.getString(StrequentContactsCursorLoader.PHONE_PHOTO_URI);
    ContactPhotoManager.getInstance(context)
        .loadDialerThumbnailOrPhoto(
            photoView,
            contactUri,
            cursor.getLong(StrequentContactsCursorLoader.PHONE_PHOTO_ID),
            photoUri == null ? null : Uri.parse(photoUri),
            name,
            LetterTileDrawable.TYPE_DEFAULT);
    nameView.setText(name);
    phoneType.setText(getLabel(context.getResources(), cursor));
    videoCallIcon.setVisibility(isVideoCall ? View.VISIBLE : View.GONE);
  }

  // TODO(calderwoodra): handle CNAP and cequint types.
  // TODO(calderwoodra): unify this into a utility method with CallLogAdapter#getNumberType
  private static String getLabel(Resources resources, Cursor cursor) {
    int numberType = cursor.getInt(StrequentContactsCursorLoader.PHONE_TYPE);
    String numberLabel = cursor.getString(StrequentContactsCursorLoader.PHONE_LABEL);

    // Returns empty label instead of "custom" if the custom label is empty.
    if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) {
      return "";
    }
    return (String) Phone.getTypeLabel(resources, numberType, numberLabel);
  }

  @Override
  public void onClick(View v) {
    listener.onClick(number, isVideoCall);
  }

  @Override
  public boolean onLongClick(View v) {
    // TODO(calderwoodra): implement drag and drop logic
    listener.onLongClick(number);
    return true;
  }

  /** Listener/callback for {@link FavoritesViewHolder} actions. */
  public interface FavoriteContactsListener {

    /** Called when the user clicks on a favorite contact. */
    void onClick(String number, boolean isVideoCall);

    /** Called when the user long clicks on a favorite contact. */
    void onLongClick(String number);
  }
}
+60 −0
Original line number Diff line number Diff line
/*
 * 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.support.annotation.StringRes;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/** ViewHolder for headers in {@link SpeedDialFragment}. */
public class HeaderViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

  private final SpeedDialHeaderListener listener;
  private final TextView headerText;
  private final Button addButton;

  public HeaderViewHolder(View view, SpeedDialHeaderListener listener) {
    super(view);
    this.listener = listener;
    headerText = view.findViewById(R.id.speed_dial_header_text);
    addButton = view.findViewById(R.id.speed_dial_add_button);
    addButton.setOnClickListener(this);
  }

  public void setHeaderText(@StringRes int header) {
    headerText.setText(header);
  }

  public void showAddButton(boolean show) {
    addButton.setVisibility(show ? View.VISIBLE : View.GONE);
  }

  @Override
  public void onClick(View v) {
    listener.onAddFavoriteClicked();
  }

  /** Listener/Callback for {@link HeaderViewHolder} parents. */
  public interface SpeedDialHeaderListener {

    /** Called when the user wants to add a contact to their favorites. */
    void onAddFavoriteClicked();
  }
}
+141 −0
Original line number Diff line number Diff line
/*
 * 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.content.Context;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.dialer.common.Assert;
import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener;
import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener;
import com.android.dialer.speeddial.SpeedDialCursor.RowType;
import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener;

/**
 * RecyclerView adapter for {@link SpeedDialFragment}.
 *
 * <p>Displays a list in the following order:
 *
 * <ol>
 *   <li>Favorite contacts header (with add button)
 *   <li>Favorite contacts
 *   <li>Suggested contacts header
 *   <li>Suggested contacts
 * </ol>
 */
final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

  private final Context context;
  private final FavoriteContactsListener favoritesListener;
  private final SuggestedContactsListener suggestedListener;
  private final SpeedDialHeaderListener headerListener;

  private SpeedDialCursor cursor;

  public SpeedDialAdapter(
      Context context,
      FavoriteContactsListener favoritesListener,
      SuggestedContactsListener suggestedListener,
      SpeedDialHeaderListener headerListener) {
    this.context = context;
    this.favoritesListener = favoritesListener;
    this.suggestedListener = suggestedListener;
    this.headerListener = headerListener;
  }

  @Override
  public int getItemViewType(int position) {
    return cursor.getRowType(position);
  }

  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(context);
    if (viewType == RowType.STARRED) {
      return new FavoritesViewHolder(
          inflater.inflate(R.layout.favorite_item_layout, parent, false), favoritesListener);
    } else if (viewType == RowType.SUGGESTION) {
      return new SuggestionViewHolder(
          inflater.inflate(R.layout.suggestion_row_layout, parent, false), suggestedListener);
    } else if (viewType == RowType.HEADER) {
      return new HeaderViewHolder(
          inflater.inflate(R.layout.speed_dial_header_layout, parent, false), headerListener);
    } else {
      throw Assert.createIllegalStateFailException("Invalid viewType: " + viewType);
    }
  }

  @Override
  public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    cursor.moveToPosition(position);
    switch (cursor.getRowType(position)) {
      case RowType.HEADER:
        ((HeaderViewHolder) holder).setHeaderText(cursor.getHeader());
        ((HeaderViewHolder) holder).showAddButton(cursor.hasFavorites() && position == 0);
        break;
      case RowType.STARRED:
        ((FavoritesViewHolder) holder).bind(context, cursor);
        break;
      case RowType.SUGGESTION:
        ((SuggestionViewHolder) holder).bind(context, cursor);
        break;
      default:
        throw Assert.createIllegalStateFailException("Invalid view holder: " + holder);
    }
  }

  @Override
  public int getItemCount() {
    return cursor == null || cursor.isClosed() ? 0 : cursor.getCount();
  }

  public void setCursor(SpeedDialCursor cursor) {
    this.cursor = cursor;
    notifyDataSetChanged();
  }

  LayoutManager getLayoutManager(Context context) {
    GridLayoutManager layoutManager = new GridLayoutManager(context, 3 /* spanCount */);
    layoutManager.setSpanSizeLookup(
        new SpanSizeLookup() {
          @Override
          public int getSpanSize(int position) {
            return SpeedDialAdapter.this.getSpanSize(position);
          }
        });
    return layoutManager;
  }

  @VisibleForTesting
  int getSpanSize(int position) {
    switch (cursor.getRowType(position)) {
      case RowType.SUGGESTION:
      case RowType.HEADER:
        return 3; // span the whole screen
      case RowType.STARRED:
        return 1; // span 1/3 of the screen
      default:
        throw Assert.createIllegalStateFailException(
            "Invalid row type: " + cursor.getRowType(position));
    }
  }
}
+152 −0
Original line number Diff line number Diff line
/*
 * 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.getInt(StrequentContactsCursorLoader.PHONE_IS_SUPER_PRIMARY) != 0) {
        continue;
      }

      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;
      }
    }
  }
}
Loading