diff options
Diffstat (limited to 'java/com/android/contacts/common/list/PhoneNumberPickerFragment.java')
-rw-r--r-- | java/com/android/contacts/common/list/PhoneNumberPickerFragment.java | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/java/com/android/contacts/common/list/PhoneNumberPickerFragment.java b/java/com/android/contacts/common/list/PhoneNumberPickerFragment.java new file mode 100644 index 000000000..8f25f82a5 --- /dev/null +++ b/java/com/android/contacts/common/list/PhoneNumberPickerFragment.java @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2010 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.contacts.common.list; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.Loader; +import android.database.Cursor; +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.ArraySet; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import com.android.contacts.common.R; +import com.android.contacts.common.util.AccountFilterUtil; +import com.android.dialer.callcomposer.CallComposerContact; +import com.android.dialer.callintent.CallInitiationType; +import com.android.dialer.callintent.CallInitiationType.Type; +import com.android.dialer.callintent.CallSpecificAppData; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.enrichedcall.EnrichedCallComponent; +import com.android.dialer.enrichedcall.EnrichedCallManager; +import com.android.dialer.logging.Logger; +import com.android.dialer.protos.ProtoParsers; +import java.util.Set; +import org.json.JSONException; +import org.json.JSONObject; + +/** Fragment containing a phone number list for picking. */ +public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactEntryListAdapter> + implements PhoneNumberListAdapter.Listener, EnrichedCallManager.CapabilitiesListener { + + private static final String KEY_FILTER = "filter"; + private OnPhoneNumberPickerActionListener mListener; + private ContactListFilter mFilter; + private View mAccountFilterHeader; + /** + * Lives as ListView's header and is shown when {@link #mAccountFilterHeader} is set to View.GONE. + */ + private View mPaddingView; + /** true if the loader has started at least once. */ + private boolean mLoaderStarted; + + private boolean mUseCallableUri; + + private ContactListItemView.PhotoPosition mPhotoPosition = + ContactListItemView.getDefaultPhotoPosition(false /* normal/non opposite */); + + private final Set<OnLoadFinishedListener> mLoadFinishedListeners = new ArraySet<>(); + + private CursorReranker mCursorReranker; + + public PhoneNumberPickerFragment() { + setQuickContactEnabled(false); + setPhotoLoaderEnabled(true); + setSectionHeaderDisplayEnabled(true); + setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE); + + // Show nothing instead of letting caller Activity show something. + setHasOptionsMenu(true); + } + + /** + * Handles a click on the video call icon for a row in the list. + * + * @param position The position in the list where the click ocurred. + */ + @Override + public void onVideoCallIconClicked(int position) { + callNumber(position, true /* isVideoCall */); + } + + @Override + public void onCallAndShareIconClicked(int position) { + // Required because of cyclic dependencies of everything depending on contacts/common. + String componentName = "com.android.dialer.callcomposer.CallComposerActivity"; + Intent intent = new Intent(); + intent.setComponent(new ComponentName(getContext(), componentName)); + CallComposerContact contact = + ((PhoneNumberListAdapter) getAdapter()).getCallComposerContact(position); + ProtoParsers.put(intent, "CALL_COMPOSER_CONTACT", contact); + startActivity(intent); + } + + public void setDirectorySearchEnabled(boolean flag) { + setDirectorySearchMode( + flag ? DirectoryListLoader.SEARCH_MODE_DEFAULT : DirectoryListLoader.SEARCH_MODE_NONE); + } + + public void setOnPhoneNumberPickerActionListener(OnPhoneNumberPickerActionListener listener) { + this.mListener = listener; + } + + public OnPhoneNumberPickerActionListener getOnPhoneNumberPickerListener() { + return mListener; + } + + @Override + protected void onCreateView(LayoutInflater inflater, ViewGroup container) { + super.onCreateView(inflater, container); + + View paddingView = inflater.inflate(R.layout.contact_detail_list_padding, null, false); + mPaddingView = paddingView.findViewById(R.id.contact_detail_list_padding); + getListView().addHeaderView(paddingView); + + mAccountFilterHeader = getView().findViewById(R.id.account_filter_header_container); + updateFilterHeaderView(); + + setVisibleScrollbarEnabled(getVisibleScrollbarEnabled()); + } + + @Override + public void onPause() { + super.onPause(); + EnrichedCallComponent.get(getContext()) + .getEnrichedCallManager() + .unregisterCapabilitiesListener(this); + } + + @Override + public void onResume() { + super.onResume(); + EnrichedCallComponent.get(getContext()) + .getEnrichedCallManager() + .registerCapabilitiesListener(this); + } + + protected boolean getVisibleScrollbarEnabled() { + return true; + } + + @Override + protected void setSearchMode(boolean flag) { + super.setSearchMode(flag); + updateFilterHeaderView(); + } + + private void updateFilterHeaderView() { + final ContactListFilter filter = getFilter(); + if (mAccountFilterHeader == null || filter == null) { + return; + } + final boolean shouldShowHeader = + !isSearchMode() + && AccountFilterUtil.updateAccountFilterTitleForPhone( + mAccountFilterHeader, filter, false); + if (shouldShowHeader) { + mPaddingView.setVisibility(View.GONE); + mAccountFilterHeader.setVisibility(View.VISIBLE); + } else { + mPaddingView.setVisibility(View.VISIBLE); + mAccountFilterHeader.setVisibility(View.GONE); + } + } + + @Override + public void restoreSavedState(Bundle savedState) { + super.restoreSavedState(savedState); + + if (savedState == null) { + return; + } + + mFilter = savedState.getParcelable(KEY_FILTER); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(KEY_FILTER, mFilter); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == android.R.id.home) { // See ActionBar#setDisplayHomeAsUpEnabled() + if (mListener != null) { + mListener.onHomeInActionBarSelected(); + } + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onItemClick(int position, long id) { + callNumber(position, false /* isVideoCall */); + } + + /** + * Initiates a call to the number at the specified position. + * + * @param position The position. + * @param isVideoCall {@code true} if the call should be initiated as a video call, {@code false} + * otherwise. + */ + private void callNumber(int position, boolean isVideoCall) { + final String number = getPhoneNumber(position); + if (!TextUtils.isEmpty(number)) { + cacheContactInfo(position); + CallSpecificAppData callSpecificAppData = + CallSpecificAppData.newBuilder() + .setCallInitiationType(getCallInitiationType(true /* isRemoteDirectory */)) + .setPositionOfSelectedSearchResult(position) + .setCharactersInSearchString(getQueryString() == null ? 0 : getQueryString().length()) + .build(); + mListener.onPickPhoneNumber(number, isVideoCall, callSpecificAppData); + } else { + LogUtil.i( + "PhoneNumberPickerFragment.callNumber", + "item at %d was clicked before adapter is ready, ignoring", + position); + } + + // Get the lookup key and track any analytics + final String lookupKey = getLookupKey(position); + if (!TextUtils.isEmpty(lookupKey)) { + maybeTrackAnalytics(lookupKey); + } + } + + protected void cacheContactInfo(int position) { + // Not implemented. Hook for child classes + } + + protected String getPhoneNumber(int position) { + final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter(); + return adapter.getPhoneNumber(position); + } + + protected String getLookupKey(int position) { + final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter(); + return adapter.getLookupKey(position); + } + + @Override + protected void startLoading() { + mLoaderStarted = true; + super.startLoading(); + } + + @Override + @MainThread + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + Assert.isMainThread(); + // TODO: define and verify behavior for "Nearby places", corp directories, + // and dividers listed in UI between these categories + if (mCursorReranker != null + && data != null + && !data.isClosed() + && data.getCount() > 0 + && loader.getId() != -1) { // skip invalid directory ID of -1 + data = mCursorReranker.rerankCursor(data); + } + super.onLoadFinished(loader, data); + + // disable scroll bar if there is no data + setVisibleScrollbarEnabled(data != null && !data.isClosed() && data.getCount() > 0); + + if (data != null) { + notifyListeners(); + } + } + + /** Ranks cursor data rows and returns reference to new cursor object with reordered data. */ + public interface CursorReranker { + @MainThread + Cursor rerankCursor(Cursor data); + } + + @MainThread + public void setReranker(@Nullable CursorReranker reranker) { + Assert.isMainThread(); + mCursorReranker = reranker; + } + + /** Listener that is notified when cursor has finished loading data. */ + public interface OnLoadFinishedListener { + void onLoadFinished(); + } + + @MainThread + public void addOnLoadFinishedListener(OnLoadFinishedListener listener) { + Assert.isMainThread(); + mLoadFinishedListeners.add(listener); + } + + @MainThread + public void removeOnLoadFinishedListener(OnLoadFinishedListener listener) { + Assert.isMainThread(); + mLoadFinishedListeners.remove(listener); + } + + @MainThread + protected void notifyListeners() { + Assert.isMainThread(); + for (OnLoadFinishedListener listener : mLoadFinishedListeners) { + listener.onLoadFinished(); + } + } + + @Override + public void onCapabilitiesUpdated() { + if (getAdapter() != null) { + getAdapter().notifyDataSetChanged(); + } + } + + @MainThread + @Override + public void onDetach() { + Assert.isMainThread(); + mLoadFinishedListeners.clear(); + super.onDetach(); + } + + public void setUseCallableUri(boolean useCallableUri) { + mUseCallableUri = useCallableUri; + } + + public boolean usesCallableUri() { + return mUseCallableUri; + } + + @Override + protected ContactEntryListAdapter createListAdapter() { + PhoneNumberListAdapter adapter = new PhoneNumberListAdapter(getActivity()); + adapter.setDisplayPhotos(true); + adapter.setUseCallableUri(mUseCallableUri); + return adapter; + } + + @Override + protected void configureAdapter() { + super.configureAdapter(); + + final ContactEntryListAdapter adapter = getAdapter(); + if (adapter == null) { + return; + } + + if (!isSearchMode() && mFilter != null) { + adapter.setFilter(mFilter); + } + + setPhotoPosition(adapter); + } + + protected void setPhotoPosition(ContactEntryListAdapter adapter) { + ((PhoneNumberListAdapter) adapter).setPhotoPosition(mPhotoPosition); + } + + @Override + protected View inflateView(LayoutInflater inflater, ViewGroup container) { + return inflater.inflate(R.layout.contact_list_content, null); + } + + public ContactListFilter getFilter() { + return mFilter; + } + + public void setFilter(ContactListFilter filter) { + if ((mFilter == null && filter == null) || (mFilter != null && mFilter.equals(filter))) { + return; + } + + mFilter = filter; + if (mLoaderStarted) { + reloadData(); + } + updateFilterHeaderView(); + } + + public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) { + mPhotoPosition = photoPosition; + + final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter(); + if (adapter != null) { + adapter.setPhotoPosition(photoPosition); + } + } + + /** + * @param isRemoteDirectory {@code true} if the call was initiated using a contact/phone number + * not in the local contacts database + */ + protected CallInitiationType.Type getCallInitiationType(boolean isRemoteDirectory) { + return Type.UNKNOWN_INITIATION; + } + + /** + * Where a lookup key contains analytic event information, logs the associated analytics event. + * + * @param lookupKey The lookup key JSON object. + */ + private void maybeTrackAnalytics(String lookupKey) { + try { + JSONObject json = new JSONObject(lookupKey); + + String analyticsCategory = + json.getString(PhoneNumberListAdapter.PhoneQuery.ANALYTICS_CATEGORY); + String analyticsAction = json.getString(PhoneNumberListAdapter.PhoneQuery.ANALYTICS_ACTION); + String analyticsValue = json.getString(PhoneNumberListAdapter.PhoneQuery.ANALYTICS_VALUE); + + if (TextUtils.isEmpty(analyticsCategory) + || TextUtils.isEmpty(analyticsAction) + || TextUtils.isEmpty(analyticsValue)) { + return; + } + + // Assume that the analytic value being tracked could be a float value, but just cast + // to a long so that the analytic server can handle it. + long value; + try { + float floatValue = Float.parseFloat(analyticsValue); + value = (long) floatValue; + } catch (NumberFormatException nfe) { + return; + } + + Logger.get(getActivity()) + .sendHitEventAnalytics(analyticsCategory, analyticsAction, "" /* label */, value); + } catch (JSONException e) { + // Not an error; just a lookup key that doesn't have the right information. + } + } +} |