1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.searchfragment.list;
18 
19 import android.content.Context;
20 import android.support.annotation.Nullable;
21 import android.support.annotation.VisibleForTesting;
22 import android.support.v7.widget.RecyclerView;
23 import android.support.v7.widget.RecyclerView.ViewHolder;
24 import android.text.TextUtils;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.View.OnClickListener;
28 import android.view.ViewGroup;
29 import com.android.dialer.common.Assert;
30 import com.android.dialer.searchfragment.common.RowClickListener;
31 import com.android.dialer.searchfragment.common.SearchCursor;
32 import com.android.dialer.searchfragment.cp2.SearchContactViewHolder;
33 import com.android.dialer.searchfragment.directories.DirectoryContactViewHolder;
34 import com.android.dialer.searchfragment.list.SearchCursorManager.RowType;
35 import com.android.dialer.searchfragment.nearbyplaces.NearbyPlaceViewHolder;
36 import java.util.List;
37 
38 /** RecyclerView adapter for {@link NewSearchFragment}. */
39 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
40 public final class SearchAdapter extends RecyclerView.Adapter<ViewHolder> {
41 
42   private final SearchCursorManager searchCursorManager;
43   private final Context context;
44 
45   private String query;
46   // Raw query number from dialpad, which may contain special character such as "+". This is used
47   // for actions to add contact or send sms.
48   private String rawNumber;
49   private OnClickListener allowClickListener;
50   private OnClickListener dismissClickListener;
51   private RowClickListener rowClickListener;
52 
53   @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
SearchAdapter( Context context, SearchCursorManager searchCursorManager, RowClickListener rowClickListener)54   public SearchAdapter(
55       Context context, SearchCursorManager searchCursorManager, RowClickListener rowClickListener) {
56     this.context = context;
57     this.searchCursorManager = searchCursorManager;
58     this.rowClickListener = rowClickListener;
59   }
60 
61   @Override
onCreateViewHolder(ViewGroup root, @RowType int rowType)62   public ViewHolder onCreateViewHolder(ViewGroup root, @RowType int rowType) {
63     switch (rowType) {
64       case RowType.CONTACT_ROW:
65         return new SearchContactViewHolder(
66             LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false),
67             rowClickListener);
68       case RowType.NEARBY_PLACES_ROW:
69         return new NearbyPlaceViewHolder(
70             LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false),
71             rowClickListener);
72       case RowType.CONTACT_HEADER:
73       case RowType.DIRECTORY_HEADER:
74       case RowType.NEARBY_PLACES_HEADER:
75         return new HeaderViewHolder(
76             LayoutInflater.from(context).inflate(R.layout.header_layout, root, false));
77       case RowType.DIRECTORY_ROW:
78         return new DirectoryContactViewHolder(
79             LayoutInflater.from(context).inflate(R.layout.search_contact_row, root, false),
80             rowClickListener);
81       case RowType.SEARCH_ACTION:
82         return new SearchActionViewHolder(
83             LayoutInflater.from(context).inflate(R.layout.search_action_layout, root, false),
84             rowClickListener);
85       case RowType.LOCATION_REQUEST:
86         return new LocationPermissionViewHolder(
87             LayoutInflater.from(context).inflate(R.layout.location_permission_row, root, false),
88             allowClickListener,
89             dismissClickListener);
90       case RowType.INVALID:
91       default:
92         throw Assert.createIllegalStateFailException("Invalid RowType: " + rowType);
93     }
94   }
95 
96   @Override
getItemViewType(int position)97   public @RowType int getItemViewType(int position) {
98     return searchCursorManager.getRowType(position);
99   }
100 
101   @Override
onBindViewHolder(ViewHolder holder, int position)102   public void onBindViewHolder(ViewHolder holder, int position) {
103     if (holder instanceof SearchContactViewHolder) {
104       ((SearchContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
105     } else if (holder instanceof NearbyPlaceViewHolder) {
106       ((NearbyPlaceViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
107     } else if (holder instanceof DirectoryContactViewHolder) {
108       ((DirectoryContactViewHolder) holder).bind(searchCursorManager.getCursor(position), query);
109     } else if (holder instanceof HeaderViewHolder) {
110       String header =
111           searchCursorManager.getCursor(position).getString(SearchCursor.HEADER_TEXT_POSITION);
112       ((HeaderViewHolder) holder).setHeader(header);
113     } else if (holder instanceof SearchActionViewHolder) {
114       ((SearchActionViewHolder) holder)
115           .setAction(
116               searchCursorManager.getSearchAction(position),
117               position,
118               TextUtils.isEmpty(rawNumber) ? query : rawNumber);
119     } else if (holder instanceof LocationPermissionViewHolder) {
120       // No-op
121     } else {
122       throw Assert.createIllegalStateFailException("Invalid ViewHolder: " + holder);
123     }
124   }
125 
setContactsCursor(SearchCursor cursor)126   public void setContactsCursor(SearchCursor cursor) {
127     if (searchCursorManager.setContactsCursor(cursor)) {
128       // Since this is a new contacts cursor, we need to reapply the filter.
129       searchCursorManager.setQuery(query);
130       notifyDataSetChanged();
131     }
132   }
133 
clear()134   void clear() {
135     searchCursorManager.clear();
136   }
137 
138   @Override
getItemCount()139   public int getItemCount() {
140     return searchCursorManager.getCount();
141   }
142 
setQuery(String query, @Nullable String rawNumber)143   public void setQuery(String query, @Nullable String rawNumber) {
144     this.query = query;
145     this.rawNumber = rawNumber;
146     if (searchCursorManager.setQuery(query)) {
147       notifyDataSetChanged();
148     }
149   }
150 
151   /** Sets the actions to be shown at the bottom of the search results. */
setSearchActions(List<Integer> actions)152   void setSearchActions(List<Integer> actions) {
153     if (searchCursorManager.setSearchActions(actions)) {
154       notifyDataSetChanged();
155     }
156   }
157 
setNearbyPlacesCursor(SearchCursor nearbyPlacesCursor)158   public void setNearbyPlacesCursor(SearchCursor nearbyPlacesCursor) {
159     if (searchCursorManager.setNearbyPlacesCursor(nearbyPlacesCursor)) {
160       notifyDataSetChanged();
161     }
162   }
163 
164   /**
165    * Updates the adapter to show the location request row element. If the element was previously
166    * hidden, the adapter will call {@link #notifyDataSetChanged()}.
167    */
showLocationPermissionRequest( OnClickListener allowClickListener, OnClickListener dismissClickListener)168   public void showLocationPermissionRequest(
169       OnClickListener allowClickListener, OnClickListener dismissClickListener) {
170     this.allowClickListener = Assert.isNotNull(allowClickListener);
171     this.dismissClickListener = Assert.isNotNull(dismissClickListener);
172     if (searchCursorManager.showLocationPermissionRequest(true)) {
173       notifyItemInserted(0);
174     }
175   }
176 
177   /**
178    * Updates the adapter to hide the location request row element. If the element was previously
179    * visible, the adapter will call {@link #notifyDataSetChanged()}.
180    */
hideLocationPermissionRequest()181   void hideLocationPermissionRequest() {
182     allowClickListener = null;
183     dismissClickListener = null;
184     if (searchCursorManager.showLocationPermissionRequest(false)) {
185       notifyItemRemoved(0);
186     }
187   }
188 
setDirectoryContactsCursor(SearchCursor directoryContactsCursor)189   void setDirectoryContactsCursor(SearchCursor directoryContactsCursor) {
190     if (searchCursorManager.setCorpDirectoryCursor(directoryContactsCursor)) {
191       notifyDataSetChanged();
192     }
193   }
194 
195   /** Viewholder for R.layout.location_permission_row that requests the location permission. */
196   private static class LocationPermissionViewHolder extends RecyclerView.ViewHolder {
197 
LocationPermissionViewHolder( View itemView, OnClickListener allowClickListener, OnClickListener dismissClickListener)198     LocationPermissionViewHolder(
199         View itemView, OnClickListener allowClickListener, OnClickListener dismissClickListener) {
200       super(itemView);
201       Assert.isNotNull(allowClickListener);
202       Assert.isNotNull(dismissClickListener);
203       itemView
204           .findViewById(
205               com.android.dialer.searchfragment.nearbyplaces.R.id.location_permission_allow)
206           .setOnClickListener(allowClickListener);
207       itemView
208           .findViewById(
209               com.android.dialer.searchfragment.nearbyplaces.R.id.location_permission_dismiss)
210           .setOnClickListener(dismissClickListener);
211     }
212   }
213 }
214