summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/app/list/SearchFragment.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/app/list/SearchFragment.java')
-rw-r--r--java/com/android/dialer/app/list/SearchFragment.java425
1 files changed, 425 insertions, 0 deletions
diff --git a/java/com/android/dialer/app/list/SearchFragment.java b/java/com/android/dialer/app/list/SearchFragment.java
new file mode 100644
index 000000000..4a7d48ae4
--- /dev/null
+++ b/java/com/android/dialer/app/list/SearchFragment.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2013 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.app.list;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.Space;
+import com.android.contacts.common.list.ContactEntryListAdapter;
+import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.common.list.PhoneNumberPickerFragment;
+import com.android.contacts.common.util.FabUtil;
+import com.android.dialer.animation.AnimUtils;
+import com.android.dialer.app.R;
+import com.android.dialer.app.dialpad.DialpadFragment.ErrorDialogFragment;
+import com.android.dialer.app.widget.DialpadSearchEmptyContentView;
+import com.android.dialer.app.widget.EmptyContentView;
+import com.android.dialer.callintent.nano.CallSpecificAppData;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.IntentUtil;
+import com.android.dialer.util.PermissionsUtil;
+
+public class SearchFragment extends PhoneNumberPickerFragment {
+
+ protected EmptyContentView mEmptyView;
+ private OnListFragmentScrolledListener mActivityScrollListener;
+ private View.OnTouchListener mActivityOnTouchListener;
+ /*
+ * Stores the untouched user-entered string that is used to populate the add to contacts
+ * intent.
+ */
+ private String mAddToContactNumber;
+ private int mActionBarHeight;
+ private int mShadowHeight;
+ private int mPaddingTop;
+ private int mShowDialpadDuration;
+ private int mHideDialpadDuration;
+ /**
+ * 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;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ setQuickContactEnabled(true);
+ setAdjustSelectionBoundsEnabled(false);
+ setDarkTheme(false);
+ setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(false /* opposite */));
+ setUseCallableUri(true);
+
+ try {
+ mActivityScrollListener = (OnListFragmentScrolledListener) activity;
+ } catch (ClassCastException e) {
+ LogUtil.v(
+ "SearchFragment.onAttach",
+ activity.toString()
+ + " doesn't implement OnListFragmentScrolledListener. "
+ + "Ignoring.");
+ }
+ }
+
+ @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);
+ mShowDialpadDuration = res.getInteger(R.integer.dialpad_slide_in_duration);
+ mHideDialpadDuration = res.getInteger(R.integer.dialpad_slide_out_duration);
+
+ final ListView listView = getListView();
+
+ if (mEmptyView == null) {
+ if (this instanceof SmartDialSearchFragment) {
+ mEmptyView = new DialpadSearchEmptyContentView(getActivity());
+ } else {
+ mEmptyView = new EmptyContentView(getActivity());
+ }
+ ((ViewGroup) getListView().getParent()).addView(mEmptyView);
+ getListView().setEmptyView(mEmptyView);
+ setupEmptyView();
+ }
+
+ listView.setBackgroundColor(res.getColor(R.color.background_dialer_results));
+ listView.setClipToPadding(false);
+ setVisibleScrollbarEnabled(false);
+
+ //Turn of 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) {
+ if (mActivityScrollListener != null) {
+ mActivityScrollListener.onListFragmentScrollStateChange(scrollState);
+ }
+ }
+
+ @Override
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}
+ });
+ if (mActivityOnTouchListener != null) {
+ listView.setOnTouchListener(mActivityOnTouchListener);
+ }
+
+ updatePosition(false /* animate */);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ FabUtil.addBottomPaddingToListViewForFab(getListView(), getResources());
+ }
+
+ @Override
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ Animator animator = null;
+ if (nextAnim != 0) {
+ animator = AnimatorInflater.loadAnimator(getActivity(), nextAnim);
+ }
+ if (animator != null) {
+ final View view = getView();
+ final int oldLayerType = view.getLayerType();
+ animator.addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setLayerType(oldLayerType, null);
+ }
+ });
+ }
+ return animator;
+ }
+
+ @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);
+ }
+ }
+
+ public void setAddToContactNumber(String addToContactNumber) {
+ mAddToContactNumber = addToContactNumber;
+ }
+
+ /**
+ * Return true if phone number is prohibited by a value -
+ * (R.string.config_prohibited_phone_number_regexp) in the config files. False otherwise.
+ */
+ public boolean checkForProhibitedPhoneNumber(String number) {
+ // Regular expression prohibiting manual phone call. Can be empty i.e. "no rule".
+ String prohibitedPhoneNumberRegexp =
+ getResources().getString(R.string.config_prohibited_phone_number_regexp);
+
+ // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
+ // test equipment.
+ if (number != null
+ && !TextUtils.isEmpty(prohibitedPhoneNumberRegexp)
+ && number.matches(prohibitedPhoneNumberRegexp)) {
+ LogUtil.i(
+ "SearchFragment.checkForProhibitedPhoneNumber",
+ "the phone number is prohibited explicitly by a rule");
+ if (getActivity() != null) {
+ DialogFragment dialogFragment =
+ ErrorDialogFragment.newInstance(R.string.dialog_phone_call_prohibited_message);
+ dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected ContactEntryListAdapter createListAdapter() {
+ DialerPhoneNumberListAdapter adapter = new DialerPhoneNumberListAdapter(getActivity());
+ adapter.setDisplayPhotos(true);
+ adapter.setUseCallableUri(super.usesCallableUri());
+ adapter.setListener(this);
+ return adapter;
+ }
+
+ @Override
+ protected void onItemClick(int position, long id) {
+ final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter();
+ final int shortcutType = adapter.getShortcutTypeFromPosition(position);
+ final OnPhoneNumberPickerActionListener listener;
+ final Intent intent;
+ final String number;
+
+ LogUtil.i("SearchFragment.onItemClick", "shortcutType: " + shortcutType);
+
+ switch (shortcutType) {
+ case DialerPhoneNumberListAdapter.SHORTCUT_INVALID:
+ super.onItemClick(position, id);
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_DIRECT_CALL:
+ number = adapter.getQueryString();
+ listener = getOnPhoneNumberPickerListener();
+ if (listener != null && !checkForProhibitedPhoneNumber(number)) {
+ CallSpecificAppData callSpecificAppData = new CallSpecificAppData();
+ callSpecificAppData.callInitiationType =
+ getCallInitiationType(false /* isRemoteDirectory */);
+ callSpecificAppData.positionOfSelectedSearchResult = position;
+ callSpecificAppData.charactersInSearchString =
+ getQueryString() == null ? 0 : getQueryString().length();
+ listener.onPickPhoneNumber(number, false /* isVideoCall */, callSpecificAppData);
+ }
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_CREATE_NEW_CONTACT:
+ number =
+ TextUtils.isEmpty(mAddToContactNumber)
+ ? adapter.getFormattedQueryString()
+ : mAddToContactNumber;
+ intent = IntentUtil.getNewContactIntent(number);
+ DialerUtils.startActivityWithErrorToast(getActivity(), intent);
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_ADD_TO_EXISTING_CONTACT:
+ number =
+ TextUtils.isEmpty(mAddToContactNumber)
+ ? adapter.getFormattedQueryString()
+ : mAddToContactNumber;
+ intent = IntentUtil.getAddToExistingContactIntent(number);
+ DialerUtils.startActivityWithErrorToast(
+ getActivity(), intent, R.string.add_contact_not_available);
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_SEND_SMS_MESSAGE:
+ number = adapter.getFormattedQueryString();
+ intent = IntentUtil.getSendSmsIntent(number);
+ DialerUtils.startActivityWithErrorToast(getActivity(), intent);
+ break;
+ case DialerPhoneNumberListAdapter.SHORTCUT_MAKE_VIDEO_CALL:
+ number =
+ TextUtils.isEmpty(mAddToContactNumber) ? adapter.getQueryString() : mAddToContactNumber;
+ listener = getOnPhoneNumberPickerListener();
+ if (listener != null && !checkForProhibitedPhoneNumber(number)) {
+ CallSpecificAppData callSpecificAppData = new CallSpecificAppData();
+ callSpecificAppData.callInitiationType =
+ getCallInitiationType(false /* isRemoteDirectory */);
+ callSpecificAppData.positionOfSelectedSearchResult = position;
+ callSpecificAppData.charactersInSearchString =
+ getQueryString() == null ? 0 : getQueryString().length();
+ listener.onPickPhoneNumber(number, true /* isVideoCall */, callSpecificAppData);
+ }
+ break;
+ }
+ }
+
+ /**
+ * Updates the position and padding of the search fragment, depending on whether the dialpad is
+ * shown. This can be optionally animated.
+ */
+ public void updatePosition(boolean animate) {
+ if (mActivity == null) {
+ // Activity will be set in onStart, and this method will be called again
+ return;
+ }
+
+ // Use negative shadow height instead of 0 to account for the 9-patch's shadow.
+ int startTranslationValue =
+ mActivity.isDialpadShown() ? mActionBarHeight - mShadowHeight : -mShadowHeight;
+ int endTranslationValue = 0;
+ // Prevents ListView from being translated down after a rotation when the ActionBar is up.
+ if (animate || mActivity.isActionBarShowing()) {
+ endTranslationValue = mActivity.isDialpadShown() ? 0 : mActionBarHeight - mShadowHeight;
+ }
+ if (animate) {
+ // If the dialpad will be shown, then this animation involves sliding the list up.
+ final boolean slideUp = mActivity.isDialpadShown();
+
+ Interpolator interpolator = slideUp ? AnimUtils.EASE_IN : AnimUtils.EASE_OUT;
+ int duration = slideUp ? mShowDialpadDuration : mHideDialpadDuration;
+ getView().setTranslationY(startTranslationValue);
+ getView()
+ .animate()
+ .translationY(endTranslationValue)
+ .setInterpolator(interpolator)
+ .setDuration(duration)
+ .setListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (!slideUp) {
+ resizeListView();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (slideUp) {
+ resizeListView();
+ }
+ }
+ });
+
+ } else {
+ 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 DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) 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;
+ }
+
+ protected void setupEmptyView() {}
+
+ public interface HostInterface {
+
+ boolean isActionBarShowing();
+
+ boolean isDialpadShown();
+
+ int getDialpadHeight();
+
+ int getActionBarHideOffset();
+
+ int getActionBarHeight();
+ }
+}