blob: d50ccf55c0562f00d775b142c5405b302b1c67ca [file] [log] [blame]
Wenyi Wang1fc3ef42016-01-14 18:21:40 -08001package com.android.contacts.callblocking;
2
Wenyi Wang1fc3ef42016-01-14 18:21:40 -08003import android.app.Activity;
4import android.content.pm.PackageManager;
5import android.content.res.Configuration;
6import android.content.res.Resources;
7import android.os.Bundle;
8import android.support.v7.app.ActionBar;
9import android.support.v7.app.AppCompatActivity;
10import android.support.v13.app.FragmentCompat;
11import android.telephony.PhoneNumberUtils;
12import android.text.Editable;
13import android.text.TextUtils;
14import android.text.TextWatcher;
15import android.util.Log;
16import android.util.TypedValue;
17import android.view.LayoutInflater;
18import android.view.View;
19import android.view.ViewGroup;
20import android.widget.AbsListView;
21import android.widget.AbsListView.OnScrollListener;
22import android.widget.AdapterView;
23import android.widget.EditText;
24import android.widget.LinearLayout;
25import android.widget.ListView;
26import android.widget.Space;
27import android.widget.Toast;
28
29import com.android.contacts.R;
30import com.android.contacts.callblocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
31import com.android.contacts.common.GeoUtil;
32import com.android.contacts.common.list.ContactEntryListAdapter;
33import com.android.contacts.common.list.ContactListItemView;
34import com.android.contacts.common.list.PhoneNumberPickerFragment;
35import com.android.contacts.common.list.PinnedHeaderListView;
36import com.android.contacts.common.util.ContactDisplayUtils;
37import com.android.contacts.common.util.PermissionsUtil;
38import com.android.contacts.common.util.ViewUtil;
39import com.android.contacts.widget.EmptyContentView;
40import com.android.contacts.widget.SearchEditTextLayout;
41import com.android.contacts.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
42
43import static android.Manifest.permission.READ_CONTACTS;
44
45public class SearchFragment extends PhoneNumberPickerFragment
46 implements OnEmptyViewActionButtonClickedListener,
47 FragmentCompat.OnRequestPermissionsResultCallback,
48 BlockNumberDialogFragment.Callback{
49 private static final String TAG = SearchFragment.class.getSimpleName();
50
51 public static final int PERMISSION_REQUEST_CODE = 1;
52 private static final int SEARCH_DIRECTORY_RESULT_LIMIT = 5;
Wenyi Wang1fc3ef42016-01-14 18:21:40 -080053
Wenyi Wang1fc3ef42016-01-14 18:21:40 -080054 private View.OnTouchListener mActivityOnTouchListener;
55 private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler;
56 private EditText mSearchView;
57
58 private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {
59 @Override
60 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
61
62 @Override
63 public void onTextChanged(CharSequence s, int start, int before, int count) {
64 setQueryString(s.toString(), false);
65 }
66
67 @Override
68 public void afterTextChanged(Editable s) {}
69 };
70
71 private final SearchEditTextLayout.Callback mSearchLayoutCallback =
72 new SearchEditTextLayout.Callback() {
73 @Override
74 public void onBackButtonClicked() {
75 getActivity().onBackPressed();
76 }
77
78 @Override
79 public void onSearchViewClicked() {
80 }
81 };
82 /**
83 * Stores the untouched user-entered string that is used to populate the add to contacts
84 * intent.
85 */
Wenyi Wang1fc3ef42016-01-14 18:21:40 -080086 private int mActionBarHeight;
87 private int mShadowHeight;
88 private int mPaddingTop;
89
90 /**
91 * Used to resize the list view containing search results so that it fits the available space
92 * above the dialpad. Does not have a user-visible effect in regular touch usage (since the
93 * dialpad hides that portion of the ListView anyway), but improves usability in accessibility
94 * mode.
95 */
96 private Space mSpacer;
97
98 private HostInterface mActivity;
99
100 protected EmptyContentView mEmptyView;
101
102 public interface HostInterface {
103 boolean isActionBarShowing();
104 boolean isDialpadShown();
105 int getDialpadHeight();
106 int getActionBarHideOffset();
107 int getActionBarHeight();
108 }
109
110 protected String mPermissionToRequest;
111
112 public SearchFragment() {
113 configureDirectorySearch();
114 }
115
116 public void configureDirectorySearch() {
117 setDirectorySearchEnabled(true);
118 setDirectoryResultLimit(SEARCH_DIRECTORY_RESULT_LIMIT);
119 }
120
121 @Override
122 public void onCreate(Bundle savedInstanceState) {
123 super.onCreate(savedInstanceState);
124
125 setShowEmptyListForNullQuery(true);
126 /*
127 * Pass in the empty string here so ContactEntryListFragment#setQueryString interprets it as
128 * an empty search query, rather than as an uninitalized value. In the latter case, the
129 * adapter returned by #createListAdapter is used, which populates the view with contacts.
130 * Passing in the empty string forces ContactEntryListFragment to interpret it as an empty
131 * query, which results in showing an empty view
132 */
133 setQueryString(getQueryString() == null ? "" : getQueryString(), false);
134 mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler(
135 getContext().getContentResolver());
136 }
137
138 @Override
139 public void onResume() {
140 super.onResume();
141
142 ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
143 actionBar.setCustomView(R.layout.search_edittext);
144 actionBar.setDisplayShowCustomEnabled(true);
145 actionBar.setDisplayHomeAsUpEnabled(false);
146 actionBar.setDisplayShowHomeEnabled(false);
147
148 final SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar
149 .getCustomView().findViewById(R.id.search_view_container);
150 searchEditTextLayout.expand(true);
151 searchEditTextLayout.setCallback(mSearchLayoutCallback);
152 searchEditTextLayout.setBackgroundDrawable(null);
153
154 mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view);
155 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
156 mSearchView.setHint(R.string.block_number_search_hint);
157
158 searchEditTextLayout.findViewById(R.id.search_box_expanded)
159 .setBackgroundColor(getContext().getResources().getColor(android.R.color.white));
160
161 if (!TextUtils.isEmpty(getQueryString())) {
162 mSearchView.setText(getQueryString());
163 }
164
165 // TODO: Don't set custom text size; use default search text size.
166 mSearchView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
167 getResources().getDimension(R.dimen.blocked_number_search_text_size));
168 }
169
170 @Override
171 protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
172 super.onCreateView(inflater, container);
173 ((PinnedHeaderListView) getListView()).setScrollToSectionOnHeaderTouch(true);
174 }
175
176 @Override
177 public void onAttach(Activity activity) {
178 super.onAttach(activity);
179
180 setQuickContactEnabled(true);
181 setAdjustSelectionBoundsEnabled(false);
182 setDarkTheme(false);
183 setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(false /* opposite */));
184 setUseCallableUri(true);
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800185 }
186
187 @Override
188 public void onStart() {
189 super.onStart();
190 if (isSearchMode()) {
191 getAdapter().setHasHeader(0, false);
192 }
193
194 mActivity = (HostInterface) getActivity();
195
196 final Resources res = getResources();
197 mActionBarHeight = mActivity.getActionBarHeight();
198 mShadowHeight = res.getDrawable(R.drawable.search_shadow).getIntrinsicHeight();
199 mPaddingTop = res.getDimensionPixelSize(R.dimen.search_list_padding_top);
200
201 final View parentView = getView();
202
203 final ListView listView = getListView();
204
205 if (mEmptyView == null) {
206 mEmptyView = new EmptyContentView(getActivity());
207 ((ViewGroup) getListView().getParent()).addView(mEmptyView);
208 getListView().setEmptyView(mEmptyView);
209 setupEmptyView();
210 }
211
212 listView.setBackgroundColor(res.getColor(R.color.background_contacts_results));
213 listView.setClipToPadding(false);
214 setVisibleScrollbarEnabled(false);
215
216 // Turn off accessibility live region as the list constantly update itself and spam
217 // messages.
218 listView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE);
219 ContentChangedFilter.addToParent(listView);
220
221 listView.setOnScrollListener(new OnScrollListener() {
222 @Override
223 public void onScrollStateChanged(AbsListView view, int scrollState) {
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800224 }
225
226 @Override
227 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
228 int totalItemCount) {
229 }
230 });
231 if (mActivityOnTouchListener != null) {
232 listView.setOnTouchListener(mActivityOnTouchListener);
233 }
234
235 updatePosition();
236 }
237
238 @Override
239 public void onViewCreated(View view, Bundle savedInstanceState) {
240 super.onViewCreated(view, savedInstanceState);
241 ViewUtil.addBottomPaddingToListViewForFab(getListView(), getResources());
242 }
243
244 @Override
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800245 protected void setSearchMode(boolean flag) {
246 super.setSearchMode(flag);
247 // This hides the "All contacts with phone numbers" header in the search fragment
248 final ContactEntryListAdapter adapter = getAdapter();
249 if (adapter != null) {
250 adapter.setHasHeader(0, false);
251 }
252 }
253
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800254 @Override
255 protected ContactEntryListAdapter createListAdapter() {
256 SearchAdapter adapter = new SearchAdapter(getActivity());
257 adapter.setDisplayPhotos(true);
258 // Don't show SIP addresses.
259 adapter.setUseCallableUri(false);
260 // Keep in sync with the queryString set in #onCreate
261 adapter.setQueryString(getQueryString() == null ? "" : getQueryString());
262 return adapter;
263 }
264
265 protected void setupEmptyView() {
266 if (mEmptyView != null && getActivity() != null) {
267 final int imageResource;
268 final int actionLabelResource;
269 final int descriptionResource;
270 final OnEmptyViewActionButtonClickedListener listener;
271 if (!PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) {
272 imageResource = R.drawable.empty_contacts;
273 actionLabelResource = R.string.permission_single_turn_on;
274 descriptionResource = R.string.permission_no_search;
275 listener = this;
276 mPermissionToRequest = READ_CONTACTS;
277 } else {
278 imageResource = EmptyContentView.NO_IMAGE;
279 actionLabelResource = EmptyContentView.NO_LABEL;
280 descriptionResource = EmptyContentView.NO_LABEL;
281 listener = null;
282 mPermissionToRequest = null;
283 }
284
285 mEmptyView.setImage(imageResource);
286 mEmptyView.setActionLabel(actionLabelResource);
287 mEmptyView.setDescription(descriptionResource);
288 if (listener != null) {
289 mEmptyView.setActionClickedListener(listener);
290 }
291 }
292 }
293
294 @Override
295 public void onEmptyViewActionButtonClicked() {
296 final Activity activity = getActivity();
297 if (activity == null) {
298 return;
299 }
300
301 if (READ_CONTACTS.equals(mPermissionToRequest)) {
302 FragmentCompat.requestPermissions(this, new String[]{mPermissionToRequest},
303 PERMISSION_REQUEST_CODE);
304 }
305 }
306
307
308 @Override
309 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
310 super.onItemClick(parent, view, position, id);
311 final int adapterPosition = position - getListView().getHeaderViewsCount();
312 final SearchAdapter adapter = (SearchAdapter) getAdapter();
313 final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition);
Wenyi Wangbf452192016-01-22 10:11:43 -0800314 final Long blockId = (Long) view.getTag(R.id.block_id);
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800315 final String number;
316 switch (shortcutType) {
317 case SearchAdapter.SHORTCUT_INVALID:
318 // Handles click on a search result, either contact or nearby places result.
319 number = adapter.getPhoneNumber(adapterPosition);
320 blockContactNumber(number, blockId);
321 break;
322 case SearchAdapter.SHORTCUT_BLOCK_NUMBER:
323 // Handles click on 'Block number' shortcut to add the user query as a number.
324 number = adapter.getQueryString();
325 blockNumber(number);
326 break;
327 default:
328 Log.w(TAG, "Ignoring unsupported shortcut type: " + shortcutType);
329 break;
330 }
331 }
332
333 private void blockNumber(final String number) {
334 final String countryIso = GeoUtil.getCurrentCountryIso(getContext());
335 final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() {
336 @Override
Wenyi Wangbf452192016-01-22 10:11:43 -0800337 public void onCheckComplete(Long id) {
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800338 if (id == null) {
339 BlockNumberDialogFragment.show(
340 id,
341 number,
342 countryIso,
343 PhoneNumberUtils.formatNumber(number, countryIso),
344 R.id.blocked_numbers_activity_container,
345 getFragmentManager(),
346 SearchFragment.this);
347 } else {
348 Toast.makeText(getContext(),
349 ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(),
350 R.string.alreadyBlocked, number),
351 Toast.LENGTH_SHORT).show();
352 }
353 }
354 };
355 final boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber(
356 onCheckListener, number, countryIso);
357 if (!success) {
358 Toast.makeText(getContext(),
359 ContactDisplayUtils.getTtsSpannedPhoneNumber(
360 getResources(), R.string.invalidNumber, number),
361 Toast.LENGTH_SHORT).show();
362 }
363 }
364
365
366 @Override
367 protected void onItemClick(int position, long id) {
368 // Prevent super.onItemClicked(int position, long id) from being called.
369 }
370
371 /**
372 * Updates the position and padding of the search fragment.
373 */
374 public void updatePosition() {
375 int endTranslationValue = 0;
376 // Prevents ListView from being translated down after a rotation when the ActionBar is up.
377 if (mActivity.isActionBarShowing()) {
378 endTranslationValue =
379 mActivity.isDialpadShown() ? 0 : mActionBarHeight - mShadowHeight;
380 }
381 getView().setTranslationY(endTranslationValue);
382 resizeListView();
383
384 // There is padding which should only be applied when the dialpad is not shown.
385 int paddingTop = mActivity.isDialpadShown() ? 0 : mPaddingTop;
386 final ListView listView = getListView();
387 listView.setPaddingRelative(
388 listView.getPaddingStart(),
389 paddingTop,
390 listView.getPaddingEnd(),
391 listView.getPaddingBottom());
392 }
393
394 public void resizeListView() {
395 if (mSpacer == null) {
396 return;
397 }
398 int spacerHeight = mActivity.isDialpadShown() ? mActivity.getDialpadHeight() : 0;
399 if (spacerHeight != mSpacer.getHeight()) {
400 final LinearLayout.LayoutParams lp =
401 (LinearLayout.LayoutParams) mSpacer.getLayoutParams();
402 lp.height = spacerHeight;
403 mSpacer.setLayoutParams(lp);
404 }
405 }
406
407 @Override
408 protected void startLoading() {
409 if (getActivity() == null) {
410 return;
411 }
412
413 if (PermissionsUtil.hasContactsPermissions(getActivity())) {
414 super.startLoading();
415 } else if (TextUtils.isEmpty(getQueryString())) {
416 // Clear out any existing call shortcuts.
417 final SearchAdapter adapter = (SearchAdapter) getAdapter();
418 adapter.disableAllShortcuts();
419 } else {
420 // The contact list is not going to change (we have no results since permissions are
421 // denied), but the shortcuts might because of the different query, so update the
422 // list.
423 getAdapter().notifyDataSetChanged();
424 }
425
426 setupEmptyView();
427 }
428
429 public void setOnTouchListener(View.OnTouchListener onTouchListener) {
430 mActivityOnTouchListener = onTouchListener;
431 }
432
433 @Override
434 protected View inflateView(LayoutInflater inflater, ViewGroup container) {
435 final LinearLayout parent = (LinearLayout) super.inflateView(inflater, container);
436 final int orientation = getResources().getConfiguration().orientation;
437 if (orientation == Configuration.ORIENTATION_PORTRAIT) {
438 mSpacer = new Space(getActivity());
439 parent.addView(mSpacer,
440 new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0));
441 }
442 return parent;
443 }
444
445 @Override
446 public void onRequestPermissionsResult(int requestCode, String[] permissions,
447 int[] grantResults) {
448 if (requestCode == PERMISSION_REQUEST_CODE) {
449 setupEmptyView();
450 if (grantResults != null && grantResults.length == 1
451 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
452 PermissionsUtil.notifyPermissionGranted(getActivity(), mPermissionToRequest);
453 }
454 }
455 }
456
457 @Override
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800458 public void onFilterNumberSuccess() {
459 goBack();
460 }
461
462 @Override
463 public void onUnfilterNumberSuccess() {
464 Log.wtf(TAG, "Unblocked a number from the SearchFragment");
465 goBack();
466 }
467
468 private void goBack() {
469 Activity activity = getActivity();
470 if (activity == null) {
471 return;
472 }
473 activity.onBackPressed();
474 }
475
476 @Override
477 public void onChangeFilteredNumberUndo() {
478 getAdapter().notifyDataSetChanged();
479 }
480
Wenyi Wangbf452192016-01-22 10:11:43 -0800481 private void blockContactNumber(final String number, final Long blockId) {
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800482 if (blockId != null) {
483 Toast.makeText(getContext(), ContactDisplayUtils.getTtsSpannedPhoneNumber(
484 getResources(), R.string.alreadyBlocked, number),
485 Toast.LENGTH_SHORT).show();
486 return;
487 }
488
Wenyi Wangbf452192016-01-22 10:11:43 -0800489 BlockNumberDialogFragment.show(
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800490 blockId,
491 number,
Wenyi Wangbf452192016-01-22 10:11:43 -0800492 GeoUtil.getCurrentCountryIso(getContext()),
Wenyi Wang1fc3ef42016-01-14 18:21:40 -0800493 number,
494 R.id.blocked_numbers_activity_container,
495 getFragmentManager(),
496 this);
497 }
498}