MTK的Dialer模块联系人搜索
拨号搜索机制分为两个部分:引导搜索和搜索。其中引导搜索是指,从用户输入到开始搜索之间的流程,而搜索部分是指,从数据库搜索字符串的过程。
一、引导搜索部分
默认的拨号界面的布局从上到下主要分为3个部分:显示列表、数字编辑框、拨号键盘。他们的作用是:用户直接在拨号键盘上输入数字,然后数字编辑框显示所输入的数字,同时在显示列表中体现此时的搜索结果。如图所示:
拨号界面布局
从流程上来讲,需要拨号键盘将用户点击转换为按键事件并传递给编辑框,然后由编辑框传递给搜索框,再由搜索框传递给列表Fragment,然后在列表所加载的Adapter中体现当前的搜索结果。
搜索流程框图
1.1、从拨号键盘到编辑框
用户在拨号键盘上的点击的数字按钮,都会在编辑框中体现出来,我们先来追踪这一过程。每个拨号键盘按钮都是DialpadKeyButton类型的View,他们继承自FrameLayout,当遇到点击事件时,就会触发configureKeypadListeners()方法,在DialpadFragment.Java中
- DialpadFragment.java
DialpadFragment.java
- private void configureKeypadListeners(View fragmentView) {
- final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
- R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};
- View dialpadKey;
- for (int i = 0; i < buttonIds.length; i++) {
- dialpadKey = fragmentView.findViewById(buttonIds[i]);
- dialpadKey.setOnClickListener(this);
- }
- // Long-pressing one button will initiate Voicemail.
- final View one = fragmentView.findViewById(R.id.one);
- one.setOnLongClickListener(this);
- // Long-pressing zero button will enter ’+’ instead.
- final View zero = fragmentView.findViewById(R.id.zero);
- zero.setOnLongClickListener(this);
- // Long-pressing one button will initiate Voicemail.
- final View start = fragmentView.findViewById(R.id.star);
- start.setOnLongClickListener(this);
- // Long-pressing zero button will enter ’+’ instead.
- final View pound = fragmentView.findViewById(R.id.pound);
- pound.setOnLongClickListener(this);
- }
private void configureKeypadListeners(View fragmentView) {
final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};
View dialpadKey;
for (int i = 0; i < buttonIds.length; i++) {
dialpadKey = fragmentView.findViewById(buttonIds[i]);
dialpadKey.setOnClickListener(this);
}
// Long-pressing one button will initiate Voicemail.
final View one = fragmentView.findViewById(R.id.one);
one.setOnLongClickListener(this);
// Long-pressing zero button will enter '+' instead.
final View zero = fragmentView.findViewById(R.id.zero);
zero.setOnLongClickListener(this);
// Long-pressing one button will initiate Voicemail.
final View start = fragmentView.findViewById(R.id.star);
start.setOnLongClickListener(this);
// Long-pressing zero button will enter '+' instead.
final View pound = fragmentView.findViewById(R.id.pound);
pound.setOnLongClickListener(this);
}
configureKeypadListeners()方法中,设置了dialpadKey点击事件的监听:dialpadKey.setOnClickListener(this);然后在DialpadFragment的onClick()方法中,将当前的点击事件转换为标准的按键输入:
- @Override DialpadFragment.java
- public void onClick(View view) {
- /** M: Prevent the event if dialpad is not shown. @{ */
- if (getActivity() != null
- && !((DialtactsActivity)getActivity()).isDialpadShown()) {
- Log.d(TAG, ”onClick but dialpad is not shown, skip !!!”);
- return;
- }
- /** @} */
- switch (view.getId()) {
- case R.id.dialpad_floating_action_button:
- mHaptic.vibrate();
- handleDialButtonPressed();
- break;
- case R.id.deleteButton: {
- keyPressed(KeyEvent.KEYCODE_DEL);
- break;
- }
- case R.id.digits: {
- if (!isDigitsEmpty()) {
- mDigits.setCursorVisible(true);
- }
- break;
- }
- case R.id.dialpad_overflow: {
- /// M: for plug-in @{
- ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(
- mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());
- /// @}
- mOverflowPopupMenu.show();
- break;
- }
- //Added by duyuanfeng for Lenovo dialpad
- case R.id.one: {
- keyPressed(KeyEvent.KEYCODE_1);
- break;
- }
- case R.id.two: {
- keyPressed(KeyEvent.KEYCODE_2);
- break;
- }
- // ……
- case R.id.star: {
- keyPressed(KeyEvent.KEYCODE_STAR);
- break;
- }
- //End addition
- default: {
- Log.wtf(TAG, ”Unexpected onClick() event from: ” + view);
- return;
- }
- }
- }
@Override DialpadFragment.java
public void onClick(View view) {
/** M: Prevent the event if dialpad is not shown. @{ */
if (getActivity() != null
&& !((DialtactsActivity)getActivity()).isDialpadShown()) {
Log.d(TAG, "onClick but dialpad is not shown, skip !!!");
return;
}
/** @} */
switch (view.getId()) {
case R.id.dialpad_floating_action_button:
mHaptic.vibrate();
handleDialButtonPressed();
break;
case R.id.deleteButton: {
keyPressed(KeyEvent.KEYCODE_DEL);
break;
}
case R.id.digits: {
if (!isDigitsEmpty()) {
mDigits.setCursorVisible(true);
}
break;
}
case R.id.dialpad_overflow: {
/// M: for plug-in @{
ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(
mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());
/// @}
mOverflowPopupMenu.show();
break;
}
//Added by duyuanfeng for Lenovo dialpad
case R.id.one: {
keyPressed(KeyEvent.KEYCODE_1);
break;
}
case R.id.two: {
keyPressed(KeyEvent.KEYCODE_2);
break;
}
// ......
case R.id.star: {
keyPressed(KeyEvent.KEYCODE_STAR);
break;
}
//End addition
default: {
Log.wtf(TAG, "Unexpected onClick() event from: " + view);
return;
}
}
}
这里看到,当我们在拨号键盘上点击某个View时,将会通过onClick()转换为标准的键盘消息,比如,在R.id.one控件上的点击,将会转换为KeyEvent.KEYCODE_1消息。然后在keyPressed()中将会把当前输入传递给编辑框:
- DialpadFragment.java
DialpadFragment.java
- private void keyPressed(int keyCode) {
- if (getView() == null || getView().getTranslationY() != 0) {
- return;
- }
- mHaptic.vibrate();
- KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
- mDigits.onKeyDown(keyCode, event);
- // If the cursor is at the end of the text we hide it.
- final int length = mDigits.length();
- if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
- mDigits.setCursorVisible(false);
- }
- if(length >=128)//songhu add for cu320
- clearDialpad();
- }
private void keyPressed(int keyCode) {
if (getView() == null || getView().getTranslationY() != 0) {
return;
}
mHaptic.vibrate();
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mDigits.onKeyDown(keyCode, event);
// If the cursor is at the end of the text we hide it.
final int length = mDigits.length();
if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
mDigits.setCursorVisible(false);
}
if(length >=128)//songhu add for cu320
clearDialpad();
}
mDigits.onKeyDown(keyCode, event)将内容传递给编辑框控件,mDigits就是编辑框控件。
1.2、从编辑框到搜索框
搜索框的作用主要是,当拨号键盘隐藏时,显示当前的输入内容。而编辑框需要将当前的输入传递给搜索框。当编辑框检测到KeyDown事件后,就会将当前键盘的输入放入编辑框中,并触发TextWatcher的相关方法:
- DialpadFragment.java
DialpadFragment.java
- public void afterTextChanged(Editable input) {
- .
- if (!mDigitsFilledByIntent &&
- SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
- mDigits.getText().clear();
- }
- if (isDigitsEmpty()) {
- mDigitsFilledByIntent = false;
- mDigits.setCursorVisible(false);
- }
- if (mDialpadQueryListener != null) {
- //传递给mDialpadQueryListener
- mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
- }
- updateDeleteButtonEnabledState();
- }
public void afterTextChanged(Editable input) {
.
if (!mDigitsFilledByIntent &&
SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
mDigits.getText().clear();
}
if (isDigitsEmpty()) {
mDigitsFilledByIntent = false;
mDigits.setCursorVisible(false);
}
if (mDialpadQueryListener != null) {
//传递给mDialpadQueryListener
mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
}
updateDeleteButtonEnabledState();
}
在这里,又将当前已经输入的文本传递给mDialpadQueryListener,它是在DialtactsActivity.java中实现的
- DialtactsActivity.java
DialtactsActivity.java
- public void onDialpadQueryChanged(String query) {
- if (mSmartDialSearchFragment != null) {
- mSmartDialSearchFragment.setAddToContactNumber(query);
- }
- final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
- /* M: [MTK Dialer Search] use mtk enhance dialpad map */
- DialerFeatureOptions.isDialerSearchEnabled() ?
- SmartDialNameMatcher.SMART_DIALPAD_MAP
- : SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
- if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
- if (DEBUG) {
- Log.d(TAG, ”onDialpadQueryChanged - new query: ” + query);
- }
- if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
- if (!TextUtils.isEmpty(normalizedQuery)) {
- mPendingSearchViewQuery = normalizedQuery;
- }
- return;
- }
- //传递给搜索框
- mSearchView.setText(normalizedQuery);
- }
- }
public void onDialpadQueryChanged(String query) {
if (mSmartDialSearchFragment != null) {
mSmartDialSearchFragment.setAddToContactNumber(query);
}
final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
/* M: [MTK Dialer Search] use mtk enhance dialpad map */
DialerFeatureOptions.isDialerSearchEnabled() ?
SmartDialNameMatcher.SMART_DIALPAD_MAP
: SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
if (DEBUG) {
Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
}
if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
if (!TextUtils.isEmpty(normalizedQuery)) {
mPendingSearchViewQuery = normalizedQuery;
}
return;
}
//传递给搜索框
mSearchView.setText(normalizedQuery);
}
}
在onDialpadQueryChanged()中将当前编辑框的内容通过setText()方法传递给了mSearchView,也就是最上方的搜索框。
1.3、从搜索框到搜索结果列表Fragment
搜索框下面的列表用于在搜索时显示搜索结果,他所处的位置是复用的,可以选择性的加载三种Fragment,当处于非搜索状态时,加载PhoneFavoriteFragment,这是进入拨号界面的默认加载项,将会显示瓦片式收藏界面,当在搜索模式时,将会加载SmartDialSearchFragment(拨号搜索,在拨号盘里输入号码呈现结果集的fragment)或者RegularSearchFragment(全局搜索,在actionbar的edittext里输入号码呈现结果集的fragment)用于显示当时的搜索结果。对于最常用的用户在拨号键盘输入内容触发的搜索,将会加载SmartDialSearchFragment。此时搜索框需要将要搜索的文本传递给SmartDialSearchFragment。
在搜索时,由于搜索框注册了文本监听器,所以将会触发TextWatcher,此时需要暂存当前要搜索的文本,并进入搜索模式,然后再将搜索内容交给SmartDialSearchFragment。
- @DialtactsActivity.java
@DialtactsActivity.java
- /**
- * Listener used to send search queries to the phone search fragment.
- */
- 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) {
- final String newText = s.toString();
- if (newText.equals(mSearchQuery)) {
- // If the query hasn’t changed (perhaps due to activity being destroyed
- // and restored, or user launching the same DIAL intent twice), then there is
- // no need to do anything here.
- return;
- }
- if (DEBUG) {
- Log.d(TAG, ”onTextChange for mSearchView called with new query: ” + newText);
- Log.d(TAG, ”Previous Query: ” + mSearchQuery);
- }
- mSearchQuery = newText;
- // 当搜索的字符串为变成不为空的时候显示搜索界面
- if (!TextUtils.isEmpty(newText)) {
- // Call enterSearchUi only if we are switching search modes, or showing a search
- // fragment for the first time.
- final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
- (!mIsDialpadShown && mInRegularSearch);
- if (!sameSearchMode) {
- enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
- }
- }
- //选择不同的搜索模式
- if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
- mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
- } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
- mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
- }
- }
- @Override
- public void afterTextChanged(Editable s) {
- }
- };
/**
* Listener used to send search queries to the phone search fragment.
*/
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) {
final String newText = s.toString();
if (newText.equals(mSearchQuery)) {
// If the query hasn't changed (perhaps due to activity being destroyed
// and restored, or user launching the same DIAL intent twice), then there is
// no need to do anything here.
return;
}
if (DEBUG) {
Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
Log.d(TAG, "Previous Query: " + mSearchQuery);
}
mSearchQuery = newText;
// 当搜索的字符串为变成不为空的时候显示搜索界面
if (!TextUtils.isEmpty(newText)) {
// Call enterSearchUi only if we are switching search modes, or showing a search
// fragment for the first time.
final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
(!mIsDialpadShown && mInRegularSearch);
if (!sameSearchMode) {
enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
}
}
//选择不同的搜索模式
if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
} else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
}
}
@Override
public void afterTextChanged(Editable s) {
}
};
在这里从搜索框进入到不同的SearchFragment,并将文本传递给SearchFragment;
1.4、从搜索列表的Fragment到Adapter
—-SearchFragment
—-PhoneNumberPickerFragment
—-ContactEntryListFragment<ContactEntryListAdapter>
—-Fragment
- ContactEntryListFragment.java
ContactEntryListFragment.java
- public void setQueryString(String queryString, boolean delaySelection) {
- if (!TextUtils.equals(mQueryString, queryString)) {
- if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {
- if (TextUtils.isEmpty(mQueryString)) {
- // Restore the adapter if the query used to be empty.
- mListView.setAdapter(mAdapter);
- } else if (TextUtils.isEmpty(queryString)) {
- // Instantly clear the list view if the new query is empty.
- mListView.setAdapter(null);
- }
- }
- mQueryString = queryString;
- setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
- if (mAdapter != null) {
- //传递给Adapter
- mAdapter.setQueryString(queryString);
- //触发Adapter重新搜索
- reloadData();
- }
- }
- }
public void setQueryString(String queryString, boolean delaySelection) {
if (!TextUtils.equals(mQueryString, queryString)) {
if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {
if (TextUtils.isEmpty(mQueryString)) {
// Restore the adapter if the query used to be empty.
mListView.setAdapter(mAdapter);
} else if (TextUtils.isEmpty(queryString)) {
// Instantly clear the list view if the new query is empty.
mListView.setAdapter(null);
}
}
mQueryString = queryString;
setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
if (mAdapter != null) {
//传递给Adapter
mAdapter.setQueryString(queryString);
//触发Adapter重新搜索
reloadData();
}
}
}
在这里,Fragment将要搜索的文本通过setQueryString()的方法传递给当前的Adapter,然后通过reloadData()方法触发Adapter的搜索机制。那么这里的Adapter具体是指哪个呢?
我们在SmartDialSearchFragment中找到了该Adapter的创建之处,他就是SmartDialNumberListAdapter:
- SmartDialSearchFragment.java
SmartDialSearchFragment.java
- @Override
- protected ContactEntryListAdapter createListAdapter() {
- SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());
- adapter.setUseCallableUri(super.usesCallableUri());
- adapter.setQuickContactEnabled(true);
- // Set adapter’s query string to restore previous instance state.
- adapter.setQueryString(getQueryString());
- return adapter;
- }
@Override
protected ContactEntryListAdapter createListAdapter() {
SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());
adapter.setUseCallableUri(super.usesCallableUri());
adapter.setQuickContactEnabled(true);
// Set adapter's query string to restore previous instance state.
adapter.setQueryString(getQueryString());
return adapter;
}
该Adapter的继承关系如下:
SmartDialNumberListAdapter
—-DialerPhoneNumberListAdapter
—-PhoneNumberListAdapter
—-ContactEntryListAdapter
—-IndexerListAdapter
—-PinnedHeaderListAdapter
—-CompositeCursorAdapter
接下来我们分析如何通过Fragment的reloadData()触发Adapter的搜索。
1.5、Adapter触发搜索机制
刚才介绍到,SmartDialSearchFragment在setQueryString()时,通过reloadData()触发了Adapter的搜索,我们来看一下这个流程:- ContactEntryListFragment.java
ContactEntryListFragment.java
- protected void reloadData() {
- removePendingDirectorySearchRequests();
- mAdapter.onDataReload();
- mLoadPriorityDirectoriesOnly = true;
- mForceLoad = true;
- //触发新的Adapter
- startLoading();
- }
- protected void startLoading() {
- Log.d(TAG, ”startLoading”);
- if (mAdapter == null) {
- // The method was called before the fragment was started
- Log.d(TAG, ”[statLoading] mAdapter is null”);
- return;
- }
- //配置Adapter要搜索的文本
- configureAdapter();
- int partitionCount = mAdapter.getPartitionCount();
- for (int i = 0; i < partitionCount; i++) {
- Partition partition = mAdapter.getPartition(i);
- if (partition instanceof DirectoryPartition) {
- DirectoryPartition directoryPartition = (DirectoryPartition)partition;
- if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
- if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
- startLoadingDirectoryPartition(i);
- }
- }
- } else {
- //通过LoaderManager进行异步查询
- getLoaderManager().initLoader(i, null, this);
- }
- }
- // Next time this method is called, we should start loading non-priority directories
- mLoadPriorityDirectoriesOnly = false;
- }
protected void reloadData() {
removePendingDirectorySearchRequests();
mAdapter.onDataReload();
mLoadPriorityDirectoriesOnly = true;
mForceLoad = true;
//触发新的Adapter
startLoading();
}
protected void startLoading() {
Log.d(TAG, "startLoading");
if (mAdapter == null) {
// The method was called before the fragment was started
Log.d(TAG, "[statLoading] mAdapter is null");
return;
}
//配置Adapter要搜索的文本
configureAdapter();
int partitionCount = mAdapter.getPartitionCount();
for (int i = 0; i < partitionCount; i++) {
Partition partition = mAdapter.getPartition(i);
if (partition instanceof DirectoryPartition) {
DirectoryPartition directoryPartition = (DirectoryPartition)partition;
if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
startLoadingDirectoryPartition(i);
}
}
} else {
//通过LoaderManager进行异步查询
getLoaderManager().initLoader(i, null, this);
}
}
// Next time this method is called, we should start loading non-priority directories
mLoadPriorityDirectoriesOnly = false;
}
在startLoading()时,通过configureAdapter()对当前的Adapter配置了要搜索的文本、排序方法以及显示主题等信息,由于
- *partition instanceof DirectoryPartition = true
*partition instanceof DirectoryPartition = true
- ContactEntryListFragment.java
- private void startLoadingDirectoryPartition(int partitionIndex) {
- DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
- partition.setStatus(DirectoryPartition.STATUS_LOADING);
- long directoryId = partition.getDirectoryId();
- if (mForceLoad) {
- if (directoryId == Directory.DEFAULT) {
- loadDirectoryPartition(partitionIndex, partition);
- } else {
- loadDirectoryPartitionDelayed(partitionIndex, partition);
- }
- } else {
- Bundle args = new Bundle();
- args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
- getLoaderManager().initLoader(partitionIndex, args, this);
- }
- }
ContactEntryListFragment.java
private void startLoadingDirectoryPartition(int partitionIndex) {
DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
partition.setStatus(DirectoryPartition.STATUS_LOADING);
long directoryId = partition.getDirectoryId();
if (mForceLoad) {
if (directoryId == Directory.DEFAULT) {
loadDirectoryPartition(partitionIndex, partition);
} else {
loadDirectoryPartitionDelayed(partitionIndex, partition);
}
} else {
Bundle args = new Bundle();
args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
getLoaderManager().initLoader(partitionIndex, args, this);
}
}
然后就通过LoaderManager进行异步查询。我们来看Loader的流程: 经过initLoader()的操作之后,就会触发SmartDialSearchFragment中的onCreateLoader()方法:
- SmartDialSearchFragments.java
SmartDialSearchFragments.java
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- // Smart dialing does not support Directory Load, falls back to normal search instead.
- if (id == getDirectoryLoaderId()) {
- return super.onCreateLoader(id, args);
- } else {
- final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();
- /// M: [MTK Dialer Search] @{
- if (DialerFeatureOptions.isDialerSearchEnabled()) {
- DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(),
- usesCallableUri());
- adapter.configureLoader(loader);
- return loader;
- /// @}
- } else {
- //创建当前的CursorLoader,也就是SmartDialCursorLoader
- SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());
- adapter.configureLoader(loader);
- return loader;
- }
- }
- }
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Smart dialing does not support Directory Load, falls back to normal search instead.
if (id == getDirectoryLoaderId()) {
return super.onCreateLoader(id, args);
} else {
final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();
/// M: [MTK Dialer Search] @{
if (DialerFeatureOptions.isDialerSearchEnabled()) {
DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(),
usesCallableUri());
adapter.configureLoader(loader);
return loader;
/// @}
} else {
//创建当前的CursorLoader,也就是SmartDialCursorLoader
SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());
adapter.configureLoader(loader);
return loader;
}
}
}
由于DialerFeatureOptions.isDialerSearchEnabled()为true,因此这里创建了DialerSearchCursorLoader作为当前的CursorLoader。然后通过adapter的configureLoader()方法将该Loader传递给SmartDialNumberListAdapter,接下来就会在DialerSearchCursorLoader中完成异步查询,现在我们看一下在DialerSearchCursorLoader中的查询流程:
- DialerSearchCursouLoader.java
- /**
- * Configures the query string to be used to find SmartDial matches.
- * @param query The query string user typed.
- */
- public void configureQuery(String query, boolean isSmartQuery) {
- Log.d(TAG, ”MTK-DialerSearch, Configure new query to be ” + query);
- mQuery = query;
- //搜索模式 isSmartQuery
- if (!isSmartQuery) {
- mQuery = DialerSearchUtils.stripTeleSeparators(query);
- }
- //判断字符串是否合法
- if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {
- mEnableDefaultSearch = true;
- }
- }
- /**
- * Queries the Contacts database and loads results in background.
- * @return Cursor of contacts that matches the SmartDial query.
- */
- @Override
- public Cursor loadInBackground() {
- Log.d(TAG, ”MTK-DialerSearch, Load in background. mQuery: ” + mQuery);
- final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);
- Cursor cursor = null;
- if (mEnableDefaultSearch) {
- cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);
- } else {
- cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);
- }
- if (cursor != null) {
- Log.d(TAG, ”MTK-DialerSearch, loadInBackground, result.getCount: ”
- + cursor.getCount());
- return cursor;
- } else {
- Log.w(TAG, ”MTK-DialerSearch, —-cursor is null—-“);
- return null;
- }
- }
DialerSearchCursouLoader.java
/**
* Configures the query string to be used to find SmartDial matches.
* @param query The query string user typed.
*/
public void configureQuery(String query, boolean isSmartQuery) {
Log.d(TAG, "MTK-DialerSearch, Configure new query to be " + query);
mQuery = query;
//搜索模式 isSmartQuery
if (!isSmartQuery) {
mQuery = DialerSearchUtils.stripTeleSeparators(query);
}
//判断字符串是否合法
if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {
mEnableDefaultSearch = true;
}
}
/**
* Queries the Contacts database and loads results in background.
* @return Cursor of contacts that matches the SmartDial query.
*/
@Override
public Cursor loadInBackground() {
Log.d(TAG, "MTK-DialerSearch, Load in background. mQuery: " + mQuery);
final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);
Cursor cursor = null;
if (mEnableDefaultSearch) {
cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);
} else {
cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);
}
if (cursor != null) {
Log.d(TAG, "MTK-DialerSearch, loadInBackground, result.getCount: "
+ cursor.getCount());
return cursor;
} else {
Log.w(TAG, "MTK-DialerSearch, ----cursor is null----");
return null;
}
}
- @DialerSearchHelper.java
- /**
- * Query dialerSearch results from contactsProvider, use MTK algorithm.
- * @param query
- * @return DialerSearch result.
- */
- public Cursor getSmartDialerSearchResults(String query) {
- Log.d(TAG, ”MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: ” + query);
- if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr
- return null;
- }
- final ContentResolver resolver = mContext.getContentResolver();
- Cursor cursor = null;
- try {
- int displayOrder = sContactsPrefs.getDisplayOrder();
- int sortOrder = sContactsPrefs.getSortOrder();
- //设置Uri的路径
- Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, ”dialer_search”);
- //设置Uri的搜索文本
- Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();
- Log.d(TAG, ”MTK-DialerSearch, displayOrder: ” + displayOrder + “ ,sortOrder: ”
- + sortOrder);
- //在Uri的path中加入两个键值对,并根据参数查询字符串
- Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(
- ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))
- .appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,
- String.valueOf(sortOrder)).build();
- cursor = resolver.query(dialerSearchParamUri, null, null, null, null);
- Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + String.valueOf(displayOrder)+“SORT_ORDER =”+String.valueOf(sortOrder));
- Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + ContactsContract.Preferences.DISPLAY_ORDER+“SORT_ORDER =”+ContactsContract.Preferences.SORT_ORDER);
- Log.d(TAG, ”MTK-DialerSearch, cursor.getCount: ” + cursor.getCount());
- return cursor;
- } catch (Exception e) {
- Log.w(TAG, ”Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults”, e);
- if (cursor != null) {
- cursor.close();
- cursor = null;
- }
- return null;
- }
- }
@DialerSearchHelper.java
/**
* Query dialerSearch results from contactsProvider, use MTK algorithm.
* @param query
* @return DialerSearch result.
*/
public Cursor getSmartDialerSearchResults(String query) {
Log.d(TAG, "MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: " + query);
if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr
return null;
}
final ContentResolver resolver = mContext.getContentResolver();
Cursor cursor = null;
try {
int displayOrder = sContactsPrefs.getDisplayOrder();
int sortOrder = sContactsPrefs.getSortOrder();
//设置Uri的路径
Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "dialer_search");
//设置Uri的搜索文本
Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();
Log.d(TAG, "MTK-DialerSearch, displayOrder: " + displayOrder + " ,sortOrder: "
+ sortOrder);
//在Uri的path中加入两个键值对,并根据参数查询字符串
Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(
ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))
.appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,
String.valueOf(sortOrder)).build();
cursor = resolver.query(dialerSearchParamUri, null, null, null, null);
Log.d(TAG, "liuhuan DISPLAY_ORDER= " + String.valueOf(displayOrder)+"SORT_ORDER ="+String.valueOf(sortOrder));
Log.d(TAG, "liuhuan DISPLAY_ORDER= " + ContactsContract.Preferences.DISPLAY_ORDER+"SORT_ORDER ="+ContactsContract.Preferences.SORT_ORDER);
Log.d(TAG, "MTK-DialerSearch, cursor.getCount: " + cursor.getCount());
return cursor;
} catch (Exception e) {
Log.w(TAG, "Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults", e);
if (cursor != null) {
cursor.close();
cursor = null;
}
return null;
}
}
LOG:dialerSearchParamUri= content://com.android.contacts/dialer_search/5?android.contacts.DISPLAY_ORDER=1&android.contacts.SORT_ORDER=1
public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, StringsortOrder){}
- @ContactEntryListFragment.java
@ContactEntryListFragment.java
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- Log.d(TAG, ”[onLoadFinished] loader:” + loader + “,data:” + data);
- /// M: check whether the fragment still in Activity @{
- if (!isAdded()) {
- Log.d(TAG, ”onLoadFinished(),This Fragment is not add to the Activity now.data:”
- + data);
- return;
- }
- /// @}
- if (!mEnabled) {
- Log.d(TAG, ”return in onLoad finish,mEnabled:” + mEnabled);
- return;
- }
- int loaderId = loader.getId();
- if (loaderId == DIRECTORY_LOADER_ID) {
- mDirectoryListStatus = STATUS_LOADED;
- mAdapter.changeDirectories(data);
- Log.d(TAG, ”onLoadFinished startloading,loaderId:” + loaderId);
- startLoading();
- } else {
- onPartitionLoaded(loaderId, data);
- if (isSearchMode()) {
- int directorySearchMode = getDirectorySearchMode();
- if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
- if (mDirectoryListStatus == STATUS_NOT_LOADED) {
- mDirectoryListStatus = STATUS_LOADING;
- getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
- } else {
- startLoading();
- }
- }
- } else {
- mDirectoryListStatus = STATUS_NOT_LOADED;
- getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
- }
- }
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.d(TAG, "[onLoadFinished] loader:" + loader + ",data:" + data);
/// M: check whether the fragment still in Activity @{
if (!isAdded()) {
Log.d(TAG, "onLoadFinished(),This Fragment is not add to the Activity now.data:"
+ data);
return;
}
/// @}
if (!mEnabled) {
Log.d(TAG, "return in onLoad finish,mEnabled:" + mEnabled);
return;
}
int loaderId = loader.getId();
if (loaderId == DIRECTORY_LOADER_ID) {
mDirectoryListStatus = STATUS_LOADED;
mAdapter.changeDirectories(data);
Log.d(TAG, "onLoadFinished startloading,loaderId:" + loaderId);
startLoading();
} else {
onPartitionLoaded(loaderId, data);
if (isSearchMode()) {
int directorySearchMode = getDirectorySearchMode();
if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
if (mDirectoryListStatus == STATUS_NOT_LOADED) {
mDirectoryListStatus = STATUS_LOADING;
getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
} else {
startLoading();
}
}
} else {
mDirectoryListStatus = STATUS_NOT_LOADED;
getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
}
}
下面总结一下搜索的流程说明:

