summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/searchfragment
diff options
context:
space:
mode:
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>