| package com.android.contacts.callblocking; |
| |
| import android.app.Activity; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.os.Bundle; |
| import android.support.v7.app.ActionBar; |
| import android.support.v7.app.AppCompatActivity; |
| import android.support.v13.app.FragmentCompat; |
| import android.telephony.PhoneNumberUtils; |
| import android.text.Editable; |
| import android.text.TextUtils; |
| import android.text.TextWatcher; |
| import android.util.Log; |
| import android.util.TypedValue; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.AbsListView; |
| import android.widget.AbsListView.OnScrollListener; |
| import android.widget.AdapterView; |
| import android.widget.EditText; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.Space; |
| import android.widget.Toast; |
| |
| import com.android.contacts.R; |
| import com.android.contacts.callblocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener; |
| import com.android.contacts.common.GeoUtil; |
| import com.android.contacts.common.list.ContactEntryListAdapter; |
| import com.android.contacts.common.list.ContactListItemView; |
| import com.android.contacts.common.list.PhoneNumberPickerFragment; |
| import com.android.contacts.common.list.PinnedHeaderListView; |
| import com.android.contacts.common.util.ContactDisplayUtils; |
| import com.android.contacts.common.util.PermissionsUtil; |
| import com.android.contacts.common.util.ViewUtil; |
| import com.android.contacts.widget.EmptyContentView; |
| import com.android.contacts.widget.SearchEditTextLayout; |
| import com.android.contacts.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener; |
| |
| import static android.Manifest.permission.READ_CONTACTS; |
| |
| public class SearchFragment extends PhoneNumberPickerFragment |
| implements OnEmptyViewActionButtonClickedListener, |
| FragmentCompat.OnRequestPermissionsResultCallback, |
| BlockNumberDialogFragment.Callback{ |
| private static final String TAG = SearchFragment.class.getSimpleName(); |
| |
| public static final int PERMISSION_REQUEST_CODE = 1; |
| private static final int SEARCH_DIRECTORY_RESULT_LIMIT = 5; |
| |
| private View.OnTouchListener mActivityOnTouchListener; |
| private FilteredNumberAsyncQueryHandler mFilteredNumberAsyncQueryHandler; |
| private EditText mSearchView; |
| |
| private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() { |
| @Override |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) {} |
| |
| @Override |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| setQueryString(s.toString(), false); |
| } |
| |
| @Override |
| public void afterTextChanged(Editable s) {} |
| }; |
| |
| private final SearchEditTextLayout.Callback mSearchLayoutCallback = |
| new SearchEditTextLayout.Callback() { |
| @Override |
| public void onBackButtonClicked() { |
| getActivity().onBackPressed(); |
| } |
| |
| @Override |
| public void onSearchViewClicked() { |
| } |
| }; |
| /** |
| * Stores the untouched user-entered string that is used to populate the add to contacts |
| * intent. |
| */ |
| private int mActionBarHeight; |
| private int mShadowHeight; |
| private int mPaddingTop; |
| |
| /** |
| * Used to resize the list view containing search results so that it fits the available space |
| * above the dialpad. Does not have a user-visible effect in regular touch usage (since the |
| * dialpad hides that portion of the ListView anyway), but improves usability in accessibility |
| * mode. |
| */ |
| private Space mSpacer; |
| |
| private HostInterface mActivity; |
| |
| protected EmptyContentView mEmptyView; |
| |
| public interface HostInterface { |
| boolean isActionBarShowing(); |
| boolean isDialpadShown(); |
| int getDialpadHeight(); |
| int getActionBarHideOffset(); |
| int getActionBarHeight(); |
| } |
| |
| protected String mPermissionToRequest; |
| |
| public SearchFragment() { |
| configureDirectorySearch(); |
| } |
| |
| public void configureDirectorySearch() { |
| setDirectorySearchEnabled(true); |
| setDirectoryResultLimit(SEARCH_DIRECTORY_RESULT_LIMIT); |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| setShowEmptyListForNullQuery(true); |
| /* |
| * Pass in the empty string here so ContactEntryListFragment#setQueryString interprets it as |
| * an empty search query, rather than as an uninitalized value. In the latter case, the |
| * adapter returned by #createListAdapter is used, which populates the view with contacts. |
| * Passing in the empty string forces ContactEntryListFragment to interpret it as an empty |
| * query, which results in showing an empty view |
| */ |
| setQueryString(getQueryString() == null ? "" : getQueryString(), false); |
| mFilteredNumberAsyncQueryHandler = new FilteredNumberAsyncQueryHandler( |
| getContext().getContentResolver()); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| |
| ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); |
| actionBar.setCustomView(R.layout.search_edittext); |
| actionBar.setDisplayShowCustomEnabled(true); |
| actionBar.setDisplayHomeAsUpEnabled(false); |
| actionBar.setDisplayShowHomeEnabled(false); |
| |
| final SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar |
| .getCustomView().findViewById(R.id.search_view_container); |
| searchEditTextLayout.expand(true); |
| searchEditTextLayout.setCallback(mSearchLayoutCallback); |
| searchEditTextLayout.setBackgroundDrawable(null); |
| |
| mSearchView = (EditText) searchEditTextLayout.findViewById(R.id.search_view); |
| mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener); |
| mSearchView.setHint(R.string.block_number_search_hint); |
| |
| searchEditTextLayout.findViewById(R.id.search_box_expanded) |
| .setBackgroundColor(getContext().getResources().getColor(android.R.color.white)); |
| |
| if (!TextUtils.isEmpty(getQueryString())) { |
| mSearchView.setText(getQueryString()); |
| } |
| |
| // TODO: Don't set custom text size; use default search text size. |
| mSearchView.setTextSize(TypedValue.COMPLEX_UNIT_PX, |
| getResources().getDimension(R.dimen.blocked_number_search_text_size)); |
| } |
| |
| @Override |
| protected void onCreateView(LayoutInflater inflater, ViewGroup container) { |
| super.onCreateView(inflater, container); |
| ((PinnedHeaderListView) getListView()).setScrollToSectionOnHeaderTouch(true); |
| } |
| |
| @Override |
| public void onAttach(Activity activity) { |
| super.onAttach(activity); |
| |
| setQuickContactEnabled(true); |
| setAdjustSelectionBoundsEnabled(false); |
| setDarkTheme(false); |
| setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(false /* opposite */)); |
| setUseCallableUri(true); |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| if (isSearchMode()) { |
| getAdapter().setHasHeader(0, false); |
| } |
| |
| mActivity = (HostInterface) getActivity(); |
| |
| final Resources res = getResources(); |
| mActionBarHeight = mActivity.getActionBarHeight(); |
| mShadowHeight = res.getDrawable(R.drawable.search_shadow).getIntrinsicHeight(); |
| mPaddingTop = res.getDimensionPixelSize(R.dimen.search_list_padding_top); |
| |
| final View parentView = getView(); |
| |
| final ListView listView = getListView(); |
| |
| if (mEmptyView == null) { |
| mEmptyView = new EmptyContentView(getActivity()); |
| ((ViewGroup) getListView().getParent()).addView(mEmptyView); |
| getListView().setEmptyView(mEmptyView); |
| setupEmptyView(); |
| } |
| |
| listView.setBackgroundColor(res.getColor(R.color.background_contacts_results)); |
| listView.setClipToPadding(false); |
| setVisibleScrollbarEnabled(false); |
| |
| // Turn off accessibility live region as the list constantly update itself and spam |
| // messages. |
| listView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); |
| ContentChangedFilter.addToParent(listView); |
| |
| listView.setOnScrollListener(new OnScrollListener() { |
| @Override |
| public void onScrollStateChanged(AbsListView view, int scrollState) { |
| } |
| |
| @Override |
| public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, |
| int totalItemCount) { |
| } |
| }); |
| if (mActivityOnTouchListener != null) { |
| listView.setOnTouchListener(mActivityOnTouchListener); |
| } |
| |
| updatePosition(); |
| } |
| |
| @Override |
| public void onViewCreated(View view, Bundle savedInstanceState) { |
| super.onViewCreated(view, savedInstanceState); |
| ViewUtil.addBottomPaddingToListViewForFab(getListView(), getResources()); |
| } |
| |
| @Override |
| protected void setSearchMode(boolean flag) { |
| super.setSearchMode(flag); |
| // This hides the "All contacts with phone numbers" header in the search fragment |
| final ContactEntryListAdapter adapter = getAdapter(); |
| if (adapter != null) { |
| adapter.setHasHeader(0, false); |
| } |
| } |
| |
| @Override |
| protected ContactEntryListAdapter createListAdapter() { |
| SearchAdapter adapter = new SearchAdapter(getActivity()); |
| adapter.setDisplayPhotos(true); |
| // Don't show SIP addresses. |
| adapter.setUseCallableUri(false); |
| // Keep in sync with the queryString set in #onCreate |
| adapter.setQueryString(getQueryString() == null ? "" : getQueryString()); |
| return adapter; |
| } |
| |
| protected void setupEmptyView() { |
| if (mEmptyView != null && getActivity() != null) { |
| final int imageResource; |
| final int actionLabelResource; |
| final int descriptionResource; |
| final OnEmptyViewActionButtonClickedListener listener; |
| if (!PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) { |
| imageResource = R.drawable.empty_contacts; |
| actionLabelResource = R.string.permission_single_turn_on; |
| descriptionResource = R.string.permission_no_search; |
| listener = this; |
| mPermissionToRequest = READ_CONTACTS; |
| } else { |
| imageResource = EmptyContentView.NO_IMAGE; |
| actionLabelResource = EmptyContentView.NO_LABEL; |
| descriptionResource = EmptyContentView.NO_LABEL; |
| listener = null; |
| mPermissionToRequest = null; |
| } |
| |
| mEmptyView.setImage(imageResource); |
| mEmptyView.setActionLabel(actionLabelResource); |
| mEmptyView.setDescription(descriptionResource); |
| if (listener != null) { |
| mEmptyView.setActionClickedListener(listener); |
| } |
| } |
| } |
| |
| @Override |
| public void onEmptyViewActionButtonClicked() { |
| final Activity activity = getActivity(); |
| if (activity == null) { |
| return; |
| } |
| |
| if (READ_CONTACTS.equals(mPermissionToRequest)) { |
| FragmentCompat.requestPermissions(this, new String[]{mPermissionToRequest}, |
| PERMISSION_REQUEST_CODE); |
| } |
| } |
| |
| |
| @Override |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| super.onItemClick(parent, view, position, id); |
| final int adapterPosition = position - getListView().getHeaderViewsCount(); |
| final SearchAdapter adapter = (SearchAdapter) getAdapter(); |
| final int shortcutType = adapter.getShortcutTypeFromPosition(adapterPosition); |
| final Long blockId = (Long) view.getTag(R.id.block_id); |
| final String number; |
| switch (shortcutType) { |
| case SearchAdapter.SHORTCUT_INVALID: |
| // Handles click on a search result, either contact or nearby places result. |
| number = adapter.getPhoneNumber(adapterPosition); |
| blockContactNumber(number, blockId); |
| break; |
| case SearchAdapter.SHORTCUT_BLOCK_NUMBER: |
| // Handles click on 'Block number' shortcut to add the user query as a number. |
| number = adapter.getQueryString(); |
| blockNumber(number); |
| break; |
| default: |
| Log.w(TAG, "Ignoring unsupported shortcut type: " + shortcutType); |
| break; |
| } |
| } |
| |
| private void blockNumber(final String number) { |
| final String countryIso = GeoUtil.getCurrentCountryIso(getContext()); |
| final OnCheckBlockedListener onCheckListener = new OnCheckBlockedListener() { |
| @Override |
| public void onCheckComplete(Long id) { |
| if (id == null) { |
| BlockNumberDialogFragment.show( |
| id, |
| number, |
| countryIso, |
| PhoneNumberUtils.formatNumber(number, countryIso), |
| R.id.blocked_numbers_activity_container, |
| getFragmentManager(), |
| SearchFragment.this); |
| } else { |
| Toast.makeText(getContext(), |
| ContactDisplayUtils.getTtsSpannedPhoneNumber(getResources(), |
| R.string.alreadyBlocked, number), |
| Toast.LENGTH_SHORT).show(); |
| } |
| } |
| }; |
| final boolean success = mFilteredNumberAsyncQueryHandler.isBlockedNumber( |
| onCheckListener, number, countryIso); |
| if (!success) { |
| Toast.makeText(getContext(), |
| ContactDisplayUtils.getTtsSpannedPhoneNumber( |
| getResources(), R.string.invalidNumber, number), |
| Toast.LENGTH_SHORT).show(); |
| } |
| } |
| |
| |
| @Override |
| protected void onItemClick(int position, long id) { |
| // Prevent super.onItemClicked(int position, long id) from being called. |
| } |
| |
| /** |
| * Updates the position and padding of the search fragment. |
| */ |
| public void updatePosition() { |
| int endTranslationValue = 0; |
| // Prevents ListView from being translated down after a rotation when the ActionBar is up. |
| if (mActivity.isActionBarShowing()) { |
| endTranslationValue = |
| mActivity.isDialpadShown() ? 0 : mActionBarHeight - mShadowHeight; |
| } |
| getView().setTranslationY(endTranslationValue); |
| resizeListView(); |
| |
| // There is padding which should only be applied when the dialpad is not shown. |
| int paddingTop = mActivity.isDialpadShown() ? 0 : mPaddingTop; |
| final ListView listView = getListView(); |
| listView.setPaddingRelative( |
| listView.getPaddingStart(), |
| paddingTop, |
| listView.getPaddingEnd(), |
| listView.getPaddingBottom()); |
| } |
| |
| public void resizeListView() { |
| if (mSpacer == null) { |
| return; |
| } |
| int spacerHeight = mActivity.isDialpadShown() ? mActivity.getDialpadHeight() : 0; |
| if (spacerHeight != mSpacer.getHeight()) { |
| final LinearLayout.LayoutParams lp = |
| (LinearLayout.LayoutParams) mSpacer.getLayoutParams(); |
| lp.height = spacerHeight; |
| mSpacer.setLayoutParams(lp); |
| } |
| } |
| |
| @Override |
| protected void startLoading() { |
| if (getActivity() == null) { |
| return; |
| } |
| |
| if (PermissionsUtil.hasContactsPermissions(getActivity())) { |
| super.startLoading(); |
| } else if (TextUtils.isEmpty(getQueryString())) { |
| // Clear out any existing call shortcuts. |
| final SearchAdapter adapter = (SearchAdapter) getAdapter(); |
| adapter.disableAllShortcuts(); |
| } else { |
| // The contact list is not going to change (we have no results since permissions are |
| // denied), but the shortcuts might because of the different query, so update the |
| // list. |
| getAdapter().notifyDataSetChanged(); |
| } |
| |
| setupEmptyView(); |
| } |
| |
| public void setOnTouchListener(View.OnTouchListener onTouchListener) { |
| mActivityOnTouchListener = onTouchListener; |
| } |
| |
| @Override |
| protected View inflateView(LayoutInflater inflater, ViewGroup container) { |
| final LinearLayout parent = (LinearLayout) super.inflateView(inflater, container); |
| final int orientation = getResources().getConfiguration().orientation; |
| if (orientation == Configuration.ORIENTATION_PORTRAIT) { |
| mSpacer = new Space(getActivity()); |
| parent.addView(mSpacer, |
| new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0)); |
| } |
| return parent; |
| } |
| |
| @Override |
| public void onRequestPermissionsResult(int requestCode, String[] permissions, |
| int[] grantResults) { |
| if (requestCode == PERMISSION_REQUEST_CODE) { |
| setupEmptyView(); |
| if (grantResults != null && grantResults.length == 1 |
| && PackageManager.PERMISSION_GRANTED == grantResults[0]) { |
| PermissionsUtil.notifyPermissionGranted(getActivity(), mPermissionToRequest); |
| } |
| } |
| } |
| |
| @Override |
| public void onFilterNumberSuccess() { |
| goBack(); |
| } |
| |
| @Override |
| public void onUnfilterNumberSuccess() { |
| Log.wtf(TAG, "Unblocked a number from the SearchFragment"); |
| goBack(); |
| } |
| |
| private void goBack() { |
| Activity activity = getActivity(); |
| if (activity == null) { |
| return; |
| } |
| activity.onBackPressed(); |
| } |
| |
| @Override |
| public void onChangeFilteredNumberUndo() { |
| getAdapter().notifyDataSetChanged(); |
| } |
| |
| private void blockContactNumber(final String number, final Long blockId) { |
| if (blockId != null) { |
| Toast.makeText(getContext(), ContactDisplayUtils.getTtsSpannedPhoneNumber( |
| getResources(), R.string.alreadyBlocked, number), |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| |
| BlockNumberDialogFragment.show( |
| blockId, |
| number, |
| GeoUtil.getCurrentCountryIso(getContext()), |
| number, |
| R.id.blocked_numbers_activity_container, |
| getFragmentManager(), |
| this); |
| } |
| } |