summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/searchfragment
diff options
context:
space:
mode:
authorcalderwoodra <calderwoodra@google.com>2017-09-01 23:28:21 -0700
committerEric Erfanian <erfanian@google.com>2017-09-07 15:50:17 +0000
commitaf3cc176cdc652a05645cab9ad213696970c6e4d (patch)
tree2e633f463df6dbddf97914a6073c8fa39a706241 /java/com/android/dialer/searchfragment
parent34f1886107825b4495741a034f15e1f1edacf95c (diff)
Added search actions to the end of the dialpad search results.
Users can now create new contacts, add to existing contacts, send sms and make ViLTE calls from dialpad search results. screenshot: http://screen/7iR038nUvmh from the bugbash: 11. Missing “Create new contact” “Add to a contact” “Send SMS” from search results with a phone number search Bug: 64902476 Test: many PiperOrigin-RevId: 167362073 Change-Id: I2f94d863035c119ec526e02e088992c618a858a9
Diffstat (limited to 'java/com/android/dialer/searchfragment')
-rw-r--r--java/com/android/dialer/searchfragment/list/NewSearchFragment.java40
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchActionViewHolder.java145
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchAdapter.java14
-rw-r--r--java/com/android/dialer/searchfragment/list/SearchCursorManager.java35
-rw-r--r--java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml39
-rw-r--r--java/com/android/dialer/searchfragment/list/res/values/strings.xml14
6 files changed, 276 insertions, 11 deletions
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 5b3532cdb..0623d394a 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -28,6 +28,7 @@ import android.support.annotation.VisibleForTesting;
import android.support.v13.app.FragmentCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -44,16 +45,19 @@ import com.android.dialer.enrichedcall.EnrichedCallComponent;
import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener;
import com.android.dialer.searchfragment.common.SearchCursor;
import com.android.dialer.searchfragment.cp2.SearchContactsCursorLoader;
+import com.android.dialer.searchfragment.list.SearchActionViewHolder.Action;
import com.android.dialer.searchfragment.nearbyplaces.NearbyPlacesCursorLoader;
import com.android.dialer.searchfragment.remote.RemoteContactsCursorLoader;
import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader;
import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader.Directory;
+import com.android.dialer.util.CallUtil;
import com.android.dialer.util.PermissionsUtil;
import com.android.dialer.util.ViewUtil;
import com.android.dialer.widget.EmptyContentView;
import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/** Fragment used for searching contacts. */
@@ -99,6 +103,7 @@ public final class NewSearchFragment extends Fragment
View view = inflater.inflate(R.layout.fragment_search, parent, false);
adapter = new SearchAdapter(getActivity(), new SearchCursorManager());
adapter.setCallInitiationType(callInitiationType);
+ adapter.setSearchActions(getActions());
emptyContentView = view.findViewById(R.id.empty_view);
recyclerView = view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -184,22 +189,18 @@ public final class NewSearchFragment extends Fragment
recyclerView.setAdapter(null);
}
- public void setQuery(String query) {
+ public void setQuery(String query, CallInitiationType.Type callInitiationType) {
this.query = query;
+ this.callInitiationType = callInitiationType;
if (adapter != null) {
adapter.setQuery(query);
+ adapter.setCallInitiationType(callInitiationType);
+ adapter.setSearchActions(getActions());
loadNearbyPlacesCursor();
loadRemoteContactsCursors();
}
}
- public void setCallInitiationType(CallInitiationType.Type callInitiationType) {
- this.callInitiationType = callInitiationType;
- if (adapter != null) {
- adapter.setCallInitiationType(callInitiationType);
- }
- }
-
/** Translate the search fragment and resize it to fit on the screen. */
public void animatePosition(int start, int end, int duration) {
// Called before the view is ready, prepare a runnable to run in onCreateView
@@ -329,4 +330,27 @@ public final class NewSearchFragment extends Fragment
public void setRemoteDirectoriesDisabled(boolean disabled) {
remoteDirectoriesDisabledForTesting = disabled;
}
+
+ /**
+ * Returns a list of search actions to be shown in the search results.
+ *
+ * <p>List will be empty if query is 1 or 0 characters or the query isn't from the Dialpad. For
+ * the list of supported actions, see {@link SearchActionViewHolder.Action}.
+ */
+ private List<Integer> getActions() {
+ if (TextUtils.isEmpty(query)
+ || query.length() == 1
+ || callInitiationType == CallInitiationType.Type.REGULAR_SEARCH) {
+ return Collections.emptyList();
+ }
+
+ List<Integer> actions = new ArrayList<>();
+ actions.add(Action.CREATE_NEW_CONTACT);
+ actions.add(Action.ADD_TO_CONTACT);
+ actions.add(Action.SEND_SMS);
+ if (CallUtil.isVideoEnabled(getContext())) {
+ actions.add(Action.MAKE_VILTE_CALL);
+ }
+ return actions;
+ }
}
diff --git a/java/com/android/dialer/searchfragment/list/SearchActionViewHolder.java b/java/com/android/dialer/searchfragment/list/SearchActionViewHolder.java
new file mode 100644
index 000000000..62e5c72b0
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/SearchActionViewHolder.java
@@ -0,0 +1,145 @@
+/*
+ * 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.searchfragment.list;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.IntDef;
+import android.support.annotation.StringRes;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.dialer.callintent.CallInitiationType;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.callintent.CallSpecificAppData;
+import com.android.dialer.common.Assert;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.IntentUtil;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link RecyclerView.ViewHolder} for showing an {@link SearchActionViewHolder.Action} in a list.
+ */
+final class SearchActionViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+
+ /** IntDef for the different types of actions that can be used. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ Action.INVALID,
+ Action.CREATE_NEW_CONTACT,
+ Action.ADD_TO_CONTACT,
+ Action.SEND_SMS,
+ Action.MAKE_VILTE_CALL
+ })
+ @interface Action {
+ int INVALID = 0;
+ /** Opens the prompt to create a new contact. */
+ int CREATE_NEW_CONTACT = 1;
+ /** Opens a prompt to add to an existing contact. */
+ int ADD_TO_CONTACT = 2;
+ /** Opens the SMS conversation with the default SMS app. */
+ int SEND_SMS = 3;
+ /** Attempts to make a VILTE call to the number. */
+ int MAKE_VILTE_CALL = 4;
+ }
+
+ private final Context context;
+ private final ImageView actionImage;
+ private final TextView actionText;
+
+ private @Action int action;
+ private int position;
+ private String query;
+
+ SearchActionViewHolder(View view) {
+ super(view);
+ context = view.getContext();
+ actionImage = view.findViewById(R.id.search_action_image);
+ actionText = view.findViewById(R.id.search_action_text);
+ view.setOnClickListener(this);
+ }
+
+ void setAction(@Action int action, int position, String query) {
+ this.action = action;
+ this.position = position;
+ this.query = query;
+ switch (action) {
+ case Action.ADD_TO_CONTACT:
+ actionText.setText(R.string.search_shortcut_add_to_contact);
+ actionImage.setImageResource(R.drawable.quantum_ic_person_add_vd_theme_24);
+ break;
+ case Action.CREATE_NEW_CONTACT:
+ actionText.setText(R.string.search_shortcut_create_new_contact);
+ actionImage.setImageResource(R.drawable.quantum_ic_person_add_vd_theme_24);
+ break;
+ case Action.MAKE_VILTE_CALL:
+ actionText.setText(R.string.search_shortcut_make_video_call);
+ actionImage.setImageResource(R.drawable.quantum_ic_videocam_vd_theme_24);
+ break;
+ case Action.SEND_SMS:
+ actionText.setText(R.string.search_shortcut_send_sms_message);
+ actionImage.setImageResource(R.drawable.quantum_ic_message_vd_theme_24);
+ break;
+ case Action.INVALID:
+ default:
+ throw Assert.createIllegalStateFailException("Invalid action: " + action);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (action) {
+ case Action.ADD_TO_CONTACT:
+ Logger.get(context).logImpression(DialerImpression.Type.ADD_TO_A_CONTACT_FROM_DIALPAD);
+ Intent intent = IntentUtil.getAddToExistingContactIntent(query);
+ @StringRes int errorString = R.string.add_contact_not_available;
+ DialerUtils.startActivityWithErrorToast(context, intent, errorString);
+ break;
+
+ case Action.CREATE_NEW_CONTACT:
+ Logger.get(context).logImpression(DialerImpression.Type.CREATE_NEW_CONTACT_FROM_DIALPAD);
+ intent = IntentUtil.getNewContactIntent(query);
+ DialerUtils.startActivityWithErrorToast(context, intent);
+ break;
+
+ case Action.MAKE_VILTE_CALL:
+ CallSpecificAppData callSpecificAppData =
+ CallSpecificAppData.newBuilder()
+ .setCallInitiationType(CallInitiationType.Type.DIALPAD)
+ .setPositionOfSelectedSearchResult(position)
+ .setCharactersInSearchString(query.length())
+ .build();
+ intent = new CallIntentBuilder(query, callSpecificAppData).setIsVideoCall(true).build();
+ DialerUtils.startActivityWithErrorToast(context, intent);
+ break;
+
+ case Action.SEND_SMS:
+ intent = IntentUtil.getSendSmsIntent(query);
+ DialerUtils.startActivityWithErrorToast(context, intent);
+ break;
+
+ case Action.INVALID:
+ default:
+ throw Assert.createIllegalStateFailException("Invalid action: " + action);
+ }
+ }
+}
diff --git a/java/com/android/dialer/searchfragment/list/SearchAdapter.java b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
index f08d60e09..61055a0c1 100644
--- a/java/com/android/dialer/searchfragment/list/SearchAdapter.java
+++ b/java/com/android/dialer/searchfragment/list/SearchAdapter.java
@@ -40,6 +40,7 @@ import com.android.dialer.searchfragment.list.SearchCursorManager.RowType;
import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder;
import com.android.dialer.searchfragment.remote.RemoteContactViewHolder;
import com.android.dialer.util.DialerUtils;
+import java.util.List;
/** RecyclerView adapter for {@link NewSearchFragment}. */
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@@ -75,6 +76,9 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder>
case RowType.DIRECTORY_ROW:
return new RemoteContactViewHolder(
LayoutInflater.from(activity).inflate(R.layout.search_contact_row, root, false));
+ case RowType.SEARCH_ACTION:
+ return new SearchActionViewHolder(
+ LayoutInflater.from(activity).inflate(R.layout.search_action_layout, root, false));
case RowType.INVALID:
default:
throw Assert.createIllegalStateFailException("Invalid RowType: " + rowType);
@@ -98,6 +102,9 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder>
String header =
searchCursorManager.getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION);
((HeaderViewHolder) holder).setHeader(header);
+ } else if (holder instanceof SearchActionViewHolder) {
+ ((SearchActionViewHolder) holder)
+ .setAction(searchCursorManager.getSearchAction(position), position, query);
} else {
throw Assert.createIllegalStateFailException("Invalid ViewHolder: " + holder);
}
@@ -124,6 +131,13 @@ public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder>
}
}
+ /** Sets the actions to be shown at the bottom of the search results. */
+ void setSearchActions(List<Integer> actions) {
+ if (searchCursorManager.setSearchActions(actions)) {
+ notifyDataSetChanged();
+ }
+ }
+
void setCallInitiationType(CallInitiationType.Type callInitiationType) {
this.callInitiationType = callInitiationType;
}
diff --git a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
index a303425d3..95bede001 100644
--- a/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
+++ b/java/com/android/dialer/searchfragment/list/SearchCursorManager.java
@@ -23,16 +23,19 @@ import com.android.dialer.common.Assert;
import com.android.dialer.searchfragment.common.SearchCursor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Manages all of the cursors needed for {@link SearchAdapter}.
*
- * <p>This class accepts three cursors:
+ * <p>This class accepts four data sources:
*
* <ul>
* <li>A contacts cursor {@link #setContactsCursor(SearchCursor)}
* <li>A google search results cursor {@link #setNearbyPlacesCursor(SearchCursor)}
* <li>A work directory cursor {@link #setCorpDirectoryCursor(SearchCursor)}
+ * <li>A list of action to be performed on a number {@link #setSearchActions(List)}
* </ul>
*
* <p>The key purpose of this class is to compose three aforementioned cursors together to function
@@ -56,7 +59,8 @@ public final class SearchCursorManager {
SearchCursorManager.RowType.NEARBY_PLACES_HEADER,
SearchCursorManager.RowType.NEARBY_PLACES_ROW,
SearchCursorManager.RowType.DIRECTORY_HEADER,
- SearchCursorManager.RowType.DIRECTORY_ROW
+ SearchCursorManager.RowType.DIRECTORY_ROW,
+ SearchCursorManager.RowType.SEARCH_ACTION
})
@interface RowType {
int INVALID = 0;
@@ -73,11 +77,14 @@ public final class SearchCursorManager {
int DIRECTORY_HEADER = 5;
/** A row containing contact information for contacts stored externally in corp directories. */
int DIRECTORY_ROW = 6;
+ /** A row containing a search action */
+ int SEARCH_ACTION = 7;
}
private SearchCursor contactsCursor = null;
private SearchCursor nearbyPlacesCursor = null;
private SearchCursor corpDirectoryCursor = null;
+ private List<Integer> searchActions = new ArrayList<>();
/** Returns true if the cursor changed. */
boolean setContactsCursor(@Nullable SearchCursor cursor) {
@@ -149,6 +156,20 @@ public final class SearchCursorManager {
return updated;
}
+ /** Sets search actions, returning true if different from existing actions. */
+ boolean setSearchActions(List<Integer> searchActions) {
+ if (!this.searchActions.equals(searchActions)) {
+ this.searchActions = searchActions;
+ return true;
+ }
+ return false;
+ }
+
+ /** Returns {@link SearchActionViewHolder.Action}. */
+ int getSearchAction(int position) {
+ return searchActions.get(position - getCount() + searchActions.size());
+ }
+
/** Returns the sum of counts of all cursors, including headers. */
int getCount() {
int count = 0;
@@ -164,11 +185,19 @@ public final class SearchCursorManager {
count += corpDirectoryCursor.getCount();
}
- return count;
+ return count + searchActions.size();
}
@RowType
int getRowType(int position) {
+ int cursorCount = getCount();
+ if (position >= cursorCount) {
+ throw Assert.createIllegalStateFailException(
+ String.format("Invalid position: %d, cursor count: %d", position, cursorCount));
+ } else if (position >= cursorCount - searchActions.size()) {
+ return RowType.SEARCH_ACTION;
+ }
+
SearchCursor cursor = getCursor(position);
if (cursor == contactsCursor) {
return cursor.isHeader() ? RowType.CONTACT_HEADER : RowType.CONTACT_ROW;
diff --git a/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml b/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml
new file mode 100644
index 000000000..99d0fbf0c
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/list/res/layout/search_action_layout.xml
@@ -0,0 +1,39 @@
+<?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
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp">
+
+ <ImageView
+ android:id="@+id/search_action_image"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="8dp"
+ android:layout_gravity="center_vertical"
+ android:padding="12dp"
+ android:tint="@color/dialer_theme_color"/>
+
+ <TextView
+ android:id="@+id/search_action_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="72dp"
+ android:layout_marginEnd="16dp"
+ android:layout_gravity="center_vertical"
+ style="@style/PrimaryText"/>
+</FrameLayout> \ No newline at end of file
diff --git a/java/com/android/dialer/searchfragment/list/res/values/strings.xml b/java/com/android/dialer/searchfragment/list/res/values/strings.xml
index 0d25b8c7a..ea238fc92 100644
--- a/java/com/android/dialer/searchfragment/list/res/values/strings.xml
+++ b/java/com/android/dialer/searchfragment/list/res/values/strings.xml
@@ -17,4 +17,18 @@
<resources>
<!-- Shown as a prompt to turn on contacts permissions to allow contact search [CHAR LIMIT=NONE]. See 2424710404207193826 for current translation. -->
<string name="new_permission_no_search">To search your contacts, turn on the Contacts permissions.</string>
+
+ <!-- Shortcut item used to add a number directly to a new contact from search.
+ [CHAR LIMIT=25] -->
+ <string name="search_shortcut_create_new_contact">Create new contact</string>
+
+ <!-- Shortcut item used to add a number to an existing contact directly from search.
+ [CHAR LIMIT=25] -->
+ <string name="search_shortcut_add_to_contact">Add to a contact</string>
+
+ <!-- Shortcut item used to send a text message directly from search. [CHAR LIMIT=25] -->
+ <string name="search_shortcut_send_sms_message">Send SMS</string>
+
+ <!-- Shortcut item used to make a video call directly from search. [CHAR LIMIT=25] -->
+ <string name="search_shortcut_make_video_call">Make video call</string>
</resources>