summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcalderwoodra <calderwoodra@google.com>2017-11-30 15:21:47 -0800
committerCopybara-Service <copybara-piper@google.com>2017-11-30 17:17:09 -0800
commit9873647e903574ee4ef62b2f13633650793c346e (patch)
tree8446501d66585a2910fdb1677a8e4e02ee400662
parent71eae26ab4f9a8eaab16271edf63476762e28f75 (diff)
Implemented new favorites list UI.
Bug: 36841782 Test: implemented PiperOrigin-RevId: 177516412 Change-Id: If9478ce22c10fd17e352d5fdcc2c0bef5e14a6d8
-rw-r--r--java/com/android/dialer/app/DialtactsActivity.java11
-rw-r--r--java/com/android/dialer/speeddial/FavoritesViewHolder.java121
-rw-r--r--java/com/android/dialer/speeddial/HeaderViewHolder.java60
-rw-r--r--java/com/android/dialer/speeddial/SpeedDialAdapter.java141
-rw-r--r--java/com/android/dialer/speeddial/SpeedDialCursor.java152
-rw-r--r--java/com/android/dialer/speeddial/SpeedDialFragment.java119
-rw-r--r--java/com/android/dialer/speeddial/SquareImageView.java36
-rw-r--r--java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java104
-rw-r--r--java/com/android/dialer/speeddial/SuggestionViewHolder.java117
-rw-r--r--java/com/android/dialer/speeddial/res/drawable/favorite_icon.xml23
-rw-r--r--java/com/android/dialer/speeddial/res/layout/favorite_item_layout.xml70
-rw-r--r--java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml12
-rw-r--r--java/com/android/dialer/speeddial/res/layout/speed_dial_header_layout.xml41
-rw-r--r--java/com/android/dialer/speeddial/res/layout/suggestion_row_layout.xml61
-rw-r--r--java/com/android/dialer/speeddial/res/values/strings.xml8
-rw-r--r--java/com/android/dialer/theme/res/values/dimens.xml9
16 files changed, 1079 insertions, 6 deletions
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 9144fc939..2d82b6f33 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -1555,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.
@@ -1615,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;
@@ -1746,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);
+ }
}
diff --git a/java/com/android/dialer/speeddial/FavoritesViewHolder.java b/java/com/android/dialer/speeddial/FavoritesViewHolder.java
new file mode 100644
index 000000000..0cde71693
--- /dev/null
+++ b/java/com/android/dialer/speeddial/FavoritesViewHolder.java
@@ -0,0 +1,121 @@
+/*
+ * 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);
+ }
+}
diff --git a/java/com/android/dialer/speeddial/HeaderViewHolder.java b/java/com/android/dialer/speeddial/HeaderViewHolder.java
new file mode 100644
index 000000000..58120eed7
--- /dev/null
+++ b/java/com/android/dialer/speeddial/HeaderViewHolder.java
@@ -0,0 +1,60 @@
+/*
+ * 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();
+ }
+}
diff --git a/java/com/android/dialer/speeddial/SpeedDialAdapter.java b/java/com/android/dialer/speeddial/SpeedDialAdapter.java
new file mode 100644
index 000000000..5f7b68e5c
--- /dev/null
+++ b/java/com/android/dialer/speeddial/SpeedDialAdapter.java
@@ -0,0 +1,141 @@
+/*
+ * 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));
+ }
+ }
+}
diff --git a/java/com/android/dialer/speeddial/SpeedDialCursor.java b/java/com/android/dialer/speeddial/SpeedDialCursor.java
new file mode 100644
index 000000000..552fab175
--- /dev/null
+++ b/java/com/android/dialer/speeddial/SpeedDialCursor.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+ }
+ }
+ }
+}
diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java
index c087439eb..65e542cd4 100644
--- a/java/com/android/dialer/speeddial/SpeedDialFragment.java
+++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java
@@ -17,15 +17,37 @@
package com.android.dialer.speeddial;
import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Loader;
+import android.database.Cursor;
import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.common.Assert;
+import com.android.dialer.precall.PreCall;
+import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener;
+import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener;
+import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener;
/** Favorites fragment. Contents TBD. TODO(calderwoodra) */
public class SpeedDialFragment extends Fragment {
+ private static final int STREQUENT_CONTACTS_LOADER_ID = 1;
+
+ private final SpeedDialHeaderListener headerListener = new SpeedDialFragmentHeaderListener();
+ private final FavoriteContactsListener favoritesListener = new SpeedDialFavoritesListener();
+ private final SuggestedContactsListener suggestedListener = new SpeedDialSuggestedListener();
+ private final SpeedDialFragmentLoaderCallback loaderCallback =
+ new SpeedDialFragmentLoaderCallback();
+
+ private SpeedDialAdapter adapter;
+
public static SpeedDialFragment newInstance() {
return new SpeedDialFragment();
}
@@ -34,11 +56,106 @@ public class SpeedDialFragment extends Fragment {
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_speed_dial, container, false);
+ View view = inflater.inflate(R.layout.fragment_speed_dial, container, false);
+ RecyclerView recyclerView = view.findViewById(R.id.speed_dial_recycler_view);
+
+ adapter =
+ new SpeedDialAdapter(getContext(), favoritesListener, suggestedListener, headerListener);
+ recyclerView.setLayoutManager(adapter.getLayoutManager(getContext()));
+ recyclerView.setAdapter(adapter);
+ getLoaderManager().initLoader(STREQUENT_CONTACTS_LOADER_ID, null /* args */, loaderCallback);
+ return view;
}
public boolean hasFrequents() {
// TODO(calderwoodra)
return false;
}
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ loaderCallback.unregisterContentObserver();
+ }
+
+ private static class SpeedDialFragmentHeaderListener implements SpeedDialHeaderListener {
+
+ @Override
+ public void onAddFavoriteClicked() {
+ // TODO(calderwoodra): implement add favorite screen
+ }
+ }
+
+ private class SpeedDialFavoritesListener implements FavoriteContactsListener {
+
+ @Override
+ public void onClick(String number, boolean isVideoCall) {
+ // TODO(calderwoodra): add logic for duo video calls
+ PreCall.start(
+ getContext(),
+ new CallIntentBuilder(number, CallInitiationType.Type.SPEED_DIAL)
+ .setIsVideoCall(isVideoCall));
+ }
+
+ @Override
+ public void onLongClick(String number) {
+ // TODO(calderwoodra): show favorite contact floating context menu
+ }
+ }
+
+ private class SpeedDialSuggestedListener implements SuggestedContactsListener {
+
+ @Override
+ public void onOverFlowMenuClicked(String number) {
+ // TODO(calderwoodra) show overflow menu for suggested contacts
+ }
+
+ @Override
+ public void onRowClicked(String number) {
+ PreCall.start(
+ getContext(), new CallIntentBuilder(number, CallInitiationType.Type.SPEED_DIAL));
+ }
+ }
+
+ /**
+ * Loader callback that registers a content observer. {@link #unregisterContentObserver()} needs
+ * to be called during tear down of the fragment.
+ */
+ private class SpeedDialFragmentLoaderCallback implements LoaderCallbacks<Cursor> {
+
+ private StrequentContactsCursorLoader cursorLoader;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ if (id == STREQUENT_CONTACTS_LOADER_ID) {
+ return new StrequentContactsCursorLoader(getContext());
+ }
+ throw Assert.createIllegalStateFailException("Invalid loader id: " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ cursorLoader = (StrequentContactsCursorLoader) loader;
+ // Since the original cursor we queried against was modified and closed, we need to register a
+ // new content observer in order to get updates on changes to our contacts.
+ getContext()
+ .getContentResolver()
+ .registerContentObserver(
+ Contacts.CONTENT_STREQUENT_URI,
+ true /* notifyForDescendants*/,
+ cursorLoader.getContentObserver());
+ adapter.setCursor((SpeedDialCursor) data);
+ }
+
+ public void unregisterContentObserver() {
+ getContext()
+ .getContentResolver()
+ .unregisterContentObserver(cursorLoader.getContentObserver());
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ adapter.setCursor(null);
+ }
+ }
}
diff --git a/java/com/android/dialer/speeddial/SquareImageView.java b/java/com/android/dialer/speeddial/SquareImageView.java
new file mode 100644
index 000000000..a12f4d426
--- /dev/null
+++ b/java/com/android/dialer/speeddial/SquareImageView.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Nullable;
+import android.util.AttributeSet;
+import android.widget.QuickContactBadge;
+
+/** A square {@link android.widget.ImageView} constrained on width. */
+public class SquareImageView extends QuickContactBadge {
+
+ public SquareImageView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setClickable(false);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+}
diff --git a/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java b/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java
new file mode 100644
index 000000000..f5f0045e0
--- /dev/null
+++ b/java/com/android/dialer/speeddial/StrequentContactsCursorLoader.java
@@ -0,0 +1,104 @@
+/*
+ * 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.CursorLoader;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+
+/** Cursor Loader for strequent contacts. */
+final class StrequentContactsCursorLoader extends CursorLoader {
+
+ static final int PHONE_ID = 0;
+ static final int PHONE_DISPLAY_NAME = 1;
+ static final int PHONE_STARRED = 2;
+ static final int PHONE_PHOTO_URI = 3;
+ static final int PHONE_LOOKUP_KEY = 4;
+ static final int PHONE_PHOTO_ID = 5;
+ static final int PHONE_NUMBER = 6;
+ static final int PHONE_TYPE = 7;
+ static final int PHONE_LABEL = 8;
+ static final int PHONE_IS_SUPER_PRIMARY = 9;
+ static final int PHONE_PINNED = 10;
+ static final int PHONE_CONTACT_ID = 11;
+
+ static final String[] PHONE_PROJECTION =
+ new String[] {
+ Phone._ID, // 0
+ Phone.DISPLAY_NAME, // 1
+ Phone.STARRED, // 2
+ Phone.PHOTO_URI, // 3
+ Phone.LOOKUP_KEY, // 4
+ Phone.PHOTO_ID, // 5
+ Phone.NUMBER, // 6
+ Phone.TYPE, // 7
+ Phone.LABEL, // 8
+ Phone.IS_SUPER_PRIMARY, // 9
+ Phone.PINNED, // 10
+ Phone.CONTACT_ID, // 11
+ };
+
+ private final ContentObserver contentObserver = new ForceLoadContentObserver();
+
+ StrequentContactsCursorLoader(Context context) {
+ super(
+ context,
+ buildUri(),
+ PHONE_PROJECTION,
+ null /* selection */,
+ null /* selectionArgs */,
+ null /* sortOrder */);
+ // TODO(calderwoodra): implement alternative display names
+ }
+
+ static void addToCursor(MatrixCursor dest, Cursor source) {
+ dest.newRow()
+ .add(PHONE_PROJECTION[PHONE_ID], source.getLong(PHONE_ID))
+ .add(PHONE_PROJECTION[PHONE_DISPLAY_NAME], source.getString(PHONE_DISPLAY_NAME))
+ .add(PHONE_PROJECTION[PHONE_STARRED], source.getInt(PHONE_STARRED))
+ .add(PHONE_PROJECTION[PHONE_PHOTO_URI], source.getString(PHONE_PHOTO_URI))
+ .add(PHONE_PROJECTION[PHONE_LOOKUP_KEY], source.getString(PHONE_LOOKUP_KEY))
+ .add(PHONE_PROJECTION[PHONE_NUMBER], source.getString(PHONE_NUMBER))
+ .add(PHONE_PROJECTION[PHONE_TYPE], source.getInt(PHONE_TYPE))
+ .add(PHONE_PROJECTION[PHONE_LABEL], source.getString(PHONE_LABEL))
+ .add(PHONE_PROJECTION[PHONE_IS_SUPER_PRIMARY], source.getInt(PHONE_IS_SUPER_PRIMARY))
+ .add(PHONE_PROJECTION[PHONE_PINNED], source.getInt(PHONE_PINNED))
+ .add(PHONE_PROJECTION[PHONE_CONTACT_ID], source.getLong(PHONE_CONTACT_ID));
+ }
+
+ private static Uri buildUri() {
+ return Contacts.CONTENT_STREQUENT_URI
+ .buildUpon()
+ .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true")
+ .build();
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ return SpeedDialCursor.newInstance(super.loadInBackground());
+ }
+
+ ContentObserver getContentObserver() {
+ return contentObserver;
+ }
+}
diff --git a/java/com/android/dialer/speeddial/SuggestionViewHolder.java b/java/com/android/dialer/speeddial/SuggestionViewHolder.java
new file mode 100644
index 000000000..70df30706
--- /dev/null
+++ b/java/com/android/dialer/speeddial/SuggestionViewHolder.java
@@ -0,0 +1,117 @@
+/*
+ * 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.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.location.GeoUtil;
+
+/** ViewHolder for displaying suggested contacts in {@link SpeedDialFragment}. */
+public class SuggestionViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+
+ private final SuggestedContactsListener listener;
+
+ private final QuickContactBadge photoView;
+ private final TextView nameOrNumberView;
+ private final TextView numberView;
+
+ private String number;
+
+ SuggestionViewHolder(View view, SuggestedContactsListener listener) {
+ super(view);
+ photoView = view.findViewById(R.id.avatar);
+ nameOrNumberView = view.findViewById(R.id.name);
+ numberView = view.findViewById(R.id.number);
+ itemView.setOnClickListener(this);
+ view.findViewById(R.id.overflow).setOnClickListener(this);
+ this.listener = listener;
+ }
+
+ public void bind(Context context, Cursor cursor) {
+ number = cursor.getString(StrequentContactsCursorLoader.PHONE_NUMBER);
+ number = PhoneNumberUtils.formatNumber(number, GeoUtil.getCurrentCountryIso(context));
+
+ String name = cursor.getString(StrequentContactsCursorLoader.PHONE_DISPLAY_NAME);
+ String label = getLabel(context.getResources(), cursor);
+ String secondaryInfo =
+ TextUtils.isEmpty(label)
+ ? number
+ : context.getString(
+ com.android.contacts.common.R.string.call_subject_type_and_number, label, number);
+
+ nameOrNumberView.setText(name);
+ numberView.setText(secondaryInfo);
+
+ 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);
+ }
+
+ // 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) {
+ if (v.getId() == R.id.overflow) {
+ listener.onOverFlowMenuClicked(number);
+ } else {
+ listener.onRowClicked(number);
+ }
+ }
+
+ /** Listener/Callback for {@link SuggestionViewHolder} parents. */
+ public interface SuggestedContactsListener {
+
+ void onOverFlowMenuClicked(String number);
+
+ /** Called when a suggested contact is clicked. */
+ void onRowClicked(String number);
+ }
+}
diff --git a/java/com/android/dialer/speeddial/res/drawable/favorite_icon.xml b/java/com/android/dialer/speeddial/res/drawable/favorite_icon.xml
new file mode 100644
index 000000000..81b018ff1
--- /dev/null
+++ b/java/com/android/dialer/speeddial/res/drawable/favorite_icon.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <solid android:color="@color/dialer_theme_color"/>
+ <stroke android:color="@color/background_dialer_light" android:width="3dp"/>
+</shape> \ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/layout/favorite_item_layout.xml b/java/com/android/dialer/speeddial/res/layout/favorite_item_layout.xml
new file mode 100644
index 000000000..fb476659c
--- /dev/null
+++ b/java/com/android/dialer/speeddial/res/layout/favorite_item_layout.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="12dp">
+
+ <FrameLayout
+ android:id="@+id/avatar_container"
+ android:layout_width="104dp"
+ android:layout_height="104dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="8dp">
+
+ <com.android.dialer.speeddial.SquareImageView
+ android:id="@+id/avatar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clickable="false"/>
+
+ <FrameLayout
+ android:id="@+id/video_call_container"
+ android:layout_width="36dp"
+ android:layout_height="36dp"
+ android:layout_gravity="bottom|end">
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/favorite_icon"/>
+
+ <ImageView
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:src="@drawable/quantum_ic_videocam_white_24"/>
+ </FrameLayout>
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ style="@style/PrimaryText"/>
+
+ <TextView
+ android:id="@+id/phone_type"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ style="@style/SecondaryText"/>
+</LinearLayout> \ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml b/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml
index 04e230e4d..d432f097b 100644
--- a/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml
+++ b/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml
@@ -14,7 +14,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_width="match_parent"/>
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/speed_dial_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:clipToPadding="false"/>
diff --git a/java/com/android/dialer/speeddial/res/layout/speed_dial_header_layout.xml b/java/com/android/dialer/speeddial/res/layout/speed_dial_header_layout.xml
new file mode 100644
index 000000000..0a84b41e6
--- /dev/null
+++ b/java/com/android/dialer/speeddial/res/layout/speed_dial_header_layout.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/dialer_list_item_min_height">
+
+ <TextView
+ android:id="@+id/speed_dial_header_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:textSize="16sp"
+ style="@style/SecondaryText"/>
+
+ <Button
+ android:id="@+id/speed_dial_add_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:minHeight="@dimen/dialer_touch_target_min_size"
+ android:minWidth="@dimen/dialer_button_min_width"
+ android:text="@string/speed_dial_add_button_text"
+ android:textColor="@color/dialer_theme_color"
+ style="@style/Widget.AppCompat.Button.Borderless"/>
+</RelativeLayout> \ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/layout/suggestion_row_layout.xml b/java/com/android/dialer/speeddial/res/layout/suggestion_row_layout.xml
new file mode 100644
index 000000000..4281700f3
--- /dev/null
+++ b/java/com/android/dialer/speeddial/res/layout/suggestion_row_layout.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="72dp">
+
+ <QuickContactBadge
+ android:id="@+id/avatar"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_centerVertical="true"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="56dp"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/overflow">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/PrimaryText"/>
+
+ <TextView
+ android:id="@+id/number"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/SecondaryText"/>
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/overflow"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true"
+ android:scaleType="center"
+ android:tint="@color/secondary_text_color"
+ android:src="@drawable/quantum_ic_more_vert_white_24"
+ android:background="?android:selectableItemBackgroundBorderless"/>
+</RelativeLayout> \ No newline at end of file
diff --git a/java/com/android/dialer/speeddial/res/values/strings.xml b/java/com/android/dialer/speeddial/res/values/strings.xml
index 5929df8dd..f814ed6d4 100644
--- a/java/com/android/dialer/speeddial/res/values/strings.xml
+++ b/java/com/android/dialer/speeddial/res/values/strings.xml
@@ -15,4 +15,12 @@
~ limitations under the License
-->
<resources>
+ <!-- header for a list of contacts that are the users favorites. -->
+ <string name="favorites_header">Favorites</string>
+
+ <!-- header for a list of contacts that are suggestions for the user to place calls to -->
+ <string name="suggestions_header">Suggestions</string>
+
+ <!-- text for a button that prompts the user to add a contact to their favorites -->
+ <string name="speed_dial_add_button_text">Add</string>
</resources> \ No newline at end of file
diff --git a/java/com/android/dialer/theme/res/values/dimens.xml b/java/com/android/dialer/theme/res/values/dimens.xml
index 972cb535c..2b5243ebd 100644
--- a/java/com/android/dialer/theme/res/values/dimens.xml
+++ b/java/com/android/dialer/theme/res/values/dimens.xml
@@ -41,4 +41,13 @@
<item name="alpha_enabled" format="float" type="dimen">1.0</item>
<item name="alpha_hiden" format="float" type="dimen">0.54</item>
+
+ <!-- Minimum height required for all row elements in Dialer lists. -->
+ <dimen name="dialer_list_item_min_height">56dp</dimen>
+
+ <!-- Minimum a11y height and width required for all touch targets. -->
+ <dimen name="dialer_touch_target_min_size">48dp</dimen>
+
+ <!-- Minimum width for material compliant buttons. -->
+ <dimen name="dialer_button_min_width">72dp</dimen>
</resources>