MTK的Dialer模块联系人搜索
拨号搜索机制分为两个部分:引导搜索和搜索。其中引导搜索是指,从用户输入到开始搜索之间的流程,而搜索部分是指,从数据库搜索字符串的过程。
一、引导搜索部分
默认的拨号界面的布局从上到下主要分为3个部分:显示列表、数字编辑框、拨号键盘。他们的作用是:用户直接在拨号键盘上输入数字,然后数字编辑框显示所输入的数字,同时在显示列表中体现此时的搜索结果。如图所示:
拨号界面布局
从流程上来讲,需要拨号键盘将用户点击转换为按键事件并传递给编辑框,然后由编辑框传递给搜索框,再由搜索框传递给列表Fragment,然后在列表所加载的Adapter中体现当前的搜索结果。
搜索流程框图
1.1、从拨号键盘到编辑框
用户在拨号键盘上的点击的数字按钮,都会在编辑框中体现出来,我们先来追踪这一过程。每个拨号键盘按钮都是DialpadKeyButton类型的View,他们继承自FrameLayout,当遇到点击事件时,就会触发configureKeypadListeners()方法,在DialpadFragment.Java中
- DialpadFragment.java
DialpadFragment.java
- private void configureKeypadListeners(View fragmentView) {
- final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
- R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};
- View dialpadKey;
- for (int i = 0; i < buttonIds.length; i++) {
- dialpadKey = fragmentView.findViewById(buttonIds[i]);
- dialpadKey.setOnClickListener(this);
- }
- // Long-pressing one button will initiate Voicemail.
- final View one = fragmentView.findViewById(R.id.one);
- one.setOnLongClickListener(this);
- // Long-pressing zero button will enter ’+’ instead.
- final View zero = fragmentView.findViewById(R.id.zero);
- zero.setOnLongClickListener(this);
- // Long-pressing one button will initiate Voicemail.
- final View start = fragmentView.findViewById(R.id.star);
- start.setOnLongClickListener(this);
- // Long-pressing zero button will enter ’+’ instead.
- final View pound = fragmentView.findViewById(R.id.pound);
- pound.setOnLongClickListener(this);
- }
private void configureKeypadListeners(View fragmentView) {
final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};
View dialpadKey;
for (int i = 0; i < buttonIds.length; i++) {
dialpadKey = fragmentView.findViewById(buttonIds[i]);
dialpadKey.setOnClickListener(this);
}
// Long-pressing one button will initiate Voicemail.
final View one = fragmentView.findViewById(R.id.one);
one.setOnLongClickListener(this);
// Long-pressing zero button will enter '+' instead.
final View zero = fragmentView.findViewById(R.id.zero);
zero.setOnLongClickListener(this);
// Long-pressing one button will initiate Voicemail.
final View start = fragmentView.findViewById(R.id.star);
start.setOnLongClickListener(this);
// Long-pressing zero button will enter '+' instead.
final View pound = fragmentView.findViewById(R.id.pound);
pound.setOnLongClickListener(this);
}
configureKeypadListeners()方法中,设置了dialpadKey点击事件的监听:dialpadKey.setOnClickListener(this);然后在DialpadFragment的onClick()方法中,将当前的点击事件转换为标准的按键输入:
- @Override DialpadFragment.java
- public void onClick(View view) {
- /** M: Prevent the event if dialpad is not shown. @{ */
- if (getActivity() != null
- && !((DialtactsActivity)getActivity()).isDialpadShown()) {
- Log.d(TAG, ”onClick but dialpad is not shown, skip !!!”);
- return;
- }
- /** @} */
- switch (view.getId()) {
- case R.id.dialpad_floating_action_button:
- mHaptic.vibrate();
- handleDialButtonPressed();
- break;
- case R.id.deleteButton: {
- keyPressed(KeyEvent.KEYCODE_DEL);
- break;
- }
- case R.id.digits: {
- if (!isDigitsEmpty()) {
- mDigits.setCursorVisible(true);
- }
- break;
- }
- case R.id.dialpad_overflow: {
- /// M: for plug-in @{
- ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(
- mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());
- /// @}
- mOverflowPopupMenu.show();
- break;
- }
- //Added by duyuanfeng for Lenovo dialpad
- case R.id.one: {
- keyPressed(KeyEvent.KEYCODE_1);
- break;
- }
- case R.id.two: {
- keyPressed(KeyEvent.KEYCODE_2);
- break;
- }
- // ……
- case R.id.star: {
- keyPressed(KeyEvent.KEYCODE_STAR);
- break;
- }
- //End addition
- default: {
- Log.wtf(TAG, ”Unexpected onClick() event from: ” + view);
- return;
- }
- }
- }
@Override DialpadFragment.java
public void onClick(View view) {
/** M: Prevent the event if dialpad is not shown. @{ */
if (getActivity() != null
&& !((DialtactsActivity)getActivity()).isDialpadShown()) {
Log.d(TAG, "onClick but dialpad is not shown, skip !!!");
return;
}
/** @} */
switch (view.getId()) {
case R.id.dialpad_floating_action_button:
mHaptic.vibrate();
handleDialButtonPressed();
break;
case R.id.deleteButton: {
keyPressed(KeyEvent.KEYCODE_DEL);
break;
}
case R.id.digits: {
if (!isDigitsEmpty()) {
mDigits.setCursorVisible(true);
}
break;
}
case R.id.dialpad_overflow: {
/// M: for plug-in @{
ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(
mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());
/// @}
mOverflowPopupMenu.show();
break;
}
//Added by duyuanfeng for Lenovo dialpad
case R.id.one: {
keyPressed(KeyEvent.KEYCODE_1);
break;
}
case R.id.two: {
keyPressed(KeyEvent.KEYCODE_2);
break;
}
// ......
case R.id.star: {
keyPressed(KeyEvent.KEYCODE_STAR);
break;
}
//End addition
default: {
Log.wtf(TAG, "Unexpected onClick() event from: " + view);
return;
}
}
}
这里看到,当我们在拨号键盘上点击某个View时,将会通过onClick()转换为标准的键盘消息,比如,在R.id.one控件上的点击,将会转换为KeyEvent.KEYCODE_1消息。然后在keyPressed()中将会把当前输入传递给编辑框:
- DialpadFragment.java
DialpadFragment.java
- private void keyPressed(int keyCode) {
- if (getView() == null || getView().getTranslationY() != 0) {
- return;
- }
- mHaptic.vibrate();
- KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
- mDigits.onKeyDown(keyCode, event);
- // If the cursor is at the end of the text we hide it.
- final int length = mDigits.length();
- if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
- mDigits.setCursorVisible(false);
- }
- if(length >=128)//songhu add for cu320
- clearDialpad();
- }
private void keyPressed(int keyCode) {
if (getView() == null || getView().getTranslationY() != 0) {
return;
}
mHaptic.vibrate();
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
mDigits.onKeyDown(keyCode, event);
// If the cursor is at the end of the text we hide it.
final int length = mDigits.length();
if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
mDigits.setCursorVisible(false);
}
if(length >=128)//songhu add for cu320
clearDialpad();
}
mDigits.onKeyDown(keyCode, event)将内容传递给编辑框控件,mDigits就是编辑框控件。
1.2、从编辑框到搜索框
搜索框的作用主要是,当拨号键盘隐藏时,显示当前的输入内容。而编辑框需要将当前的输入传递给搜索框。当编辑框检测到KeyDown事件后,就会将当前键盘的输入放入编辑框中,并触发TextWatcher的相关方法:
- DialpadFragment.java
DialpadFragment.java
- public void afterTextChanged(Editable input) {
- .
- if (!mDigitsFilledByIntent &&
- SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
- mDigits.getText().clear();
- }
- if (isDigitsEmpty()) {
- mDigitsFilledByIntent = false;
- mDigits.setCursorVisible(false);
- }
- if (mDialpadQueryListener != null) {
- //传递给mDialpadQueryListener
- mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
- }
- updateDeleteButtonEnabledState();
- }
public void afterTextChanged(Editable input) {
.
if (!mDigitsFilledByIntent &&
SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
mDigits.getText().clear();
}
if (isDigitsEmpty()) {
mDigitsFilledByIntent = false;
mDigits.setCursorVisible(false);
}
if (mDialpadQueryListener != null) {
//传递给mDialpadQueryListener
mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
}
updateDeleteButtonEnabledState();
}
在这里,又将当前已经输入的文本传递给mDialpadQueryListener,它是在DialtactsActivity.java中实现的
- DialtactsActivity.java
DialtactsActivity.java
- public void onDialpadQueryChanged(String query) {
- if (mSmartDialSearchFragment != null) {
- mSmartDialSearchFragment.setAddToContactNumber(query);
- }
- final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
- /* M: [MTK Dialer Search] use mtk enhance dialpad map */
- DialerFeatureOptions.isDialerSearchEnabled() ?
- SmartDialNameMatcher.SMART_DIALPAD_MAP
- : SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
- if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
- if (DEBUG) {
- Log.d(TAG, ”onDialpadQueryChanged - new query: ” + query);
- }
- if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
- if (!TextUtils.isEmpty(normalizedQuery)) {
- mPendingSearchViewQuery = normalizedQuery;
- }
- return;
- }
- //传递给搜索框
- mSearchView.setText(normalizedQuery);
- }
- }
public void onDialpadQueryChanged(String query) {
if (mSmartDialSearchFragment != null) {
mSmartDialSearchFragment.setAddToContactNumber(query);
}
final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,
/* M: [MTK Dialer Search] use mtk enhance dialpad map */
DialerFeatureOptions.isDialerSearchEnabled() ?
SmartDialNameMatcher.SMART_DIALPAD_MAP
: SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
if (DEBUG) {
Log.d(TAG, "onDialpadQueryChanged - new query: " + query);
}
if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
if (!TextUtils.isEmpty(normalizedQuery)) {
mPendingSearchViewQuery = normalizedQuery;
}
return;
}
//传递给搜索框
mSearchView.setText(normalizedQuery);
}
}
在onDialpadQueryChanged()中将当前编辑框的内容通过setText()方法传递给了mSearchView,也就是最上方的搜索框。
1.3、从搜索框到搜索结果列表Fragment
搜索框下面的列表用于在搜索时显示搜索结果,他所处的位置是复用的,可以选择性的加载三种Fragment,当处于非搜索状态时,加载PhoneFavoriteFragment,这是进入拨号界面的默认加载项,将会显示瓦片式收藏界面,当在搜索模式时,将会加载SmartDialSearchFragment(拨号搜索,在拨号盘里输入号码呈现结果集的fragment)或者RegularSearchFragment(全局搜索,在actionbar的edittext里输入号码呈现结果集的fragment)用于显示当时的搜索结果。对于最常用的用户在拨号键盘输入内容触发的搜索,将会加载SmartDialSearchFragment。此时搜索框需要将要搜索的文本传递给SmartDialSearchFragment。
在搜索时,由于搜索框注册了文本监听器,所以将会触发TextWatcher,此时需要暂存当前要搜索的文本,并进入搜索模式,然后再将搜索内容交给SmartDialSearchFragment。
- @DialtactsActivity.java
@DialtactsActivity.java
- /**
- * Listener used to send search queries to the phone search fragment.
- */
- 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) {
- final String newText = s.toString();
- if (newText.equals(mSearchQuery)) {
- // If the query hasn’t changed (perhaps due to activity being destroyed
- // and restored, or user launching the same DIAL intent twice), then there is
- // no need to do anything here.
- return;
- }
- if (DEBUG) {
- Log.d(TAG, ”onTextChange for mSearchView called with new query: ” + newText);
- Log.d(TAG, ”Previous Query: ” + mSearchQuery);
- }
- mSearchQuery = newText;
- // 当搜索的字符串为变成不为空的时候显示搜索界面
- if (!TextUtils.isEmpty(newText)) {
- // Call enterSearchUi only if we are switching search modes, or showing a search
- // fragment for the first time.
- final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
- (!mIsDialpadShown && mInRegularSearch);
- if (!sameSearchMode) {
- enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
- }
- }
- //选择不同的搜索模式
- if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
- mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
- } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
- mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
- }
- }
- @Override
- public void afterTextChanged(Editable s) {
- }
- };
/**
* Listener used to send search queries to the phone search fragment.
*/
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) {
final String newText = s.toString();
if (newText.equals(mSearchQuery)) {
// If the query hasn't changed (perhaps due to activity being destroyed
// and restored, or user launching the same DIAL intent twice), then there is
// no need to do anything here.
return;
}
if (DEBUG) {
Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
Log.d(TAG, "Previous Query: " + mSearchQuery);
}
mSearchQuery = newText;
// 当搜索的字符串为变成不为空的时候显示搜索界面
if (!TextUtils.isEmpty(newText)) {
// Call enterSearchUi only if we are switching search modes, or showing a search
// fragment for the first time.
final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
(!mIsDialpadShown && mInRegularSearch);
if (!sameSearchMode) {
enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
}
}
//选择不同的搜索模式
if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
} else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
}
}
@Override
public void afterTextChanged(Editable s) {
}
};
在这里从搜索框进入到不同的SearchFragment,并将文本传递给SearchFragment;
1.4、从搜索列表的Fragment到Adapter
—-SearchFragment
—-PhoneNumberPickerFragment
—-ContactEntryListFragment<ContactEntryListAdapter>
—-Fragment
- ContactEntryListFragment.java
ContactEntryListFragment.java
- public void setQueryString(String queryString, boolean delaySelection) {
- if (!TextUtils.equals(mQueryString, queryString)) {
- if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {
- if (TextUtils.isEmpty(mQueryString)) {
- // Restore the adapter if the query used to be empty.
- mListView.setAdapter(mAdapter);
- } else if (TextUtils.isEmpty(queryString)) {
- // Instantly clear the list view if the new query is empty.
- mListView.setAdapter(null);
- }
- }
- mQueryString = queryString;
- setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
- if (mAdapter != null) {
- //传递给Adapter
- mAdapter.setQueryString(queryString);
- //触发Adapter重新搜索
- reloadData();
- }
- }
- }
public void setQueryString(String queryString, boolean delaySelection) {
if (!TextUtils.equals(mQueryString, queryString)) {
if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {
if (TextUtils.isEmpty(mQueryString)) {
// Restore the adapter if the query used to be empty.
mListView.setAdapter(mAdapter);
} else if (TextUtils.isEmpty(queryString)) {
// Instantly clear the list view if the new query is empty.
mListView.setAdapter(null);
}
}
mQueryString = queryString;
setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);
if (mAdapter != null) {
//传递给Adapter
mAdapter.setQueryString(queryString);
//触发Adapter重新搜索
reloadData();
}
}
}
在这里,Fragment将要搜索的文本通过setQueryString()的方法传递给当前的Adapter,然后通过reloadData()方法触发Adapter的搜索机制。那么这里的Adapter具体是指哪个呢?
我们在SmartDialSearchFragment中找到了该Adapter的创建之处,他就是SmartDialNumberListAdapter:
- SmartDialSearchFragment.java
SmartDialSearchFragment.java
- @Override
- protected ContactEntryListAdapter createListAdapter() {
- SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());
- adapter.setUseCallableUri(super.usesCallableUri());
- adapter.setQuickContactEnabled(true);
- // Set adapter’s query string to restore previous instance state.
- adapter.setQueryString(getQueryString());
- return adapter;
- }
@Override
protected ContactEntryListAdapter createListAdapter() {
SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());
adapter.setUseCallableUri(super.usesCallableUri());
adapter.setQuickContactEnabled(true);
// Set adapter's query string to restore previous instance state.
adapter.setQueryString(getQueryString());
return adapter;
}
该Adapter的继承关系如下:
SmartDialNumberListAdapter
—-DialerPhoneNumberListAdapter
—-PhoneNumberListAdapter
—-ContactEntryListAdapter
—-IndexerListAdapter
—-PinnedHeaderListAdapter
—-CompositeCursorAdapter
接下来我们分析如何通过Fragment的reloadData()触发Adapter的搜索。
1.5、Adapter触发搜索机制
刚才介绍到,SmartDialSearchFragment在setQueryString()时,通过reloadData()触发了Adapter的搜索,我们来看一下这个流程:- ContactEntryListFragment.java
ContactEntryListFragment.java
- protected void reloadData() {
- removePendingDirectorySearchRequests();
- mAdapter.onDataReload();
- mLoadPriorityDirectoriesOnly = true;
- mForceLoad = true;
- //触发新的Adapter
- startLoading();
- }
- protected void startLoading() {
- Log.d(TAG, ”startLoading”);
- if (mAdapter == null) {
- // The method was called before the fragment was started
- Log.d(TAG, ”[statLoading] mAdapter is null”);
- return;
- }
- //配置Adapter要搜索的文本
- configureAdapter();
- int partitionCount = mAdapter.getPartitionCount();
- for (int i = 0; i < partitionCount; i++) {
- Partition partition = mAdapter.getPartition(i);
- if (partition instanceof DirectoryPartition) {
- DirectoryPartition directoryPartition = (DirectoryPartition)partition;
- if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
- if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
- startLoadingDirectoryPartition(i);
- }
- }
- } else {
- //通过LoaderManager进行异步查询
- getLoaderManager().initLoader(i, null, this);
- }
- }
- // Next time this method is called, we should start loading non-priority directories
- mLoadPriorityDirectoriesOnly = false;
- }
protected void reloadData() {
removePendingDirectorySearchRequests();
mAdapter.onDataReload();
mLoadPriorityDirectoriesOnly = true;
mForceLoad = true;
//触发新的Adapter
startLoading();
}
protected void startLoading() {
Log.d(TAG, "startLoading");
if (mAdapter == null) {
// The method was called before the fragment was started
Log.d(TAG, "[statLoading] mAdapter is null");
return;
}
//配置Adapter要搜索的文本
configureAdapter();
int partitionCount = mAdapter.getPartitionCount();
for (int i = 0; i < partitionCount; i++) {
Partition partition = mAdapter.getPartition(i);
if (partition instanceof DirectoryPartition) {
DirectoryPartition directoryPartition = (DirectoryPartition)partition;
if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {
startLoadingDirectoryPartition(i);
}
}
} else {
//通过LoaderManager进行异步查询
getLoaderManager().initLoader(i, null, this);
}
}
// Next time this method is called, we should start loading non-priority directories
mLoadPriorityDirectoriesOnly = false;
}
在startLoading()时,通过configureAdapter()对当前的Adapter配置了要搜索的文本、排序方法以及显示主题等信息,由于
- *partition instanceof DirectoryPartition = true
*partition instanceof DirectoryPartition = true
- ContactEntryListFragment.java
- private void startLoadingDirectoryPartition(int partitionIndex) {
- DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
- partition.setStatus(DirectoryPartition.STATUS_LOADING);
- long directoryId = partition.getDirectoryId();
- if (mForceLoad) {
- if (directoryId == Directory.DEFAULT) {
- loadDirectoryPartition(partitionIndex, partition);
- } else {
- loadDirectoryPartitionDelayed(partitionIndex, partition);
- }
- } else {
- Bundle args = new Bundle();
- args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
- getLoaderManager().initLoader(partitionIndex, args, this);
- }
- }
ContactEntryListFragment.java
private void startLoadingDirectoryPartition(int partitionIndex) {
DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);
partition.setStatus(DirectoryPartition.STATUS_LOADING);
long directoryId = partition.getDirectoryId();
if (mForceLoad) {
if (directoryId == Directory.DEFAULT) {
loadDirectoryPartition(partitionIndex, partition);
} else {
loadDirectoryPartitionDelayed(partitionIndex, partition);
}
} else {
Bundle args = new Bundle();
args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);
getLoaderManager().initLoader(partitionIndex, args, this);
}
}
- SmartDialSearchFragments.java
SmartDialSearchFragments.java
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- // Smart dialing does not support Directory Load, falls back to normal search instead.
- if (id == getDirectoryLoaderId()) {
- return super.onCreateLoader(id, args);
- } else {
- final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();
- /// M: [MTK Dialer Search] @{
- if (DialerFeatureOptions.isDialerSearchEnabled()) {
- DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(),
- usesCallableUri());
- adapter.configureLoader(loader);
- return loader;
- /// @}
- } else {
- //创建当前的CursorLoader,也就是SmartDialCursorLoader
- SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());
- adapter.configureLoader(loader);
- return loader;
- }
- }
- }
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Smart dialing does not support Directory Load, falls back to normal search instead.
if (id == getDirectoryLoaderId()) {
return super.onCreateLoader(id, args);
} else {
final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();
/// M: [MTK Dialer Search] @{
if (DialerFeatureOptions.isDialerSearchEnabled()) {
DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(),
usesCallableUri());
adapter.configureLoader(loader);
return loader;
/// @}
} else {
//创建当前的CursorLoader,也就是SmartDialCursorLoader
SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());
adapter.configureLoader(loader);
return loader;
}
}
}
由于DialerFeatureOptions.isDialerSearchEnabled()为true,因此这里创建了DialerSearchCursorLoader作为当前的CursorLoader。然后通过adapter的configureLoader()方法将该Loader传递给SmartDialNumberListAdapter,接下来就会在DialerSearchCursorLoader中完成异步查询,现在我们看一下在DialerSearchCursorLoader中的查询流程:
- DialerSearchCursouLoader.java
- /**
- * Configures the query string to be used to find SmartDial matches.
- * @param query The query string user typed.
- */
- public void configureQuery(String query, boolean isSmartQuery) {
- Log.d(TAG, ”MTK-DialerSearch, Configure new query to be ” + query);
- mQuery = query;
- //搜索模式 isSmartQuery
- if (!isSmartQuery) {
- mQuery = DialerSearchUtils.stripTeleSeparators(query);
- }
- //判断字符串是否合法
- if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {
- mEnableDefaultSearch = true;
- }
- }
- /**
- * Queries the Contacts database and loads results in background.
- * @return Cursor of contacts that matches the SmartDial query.
- */
- @Override
- public Cursor loadInBackground() {
- Log.d(TAG, ”MTK-DialerSearch, Load in background. mQuery: ” + mQuery);
- final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);
- Cursor cursor = null;
- if (mEnableDefaultSearch) {
- cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);
- } else {
- cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);
- }
- if (cursor != null) {
- Log.d(TAG, ”MTK-DialerSearch, loadInBackground, result.getCount: ”
- + cursor.getCount());
- return cursor;
- } else {
- Log.w(TAG, ”MTK-DialerSearch, —-cursor is null—-“);
- return null;
- }
- }
DialerSearchCursouLoader.java
/**
* Configures the query string to be used to find SmartDial matches.
* @param query The query string user typed.
*/
public void configureQuery(String query, boolean isSmartQuery) {
Log.d(TAG, "MTK-DialerSearch, Configure new query to be " + query);
mQuery = query;
//搜索模式 isSmartQuery
if (!isSmartQuery) {
mQuery = DialerSearchUtils.stripTeleSeparators(query);
}
//判断字符串是否合法
if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {
mEnableDefaultSearch = true;
}
}
/**
* Queries the Contacts database and loads results in background.
* @return Cursor of contacts that matches the SmartDial query.
*/
@Override
public Cursor loadInBackground() {
Log.d(TAG, "MTK-DialerSearch, Load in background. mQuery: " + mQuery);
final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);
Cursor cursor = null;
if (mEnableDefaultSearch) {
cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);
} else {
cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);
}
if (cursor != null) {
Log.d(TAG, "MTK-DialerSearch, loadInBackground, result.getCount: "
+ cursor.getCount());
return cursor;
} else {
Log.w(TAG, "MTK-DialerSearch, ----cursor is null----");
return null;
}
}
- @DialerSearchHelper.java
- /**
- * Query dialerSearch results from contactsProvider, use MTK algorithm.
- * @param query
- * @return DialerSearch result.
- */
- public Cursor getSmartDialerSearchResults(String query) {
- Log.d(TAG, ”MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: ” + query);
- if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr
- return null;
- }
- final ContentResolver resolver = mContext.getContentResolver();
- Cursor cursor = null;
- try {
- int displayOrder = sContactsPrefs.getDisplayOrder();
- int sortOrder = sContactsPrefs.getSortOrder();
- //设置Uri的路径
- Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, ”dialer_search”);
- //设置Uri的搜索文本
- Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();
- Log.d(TAG, ”MTK-DialerSearch, displayOrder: ” + displayOrder + “ ,sortOrder: ”
- + sortOrder);
- //在Uri的path中加入两个键值对,并根据参数查询字符串
- Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(
- ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))
- .appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,
- String.valueOf(sortOrder)).build();
- cursor = resolver.query(dialerSearchParamUri, null, null, null, null);
- Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + String.valueOf(displayOrder)+“SORT_ORDER =”+String.valueOf(sortOrder));
- Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + ContactsContract.Preferences.DISPLAY_ORDER+“SORT_ORDER =”+ContactsContract.Preferences.SORT_ORDER);
- Log.d(TAG, ”MTK-DialerSearch, cursor.getCount: ” + cursor.getCount());
- return cursor;
- } catch (Exception e) {
- Log.w(TAG, ”Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults”, e);
- if (cursor != null) {
- cursor.close();
- cursor = null;
- }
- return null;
- }
- }
@DialerSearchHelper.java
/**
* Query dialerSearch results from contactsProvider, use MTK algorithm.
* @param query
* @return DialerSearch result.
*/
public Cursor getSmartDialerSearchResults(String query) {
Log.d(TAG, "MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: " + query);
if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr
return null;
}
final ContentResolver resolver = mContext.getContentResolver();
Cursor cursor = null;
try {
int displayOrder = sContactsPrefs.getDisplayOrder();
int sortOrder = sContactsPrefs.getSortOrder();
//设置Uri的路径
Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "dialer_search");
//设置Uri的搜索文本
Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();
Log.d(TAG, "MTK-DialerSearch, displayOrder: " + displayOrder + " ,sortOrder: "
+ sortOrder);
//在Uri的path中加入两个键值对,并根据参数查询字符串
Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(
ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))
.appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,
String.valueOf(sortOrder)).build();
cursor = resolver.query(dialerSearchParamUri, null, null, null, null);
Log.d(TAG, "liuhuan DISPLAY_ORDER= " + String.valueOf(displayOrder)+"SORT_ORDER ="+String.valueOf(sortOrder));
Log.d(TAG, "liuhuan DISPLAY_ORDER= " + ContactsContract.Preferences.DISPLAY_ORDER+"SORT_ORDER ="+ContactsContract.Preferences.SORT_ORDER);
Log.d(TAG, "MTK-DialerSearch, cursor.getCount: " + cursor.getCount());
return cursor;
} catch (Exception e) {
Log.w(TAG, "Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults", e);
if (cursor != null) {
cursor.close();
cursor = null;
}
return null;
}
}
LOG:dialerSearchParamUri= content://com.android.contacts/dialer_search/5?android.contacts.DISPLAY_ORDER=1&android.contacts.SORT_ORDER=1
public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, StringsortOrder){}
- @ContactEntryListFragment.java
@ContactEntryListFragment.java
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- Log.d(TAG, ”[onLoadFinished] loader:” + loader + “,data:” + data);
- /// M: check whether the fragment still in Activity @{
- if (!isAdded()) {
- Log.d(TAG, ”onLoadFinished(),This Fragment is not add to the Activity now.data:”
- + data);
- return;
- }
- /// @}
- if (!mEnabled) {
- Log.d(TAG, ”return in onLoad finish,mEnabled:” + mEnabled);
- return;
- }
- int loaderId = loader.getId();
- if (loaderId == DIRECTORY_LOADER_ID) {
- mDirectoryListStatus = STATUS_LOADED;
- mAdapter.changeDirectories(data);
- Log.d(TAG, ”onLoadFinished startloading,loaderId:” + loaderId);
- startLoading();
- } else {
- onPartitionLoaded(loaderId, data);
- if (isSearchMode()) {
- int directorySearchMode = getDirectorySearchMode();
- if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
- if (mDirectoryListStatus == STATUS_NOT_LOADED) {
- mDirectoryListStatus = STATUS_LOADING;
- getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
- } else {
- startLoading();
- }
- }
- } else {
- mDirectoryListStatus = STATUS_NOT_LOADED;
- getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
- }
- }
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Log.d(TAG, "[onLoadFinished] loader:" + loader + ",data:" + data);
/// M: check whether the fragment still in Activity @{
if (!isAdded()) {
Log.d(TAG, "onLoadFinished(),This Fragment is not add to the Activity now.data:"
+ data);
return;
}
/// @}
if (!mEnabled) {
Log.d(TAG, "return in onLoad finish,mEnabled:" + mEnabled);
return;
}
int loaderId = loader.getId();
if (loaderId == DIRECTORY_LOADER_ID) {
mDirectoryListStatus = STATUS_LOADED;
mAdapter.changeDirectories(data);
Log.d(TAG, "onLoadFinished startloading,loaderId:" + loaderId);
startLoading();
} else {
onPartitionLoaded(loaderId, data);
if (isSearchMode()) {
int directorySearchMode = getDirectorySearchMode();
if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {
if (mDirectoryListStatus == STATUS_NOT_LOADED) {
mDirectoryListStatus = STATUS_LOADING;
getLoaderManager().initLoader(DIRECTORY_LOADER_ID, null, this);
} else {
startLoading();
}
}
} else {
mDirectoryListStatus = STATUS_NOT_LOADED;
getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);
}
}
下面总结一下搜索的流程说明:
