• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 package com.android.dialer.list;
17 
18 import static android.Manifest.permission.READ_CONTACTS;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorInflater;
22 import android.animation.AnimatorListenerAdapter;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.content.Intent;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.provider.ContactsContract;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.MotionEvent;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.animation.Interpolator;
38 import android.widget.AbsListView;
39 import android.widget.AbsListView.OnScrollListener;
40 import android.widget.LinearLayout;
41 import android.widget.ListView;
42 import android.widget.Space;
43 
44 import com.android.contacts.common.list.ContactEntryListAdapter;
45 import com.android.contacts.common.list.ContactListItemView;
46 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
47 import com.android.contacts.common.list.PhoneNumberPickerFragment;
48 import com.android.contacts.common.util.PermissionsUtil;
49 import com.android.contacts.common.util.ViewUtil;
50 import com.android.contacts.commonbind.analytics.AnalyticsUtil;
51 import com.android.dialer.dialpad.DialpadFragment.ErrorDialogFragment;
52 import com.android.dialer.R;
53 import com.android.dialer.util.DialerUtils;
54 import com.android.dialer.util.IntentUtil;
55 import com.android.dialer.widget.EmptyContentView;
56 import com.android.phone.common.animation.AnimUtils;
57 
58 public class SearchFragment extends PhoneNumberPickerFragment {
59     private static final String TAG  = SearchFragment.class.getSimpleName();
60 
61     private OnListFragmentScrolledListener mActivityScrollListener;
62     private View.OnTouchListener mActivityOnTouchListener;
63 
64     /*
65      * Stores the untouched user-entered string that is used to populate the add to contacts
66      * intent.
67      */
68     private String mAddToContactNumber;
69     private int mActionBarHeight;
70     private int mShadowHeight;
71     private int mPaddingTop;
72     private int mShowDialpadDuration;
73     private int mHideDialpadDuration;
74 
75     /**
76      * Used to resize the list view containing search results so that it fits the available space
77      * above the dialpad. Does not have a user-visible effect in regular touch usage (since the
78      * dialpad hides that portion of the ListView anyway), but improves usability in accessibility
79      * mode.
80      */
81     private Space mSpacer;
82 
83     private HostInterface mActivity;
84 
85     protected EmptyContentView mEmptyView;
86 
87     public interface HostInterface {
isActionBarShowing()88         public boolean isActionBarShowing();
isDialpadShown()89         public boolean isDialpadShown();
getDialpadHeight()90         public int getDialpadHeight();
getActionBarHideOffset()91         public int getActionBarHideOffset();
getActionBarHeight()92         public int getActionBarHeight();
93     }
94 
95     @Override
onAttach(Activity activity)96     public void onAttach(Activity activity) {
97         super.onAttach(activity);
98 
99         setQuickContactEnabled(true);
100         setAdjustSelectionBoundsEnabled(false);
101         setDarkTheme(false);
102         setPhotoPosition(ContactListItemView.getDefaultPhotoPosition(false /* opposite */));
103         setUseCallableUri(true);
104 
105         try {
106             mActivityScrollListener = (OnListFragmentScrolledListener) activity;
107         } catch (ClassCastException e) {
108             throw new ClassCastException(activity.toString()
109                     + " must implement OnListFragmentScrolledListener");
110         }
111     }
112 
113     @Override
onStart()114     public void onStart() {
115         super.onStart();
116         if (isSearchMode()) {
117             getAdapter().setHasHeader(0, false);
118         }
119 
120         mActivity = (HostInterface) getActivity();
121 
122         final Resources res = getResources();
123         mActionBarHeight = mActivity.getActionBarHeight();
124         mShadowHeight  = res.getDrawable(R.drawable.search_shadow).getIntrinsicHeight();
125         mPaddingTop = res.getDimensionPixelSize(R.dimen.search_list_padding_top);
126         mShowDialpadDuration = res.getInteger(R.integer.dialpad_slide_in_duration);
127         mHideDialpadDuration = res.getInteger(R.integer.dialpad_slide_out_duration);
128 
129         final View parentView = getView();
130 
131         final ListView listView = getListView();
132 
133         if (mEmptyView == null) {
134             mEmptyView = new EmptyContentView(getActivity());
135             ((ViewGroup) getListView().getParent()).addView(mEmptyView);
136             getListView().setEmptyView(mEmptyView);
137             setupEmptyView();
138         }
139 
140         listView.setBackgroundColor(res.getColor(R.color.background_dialer_results));
141         listView.setClipToPadding(false);
142         setVisibleScrollbarEnabled(false);
143         listView.setOnScrollListener(new OnScrollListener() {
144             @Override
145             public void onScrollStateChanged(AbsListView view, int scrollState) {
146                 mActivityScrollListener.onListFragmentScrollStateChange(scrollState);
147             }
148 
149             @Override
150             public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
151                     int totalItemCount) {
152             }
153         });
154         if (mActivityOnTouchListener != null) {
155             listView.setOnTouchListener(mActivityOnTouchListener);
156         }
157 
158         updatePosition(false /* animate */);
159     }
160 
161     @Override
onViewCreated(View view, Bundle savedInstanceState)162     public void onViewCreated(View view, Bundle savedInstanceState) {
163         super.onViewCreated(view, savedInstanceState);
164         ViewUtil.addBottomPaddingToListViewForFab(getListView(), getResources());
165     }
166 
167     @Override
onCreateAnimator(int transit, boolean enter, int nextAnim)168     public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
169         Animator animator = null;
170         if (nextAnim != 0) {
171             animator = AnimatorInflater.loadAnimator(getActivity(), nextAnim);
172         }
173         if (animator != null) {
174             final View view = getView();
175             final int oldLayerType = view.getLayerType();
176             view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
177             animator.addListener(new AnimatorListenerAdapter() {
178                 @Override
179                 public void onAnimationEnd(Animator animation) {
180                     view.setLayerType(oldLayerType, null);
181                 }
182             });
183         }
184         return animator;
185     }
186 
187     @Override
setSearchMode(boolean flag)188     protected void setSearchMode(boolean flag) {
189         super.setSearchMode(flag);
190         // This hides the "All contacts with phone numbers" header in the search fragment
191         final ContactEntryListAdapter adapter = getAdapter();
192         if (adapter != null) {
193             adapter.setHasHeader(0, false);
194         }
195     }
196 
setAddToContactNumber(String addToContactNumber)197     public void setAddToContactNumber(String addToContactNumber) {
198         mAddToContactNumber = addToContactNumber;
199     }
200 
201     /**
202      * Return true if phone number is prohibited by a value -
203      * (R.string.config_prohibited_phone_number_regexp) in the config files. False otherwise.
204      */
checkForProhibitedPhoneNumber(String number)205     public boolean checkForProhibitedPhoneNumber(String number) {
206         // Regular expression prohibiting manual phone call. Can be empty i.e. "no rule".
207         String prohibitedPhoneNumberRegexp = getResources().getString(
208             R.string.config_prohibited_phone_number_regexp);
209 
210         // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
211         // test equipment.
212         if (number != null
213                 && !TextUtils.isEmpty(prohibitedPhoneNumberRegexp)
214                 && number.matches(prohibitedPhoneNumberRegexp)) {
215             Log.d(TAG, "The phone number is prohibited explicitly by a rule.");
216             if (getActivity() != null) {
217                 DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
218                         R.string.dialog_phone_call_prohibited_message);
219                 dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
220             }
221 
222             return true;
223         }
224         return false;
225     }
226 
227     @Override
createListAdapter()228     protected ContactEntryListAdapter createListAdapter() {
229         DialerPhoneNumberListAdapter adapter = new DialerPhoneNumberListAdapter(getActivity());
230         adapter.setDisplayPhotos(true);
231         adapter.setUseCallableUri(super.usesCallableUri());
232         return adapter;
233     }
234 
235     @Override
onItemClick(int position, long id)236     protected void onItemClick(int position, long id) {
237         final DialerPhoneNumberListAdapter adapter = (DialerPhoneNumberListAdapter) getAdapter();
238         final int shortcutType = adapter.getShortcutTypeFromPosition(position);
239         final OnPhoneNumberPickerActionListener listener;
240         final Intent intent;
241         final String number;
242 
243         Log.i(TAG, "onItemClick: shortcutType=" + shortcutType);
244 
245         switch (shortcutType) {
246             case DialerPhoneNumberListAdapter.SHORTCUT_INVALID:
247                 super.onItemClick(position, id);
248                 break;
249             case DialerPhoneNumberListAdapter.SHORTCUT_DIRECT_CALL:
250                 number = adapter.getQueryString();
251                 listener = getOnPhoneNumberPickerListener();
252                 if (listener != null && !checkForProhibitedPhoneNumber(number)) {
253                     listener.onCallNumberDirectly(number);
254                 }
255                 break;
256             case DialerPhoneNumberListAdapter.SHORTCUT_CREATE_NEW_CONTACT:
257                 number = TextUtils.isEmpty(mAddToContactNumber) ?
258                         adapter.getFormattedQueryString() : mAddToContactNumber;
259                 intent = IntentUtil.getNewContactIntent(number);
260                 DialerUtils.startActivityWithErrorToast(getActivity(), intent);
261                 break;
262             case DialerPhoneNumberListAdapter.SHORTCUT_ADD_TO_EXISTING_CONTACT:
263                 number = TextUtils.isEmpty(mAddToContactNumber) ?
264                         adapter.getFormattedQueryString() : mAddToContactNumber;
265                 intent = IntentUtil.getAddToExistingContactIntent(number);
266                 DialerUtils.startActivityWithErrorToast(getActivity(), intent,
267                         R.string.add_contact_not_available);
268                 break;
269             case DialerPhoneNumberListAdapter.SHORTCUT_SEND_SMS_MESSAGE:
270                 number = adapter.getFormattedQueryString();
271                 intent = IntentUtil.getSendSmsIntent(number);
272                 DialerUtils.startActivityWithErrorToast(getActivity(), intent);
273                 break;
274             case DialerPhoneNumberListAdapter.SHORTCUT_MAKE_VIDEO_CALL:
275                 number = adapter.getQueryString();
276                 listener = getOnPhoneNumberPickerListener();
277                 if (listener != null && !checkForProhibitedPhoneNumber(number)) {
278                     listener.onCallNumberDirectly(number, true /* isVideoCall */);
279                 }
280                 break;
281         }
282     }
283 
284     /**
285      * Updates the position and padding of the search fragment, depending on whether the dialpad is
286      * shown. This can be optionally animated.
287      * @param animate
288      */
updatePosition(boolean animate)289     public void updatePosition(boolean animate) {
290         // Use negative shadow height instead of 0 to account for the 9-patch's shadow.
291         int startTranslationValue =
292                 mActivity.isDialpadShown() ? mActionBarHeight - mShadowHeight: -mShadowHeight;
293         int endTranslationValue = 0;
294         // Prevents ListView from being translated down after a rotation when the ActionBar is up.
295         if (animate || mActivity.isActionBarShowing()) {
296             endTranslationValue =
297                     mActivity.isDialpadShown() ? 0 : mActionBarHeight -mShadowHeight;
298         }
299         if (animate) {
300             // If the dialpad will be shown, then this animation involves sliding the list up.
301             final boolean slideUp = mActivity.isDialpadShown();
302 
303             Interpolator interpolator = slideUp ? AnimUtils.EASE_IN : AnimUtils.EASE_OUT ;
304             int duration = slideUp ? mShowDialpadDuration : mHideDialpadDuration;
305             getView().setTranslationY(startTranslationValue);
306             getView().animate()
307                     .translationY(endTranslationValue)
308                     .setInterpolator(interpolator)
309                     .setDuration(duration)
310                     .setListener(new AnimatorListenerAdapter() {
311                         @Override
312                         public void onAnimationStart(Animator animation) {
313                             if (!slideUp) {
314                                 resizeListView();
315                             }
316                         }
317 
318                         @Override
319                         public void onAnimationEnd(Animator animation) {
320                             if (slideUp) {
321                                 resizeListView();
322                             }
323                         }
324                     });
325 
326         } else {
327             getView().setTranslationY(endTranslationValue);
328             resizeListView();
329         }
330 
331         // There is padding which should only be applied when the dialpad is not shown.
332         int paddingTop = mActivity.isDialpadShown() ? 0 : mPaddingTop;
333         final ListView listView = getListView();
334         listView.setPaddingRelative(
335                 listView.getPaddingStart(),
336                 paddingTop,
337                 listView.getPaddingEnd(),
338                 listView.getPaddingBottom());
339     }
340 
resizeListView()341     public void resizeListView() {
342         if (mSpacer == null) {
343             return;
344         }
345         int spacerHeight = mActivity.isDialpadShown() ? mActivity.getDialpadHeight() : 0;
346         if (spacerHeight != mSpacer.getHeight()) {
347             final LinearLayout.LayoutParams lp =
348                     (LinearLayout.LayoutParams) mSpacer.getLayoutParams();
349             lp.height = spacerHeight;
350             mSpacer.setLayoutParams(lp);
351         }
352     }
353 
354     @Override
startLoading()355     protected void startLoading() {
356         if (PermissionsUtil.hasPermission(getActivity(), READ_CONTACTS)) {
357             super.startLoading();
358         } else if (TextUtils.isEmpty(getQueryString())) {
359             // Clear out any existing call shortcuts.
360             final DialerPhoneNumberListAdapter adapter =
361                     (DialerPhoneNumberListAdapter) getAdapter();
362             adapter.disableAllShortcuts();
363         } else {
364             // The contact list is not going to change (we have no results since permissions are
365             // denied), but the shortcuts might because of the different query, so update the
366             // list.
367             getAdapter().notifyDataSetChanged();
368         }
369 
370         setupEmptyView();
371     }
372 
setOnTouchListener(View.OnTouchListener onTouchListener)373     public void setOnTouchListener(View.OnTouchListener onTouchListener) {
374         mActivityOnTouchListener = onTouchListener;
375     }
376 
377     @Override
inflateView(LayoutInflater inflater, ViewGroup container)378     protected View inflateView(LayoutInflater inflater, ViewGroup container) {
379         final LinearLayout parent = (LinearLayout) super.inflateView(inflater, container);
380         final int orientation = getResources().getConfiguration().orientation;
381         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
382             mSpacer = new Space(getActivity());
383             parent.addView(mSpacer,
384                     new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0));
385         }
386         return parent;
387     }
388 
setupEmptyView()389     protected void setupEmptyView() {}
390 }
391