Android:MTK的Dialer模塊聯繫人搜索

MTK的Dialer模塊聯繫人搜索     


      撥號搜索機制分爲兩個部分引導搜索和搜索。其中引導搜索是指,從用戶輸入到開始搜索之間的流程,而搜索部分是指,從數據庫搜索字符串的過程。

一、引導搜索部分

     默認的撥號界面的佈局從上到下主要分爲3個部分:顯示列表、數字編輯框、撥號鍵盤。他們的作用是:用戶直接在撥號鍵盤上輸入數字,然後數字編輯框顯示所輸入的數字,同時在顯示列表中體現此時的搜索結果。如圖所示:

 

撥號界面佈局

   從流程上來講,需要撥號鍵盤將用戶點擊轉換爲按鍵事件並傳遞給編輯框,然後由編輯框傳遞給搜索框,再由搜索框傳遞給列表Fragment,然後在列表所加載的Adapter中體現當前的搜索結果。


搜索流程框圖

1.1、從撥號鍵盤到編輯框

       用戶在撥號鍵盤上的點擊的數字按鈕,都會在編輯框中體現出來,我們先來追蹤這一過程。每個撥號鍵盤按鈕都是DialpadKeyButton類型的View,他們繼承自FrameLayout,當遇到點擊事件時,就會觸發configureKeypadListeners()方法,在DialpadFragment.Java

  1. DialpadFragment.java  
DialpadFragment.java

  1. private void configureKeypadListeners(View fragmentView) {  
  2.         final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,  
  3.                 R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};  
  4.   
  5.         View dialpadKey;  
  6.   
  7.         for (int i = 0; i < buttonIds.length; i++) {  
  8.             dialpadKey =  fragmentView.findViewById(buttonIds[i]);  
  9.             dialpadKey.setOnClickListener(this);  
  10.         }  
  11.   
  12.         // Long-pressing one button will initiate Voicemail.  
  13.         final View one = fragmentView.findViewById(R.id.one);  
  14.         one.setOnLongClickListener(this);  
  15.   
  16.         // Long-pressing zero button will enter ’+’ instead.  
  17.         final View zero = fragmentView.findViewById(R.id.zero);  
  18.         zero.setOnLongClickListener(this);  
  19.   
  20.    
  21.  // Long-pressing one button will initiate Voicemail.  
  22.         final View start = fragmentView.findViewById(R.id.star);  
  23.         start.setOnLongClickListener(this);  
  24.   
  25.         // Long-pressing zero button will enter ’+’ instead.  
  26.         final View pound = fragmentView.findViewById(R.id.pound);  
  27.         pound.setOnLongClickListener(this);  
  28. }  
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);然後在DialpadFragmentonClick()方法中,將當前的點擊事件轉換爲標準的按鍵輸入:

  1. @Override  DialpadFragment.java  
  2.     public void onClick(View view) {  
  3.         /** M: Prevent the event if dialpad is not shown. @{ */  
  4.         if (getActivity() != null  
  5.                 && !((DialtactsActivity)getActivity()).isDialpadShown()) {  
  6.             Log.d(TAG, ”onClick but dialpad is not shown, skip !!!”);  
  7.             return;  
  8.         }  
  9.         /** @} */  
  10.         switch (view.getId()) {  
  11.             case R.id.dialpad_floating_action_button:  
  12.                 mHaptic.vibrate();  
  13.                 handleDialButtonPressed();  
  14.                 break;  
  15.             case R.id.deleteButton: {  
  16.                 keyPressed(KeyEvent.KEYCODE_DEL);  
  17.                 break;  
  18.             }  
  19.             case R.id.digits: {  
  20.                 if (!isDigitsEmpty()) {  
  21.                     mDigits.setCursorVisible(true);  
  22.                 }  
  23.                 break;  
  24.             }  
  25.             case R.id.dialpad_overflow: {  
  26.                 /// M: for plug-in @{  
  27.                 ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(  
  28.                          mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());  
  29.                 /// @}  
  30.                 mOverflowPopupMenu.show();  
  31.                 break;  
  32.             }  
  33.         //Added by duyuanfeng for Lenovo dialpad  
  34.                 case R.id.one: {  
  35.                     keyPressed(KeyEvent.KEYCODE_1);  
  36.                     break;  
  37.                 }  
  38.                 case R.id.two: {  
  39.                     keyPressed(KeyEvent.KEYCODE_2);  
  40.                     break;  
  41.                 }  
  42.            // ……  
  43.                 case R.id.star: {  
  44.                     keyPressed(KeyEvent.KEYCODE_STAR);  
  45.                     break;  
  46.                 }  
  47.         //End addition  
  48.             default: {  
  49.                 Log.wtf(TAG, ”Unexpected onClick() event from: ” + view);  
  50.                 return;  
  51.             }  
  52.         }  
  53. }  
@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()中將會把當前輸入傳遞給編輯框

  1. DialpadFragment.java  
DialpadFragment.java

  1. private void keyPressed(int keyCode) {  
  2.         if (getView() == null || getView().getTranslationY() != 0) {  
  3.             return;  
  4.         }  
  5.         mHaptic.vibrate();  
  6.         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);  
  7.         mDigits.onKeyDown(keyCode, event);  
  8.   
  9.         // If the cursor is at the end of the text we hide it.  
  10.         final int length = mDigits.length();  
  11.         if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {  
  12.             mDigits.setCursorVisible(false);  
  13.         }  
  14.         if(length >=128)//songhu add for cu320  
  15.             clearDialpad();  
  16. }  
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的相關方法:

  1. DialpadFragment.java  
DialpadFragment.java
  1. public void afterTextChanged(Editable input) {  
  2.         .  
  3.         if (!mDigitsFilledByIntent &&  
  4.                 SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {  
  5.               
  6.             mDigits.getText().clear();  
  7.         }  
  8.   
  9.         if (isDigitsEmpty()) {  
  10.             mDigitsFilledByIntent = false;  
  11.             mDigits.setCursorVisible(false);  
  12.         }  
  13.   
  14.         if (mDialpadQueryListener != null) {  
  15. //傳遞給mDialpadQueryListener  
  16.             mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());  
  17.         }  
  18.   
  19.         updateDeleteButtonEnabledState();  
  20.     }  
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中實現的

  1. DialtactsActivity.java  
DialtactsActivity.java
  1. public void onDialpadQueryChanged(String query) {  
  2.         if (mSmartDialSearchFragment != null) {  
  3.             mSmartDialSearchFragment.setAddToContactNumber(query);  
  4.         }  
  5.         final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,  
  6.                 /* M: [MTK Dialer Search] use mtk enhance dialpad map */  
  7.                 DialerFeatureOptions.isDialerSearchEnabled() ?  
  8.                         SmartDialNameMatcher.SMART_DIALPAD_MAP  
  9.                         : SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);  
  10.   
  11.         if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {  
  12.             if (DEBUG) {  
  13.                 Log.d(TAG, ”onDialpadQueryChanged - new query: ” + query);  
  14.             }  
  15.             if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {  
  16.                   
  17.                 if (!TextUtils.isEmpty(normalizedQuery)) {  
  18.                     mPendingSearchViewQuery = normalizedQuery;  
  19.                 }  
  20.                 return;  
  21.             }  
  22. //傳遞給搜索框  
  23.             mSearchView.setText(normalizedQuery);  
  24.         }  
  25. }  
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(全局搜索,在actionbaredittext裏輸入號碼呈現結果集的fragment)用於顯示當時的搜索結果。對於最常用的用戶在撥號鍵盤輸入內容觸發的搜索,將會加載SmartDialSearchFragment。此時搜索框需要將要搜索的文本傳遞給SmartDialSearchFragment
   在搜索時,由於搜索框註冊了文本監聽器,所以將會觸發TextWatcher,此時需要暫存當前要搜索的文本,並進入搜索模式,然後再將搜索內容交給SmartDialSearchFragment

  1. @DialtactsActivity.java  
@DialtactsActivity.java
  1. /** 
  2. * Listener used to send search queries to the phone search fragment. 
  3. */  
  4. private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {  
  5.         @Override  
  6.         public void beforeTextChanged(CharSequence s, int start, int count, int after) {  
  7.         }  
  8.   
  9.   
  10.         @Override  
  11.         public void onTextChanged(CharSequence s, int start, int before, int count) {  
  12.             final String newText = s.toString();  
  13.             if (newText.equals(mSearchQuery)) {  
  14.                 // If the query hasn’t changed (perhaps due to activity being destroyed  
  15.                 // and restored, or user launching the same DIAL intent twice), then there is  
  16.                 // no need to do anything here.  
  17.                 return;  
  18.             }  
  19.             if (DEBUG) {  
  20.                 Log.d(TAG, ”onTextChange for mSearchView called with new query: ” + newText);  
  21.                 Log.d(TAG, ”Previous Query: ” + mSearchQuery);  
  22.             }  
  23.             mSearchQuery = newText;  
  24.   
  25.   
  26.             // 當搜索的字符串爲變成不爲空的時候顯示搜索界面  
  27.             if (!TextUtils.isEmpty(newText)) {  
  28.                 // Call enterSearchUi only if we are switching search modes, or showing a search  
  29.                 // fragment for the first time.  
  30.                 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||  
  31.                         (!mIsDialpadShown && mInRegularSearch);  
  32.                 if (!sameSearchMode) {  
  33.                     enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);  
  34.                 }  
  35.             }  
  36.   
  37. //選擇不同的搜索模式  
  38.             if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {  
  39.                 mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);  
  40.             } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {  
  41.                 mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);  
  42.             }  
  43.         }  
  44.         @Override  
  45.         public void afterTextChanged(Editable s) {  
  46.         }  
  47. };   
/**
* 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

先來看一下SmartDialSearchFragment的繼承關係:
    SmartDialSearchFragment
            —-SearchFragment
                —-PhoneNumberPickerFragment
                    —-ContactEntryListFragment<ContactEntryListAdapter>
                        —-Fragment
       SmartDialSearchFragment拿到搜索的文本後,需要傳遞給自己的Adapter才能完成搜索任務,我們現在來分析這個交接的過程。從上面1.3節中我們看到,SmartDialSearchFragment通過setQueryString()拿到了要搜索的字串,我們來查看這個方法,他是在SmartDialSearchFragment的父類ContactEntryListFragment中被實現的:
  1. ContactEntryListFragment.java  
ContactEntryListFragment.java
  1. public void setQueryString(String queryString, boolean delaySelection) {  
  2.         if (!TextUtils.equals(mQueryString, queryString)) {  
  3.             if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {  
  4.                 if (TextUtils.isEmpty(mQueryString)) {  
  5.                     // Restore the adapter if the query used to be empty.  
  6.                     mListView.setAdapter(mAdapter);  
  7.                 } else if (TextUtils.isEmpty(queryString)) {  
  8.                     // Instantly clear the list view if the new query is empty.  
  9.                     mListView.setAdapter(null);  
  10.                 }  
  11.             }  
  12.   
  13.             mQueryString = queryString;  
  14.             setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);  
  15.   
  16.             if (mAdapter != null) {  
  17.                //傳遞給Adapter  
  18.                 mAdapter.setQueryString(queryString);  
  19. //觸發Adapter重新搜索             
  20. reloadData();  
  21.             }  
  22.         }  
  23. }  
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:
  1. SmartDialSearchFragment.java  
SmartDialSearchFragment.java

  1. @Override  
  2.     protected ContactEntryListAdapter createListAdapter() {  
  3.         SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());  
  4.         adapter.setUseCallableUri(super.usesCallableUri());  
  5.         adapter.setQuickContactEnabled(true);  
  6.         // Set adapter’s query string to restore previous instance state.  
  7.         adapter.setQueryString(getQueryString());  
  8.         return adapter;  
  9. }  
@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的搜索,我們來看一下這個流程:
  1. ContactEntryListFragment.java  
ContactEntryListFragment.java

  1. protected void reloadData() {  
  2.         removePendingDirectorySearchRequests();  
  3.         mAdapter.onDataReload();  
  4.         mLoadPriorityDirectoriesOnly = true;  
  5.         mForceLoad = true;  
  6.     //觸發新的Adapter  
  7.         startLoading();  
  8.     }  
  9.     protected void startLoading() {  
  10.         Log.d(TAG, ”startLoading”);  
  11.         if (mAdapter == null) {  
  12.             // The method was called before the fragment was started  
  13.             Log.d(TAG, ”[statLoading] mAdapter is null”);  
  14.             return;  
  15.         }  
  16.     //配置Adapter要搜索的文本  
  17.         configureAdapter();  
  18.         int partitionCount = mAdapter.getPartitionCount();  
  19.         for (int i = 0; i < partitionCount; i++) {  
  20.             Partition partition = mAdapter.getPartition(i);  
  21.             if (partition instanceof DirectoryPartition) {  
  22.                 DirectoryPartition directoryPartition = (DirectoryPartition)partition;  
  23.                 if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {  
  24.                     if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {  
  25.                         startLoadingDirectoryPartition(i);  
  26.                     }  
  27.                 }  
  28.             } else {  
  29. //通過LoaderManager進行異步查詢  
  30.                 getLoaderManager().initLoader(i, nullthis);  
  31.             }  
  32.         }  
  33.         // Next time this method is called, we should start loading non-priority directories  
  34.         mLoadPriorityDirectoriesOnly = false;  
  35. }  
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配置了要搜索的文本、排序方法以及顯示主題等信息,由於
  1. *partition instanceof DirectoryPartition = true  
*partition instanceof DirectoryPartition = true

因此就會執行startLoadingDirectoryPartition()方法:


  1. ContactEntryListFragment.java  
  2. private void startLoadingDirectoryPartition(int partitionIndex) {  
  3.         DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);  
  4.         partition.setStatus(DirectoryPartition.STATUS_LOADING);  
  5.         long directoryId = partition.getDirectoryId();  
  6.         if (mForceLoad) {  
  7.             if (directoryId == Directory.DEFAULT) {  
  8.                 loadDirectoryPartition(partitionIndex, partition);  
  9.             } else {  
  10.                 loadDirectoryPartitionDelayed(partitionIndex, partition);  
  11.             }  
  12.         } else {  
  13.             Bundle args = new Bundle();  
  14.             args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);  
  15.             getLoaderManager().initLoader(partitionIndex, args, this);  
  16.         }  
  17. }  
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()方法:

  1. SmartDialSearchFragments.java  
SmartDialSearchFragments.java
  1. public Loader<Cursor> onCreateLoader(int id, Bundle args) {  
  2.         // Smart dialing does not support Directory Load, falls back to normal search instead.  
  3.         if (id == getDirectoryLoaderId()) {  
  4.             return super.onCreateLoader(id, args);  
  5.         } else {  
  6.             final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();  
  7.             /// M: [MTK Dialer Search] @{  
  8.             if (DialerFeatureOptions.isDialerSearchEnabled()) {  
  9.                 DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(),  
  10.                         usesCallableUri());  
  11.                 adapter.configureLoader(loader);  
  12.                 return loader;  
  13.             /// @}  
  14.             } else {  
  15. //創建當前的CursorLoader,也就是SmartDialCursorLoader  
  16.                 SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());  
  17.                 adapter.configureLoader(loader);  
  18.                 return loader;  
  19.             }  
  20.         }  
  21. }  
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中的查詢流程:
  1. DialerSearchCursouLoader.java      
  2.  /** 
  3.      * Configures the query string to be used to find SmartDial matches. 
  4.      * @param query The query string user typed. 
  5.      */  
  6.     public void configureQuery(String query, boolean isSmartQuery) {  
  7.   
  8.   
  9.         Log.d(TAG, ”MTK-DialerSearch, Configure new query to be ” + query);  
  10.   
  11.   
  12.         mQuery = query;  
  13.     //搜索模式  isSmartQuery  
  14.         if (!isSmartQuery) {  
  15.             mQuery = DialerSearchUtils.stripTeleSeparators(query);  
  16.         }  
  17.     //判斷字符串是否合法  
  18.         if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {  
  19.             mEnableDefaultSearch = true;  
  20.         }  
  21.     }  
  22.   
  23.   
  24.     /** 
  25.      * Queries the Contacts database and loads results in background. 
  26.      * @return Cursor of contacts that matches the SmartDial query. 
  27.      */  
  28.     @Override  
  29.     public Cursor loadInBackground() {  
  30.   
  31.         Log.d(TAG, ”MTK-DialerSearch, Load in background. mQuery: ” + mQuery);  
  32.   
  33.         final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);  
  34.         Cursor cursor = null;  
  35.         if (mEnableDefaultSearch) {  
  36.             cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);  
  37.         } else {  
  38.             cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);  
  39.         }  
  40.         if (cursor != null) {  
  41.             Log.d(TAG, ”MTK-DialerSearch, loadInBackground, result.getCount: ”  
  42.                     + cursor.getCount());  
  43.   
  44.             return cursor;  
  45.         } else {  
  46.             Log.w(TAG, ”MTK-DialerSearch, —-cursor is null—-“);  
  47.             return null;  
  48.         }  
  49. }  
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.getSmartDialerSearchResults()得到cursor的。

  1. @DialerSearchHelper.java  
  2. /** 
  3. * Query dialerSearch results from contactsProvider, use MTK algorithm. 
  4. * @param query 
  5. * @return DialerSearch result. 
  6. */  
  7. public Cursor getSmartDialerSearchResults(String query) {  
  8.         Log.d(TAG, ”MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: ” + query);  
  9.   
  10.         if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr  
  11.             return null;  
  12.         }  
  13.   
  14.         final ContentResolver resolver = mContext.getContentResolver();  
  15.         Cursor cursor = null;  
  16.         try {  
  17.             int displayOrder = sContactsPrefs.getDisplayOrder();  
  18.             int sortOrder = sContactsPrefs.getSortOrder();  
  19.         //設置Uri的路徑  
  20.             Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, ”dialer_search”);  
  21.         //設置Uri的搜索文本  
  22.             Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();  
  23.             Log.d(TAG, ”MTK-DialerSearch, displayOrder: ” + displayOrder + “ ,sortOrder: ”  
  24.                     + sortOrder);  
  25.         //在Uri的path中加入兩個鍵值對,並根據參數查詢字符串  
  26.             Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(  
  27.                     ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))  
  28.                     .appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,  
  29.                             String.valueOf(sortOrder)).build();  
  30.   
  31.             cursor = resolver.query(dialerSearchParamUri, nullnullnullnull);  
  32.   
  33.             Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + String.valueOf(displayOrder)+“SORT_ORDER =”+String.valueOf(sortOrder));  
  34.   
  35.         Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + ContactsContract.Preferences.DISPLAY_ORDER+“SORT_ORDER =”+ContactsContract.Preferences.SORT_ORDER);  
  36.   
  37.             Log.d(TAG, ”MTK-DialerSearch, cursor.getCount: ” + cursor.getCount());  
  38.   
  39.             return cursor;  
  40.         } catch (Exception e) {  
  41.             Log.w(TAG, ”Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults”, e);  
  42.   
  43.             if (cursor != null) {  
  44.                 cursor.close();  
  45.                 cursor = null;  
  46.             }  
  47.             return null;  
  48.         }  
  49. }  
@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

appendQueryParameter(String ,String);這個方法的官方解釋爲:Encodes the key and value and then appends the parameter to the querystring.官方解釋鏈接:點擊打開鏈接我的理解就是在Uri中將加入一個鍵值對如(name,faker);就查詢name是faker的數據,最後查詢的時候是調用resolver.query()。resolver 是通過getContentResolver得來的,ContentResolver是直譯爲內容解析器,在android中程序間的數據共享是通過Provider/Reslover,提供數據(內容)的就是Provider,Reslover就提供接口對這個數據進行解讀,根據Android官方文檔,query方法的解釋爲:

public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, StringsortOrder){}

第一個參數爲 Uri,android中有很多reslover,爲了區分這些reslover,就需要每個reslover都有一個獨有的標識,而這個Uri就是這個標識;
第二個參數爲 projection,就是要獲取到reslover中的數據的哪些內容,必須聯繫人有name和id,如果只想得到聯繫人的name,那麼就可以設置這個參數:當設置爲null的時候就是獲取reslover中的所有內容;
第三個參數爲 selection :設置條件,比如,我只想得到reslover中,聯繫人爲Faker的相關信息;
第四個參數爲 selectionArgs:這個是配合第三個參數使用的,如果第三個參數中有?,那麼第四個參數就會替換第三個參數;
第五個參數爲 sortOrder:這個是設置reslover中的數據按照什麼排序;
參考文檔:點擊打開鏈接

  1. @ContactEntryListFragment.java  
@ContactEntryListFragment.java
  1. public void onLoadFinished(Loader<Cursor> loader, Cursor data) {  
  2.         Log.d(TAG, ”[onLoadFinished] loader:” + loader + “,data:” + data);  
  3.       /// M: check whether the fragment still in Activity @{  
  4.         if (!isAdded()) {  
  5.             Log.d(TAG, ”onLoadFinished(),This Fragment is not add to the Activity now.data:”  
  6.                     + data);  
  7.             return;  
  8.         }  
  9.         /// @}  
  10.   
  11.         if (!mEnabled) {  
  12.             Log.d(TAG, ”return in onLoad finish,mEnabled:” + mEnabled);  
  13.             return;  
  14.         }  
  15.   
  16.         int loaderId = loader.getId();  
  17.         if (loaderId == DIRECTORY_LOADER_ID) {  
  18.             mDirectoryListStatus = STATUS_LOADED;  
  19.             mAdapter.changeDirectories(data);  
  20.             Log.d(TAG, ”onLoadFinished startloading,loaderId:” + loaderId);  
  21.             startLoading();  
  22.         } else {  
  23.               
  24.             onPartitionLoaded(loaderId, data);  
  25.             if (isSearchMode()) {  
  26.                 int directorySearchMode = getDirectorySearchMode();  
  27.                 if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {  
  28.                     if (mDirectoryListStatus == STATUS_NOT_LOADED) {  
  29.                         mDirectoryListStatus = STATUS_LOADING;  
  30.                         getLoaderManager().initLoader(DIRECTORY_LOADER_ID, nullthis);  
  31.                     } else {  
  32.                         startLoading();  
  33.                     }  
  34.                 }  
  35.             } else {  
  36.                 mDirectoryListStatus = STATUS_NOT_LOADED;  
  37.                 getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);  
  38.             }  
  39. }  
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);
            }
}

最後將查詢結果傳遞給ContactEntryListFragment的onLoadFinished()方法:在onLoadFinished()中,通過onPartitionLoaded()對當前的Adapter所使用的Cursor進行更新,從而刷新列表。

下面總結一下搜索的流程說明:


注:本文參考了點擊打開鏈接,並添加和整理了一些內容大笑

























MTK的Dialer模塊聯繫人搜索     


      撥號搜索機制分爲兩個部分引導搜索和搜索。其中引導搜索是指,從用戶輸入到開始搜索之間的流程,而搜索部分是指,從數據庫搜索字符串的過程。

一、引導搜索部分

     默認的撥號界面的佈局從上到下主要分爲3個部分:顯示列表、數字編輯框、撥號鍵盤。他們的作用是:用戶直接在撥號鍵盤上輸入數字,然後數字編輯框顯示所輸入的數字,同時在顯示列表中體現此時的搜索結果。如圖所示:

 

撥號界面佈局

   從流程上來講,需要撥號鍵盤將用戶點擊轉換爲按鍵事件並傳遞給編輯框,然後由編輯框傳遞給搜索框,再由搜索框傳遞給列表Fragment,然後在列表所加載的Adapter中體現當前的搜索結果。


搜索流程框圖

1.1、從撥號鍵盤到編輯框

       用戶在撥號鍵盤上的點擊的數字按鈕,都會在編輯框中體現出來,我們先來追蹤這一過程。每個撥號鍵盤按鈕都是DialpadKeyButton類型的View,他們繼承自FrameLayout,當遇到點擊事件時,就會觸發configureKeypadListeners()方法,在DialpadFragment.Java

  1. DialpadFragment.java  
DialpadFragment.java

  1. private void configureKeypadListeners(View fragmentView) {  
  2.         final int[] buttonIds = new int[] {R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,  
  3.                 R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.zero, R.id.pound};  
  4.   
  5.         View dialpadKey;  
  6.   
  7.         for (int i = 0; i < buttonIds.length; i++) {  
  8.             dialpadKey =  fragmentView.findViewById(buttonIds[i]);  
  9.             dialpadKey.setOnClickListener(this);  
  10.         }  
  11.   
  12.         // Long-pressing one button will initiate Voicemail.  
  13.         final View one = fragmentView.findViewById(R.id.one);  
  14.         one.setOnLongClickListener(this);  
  15.   
  16.         // Long-pressing zero button will enter ’+’ instead.  
  17.         final View zero = fragmentView.findViewById(R.id.zero);  
  18.         zero.setOnLongClickListener(this);  
  19.   
  20.    
  21.  // Long-pressing one button will initiate Voicemail.  
  22.         final View start = fragmentView.findViewById(R.id.star);  
  23.         start.setOnLongClickListener(this);  
  24.   
  25.         // Long-pressing zero button will enter ’+’ instead.  
  26.         final View pound = fragmentView.findViewById(R.id.pound);  
  27.         pound.setOnLongClickListener(this);  
  28. }  
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);然後在DialpadFragmentonClick()方法中,將當前的點擊事件轉換爲標準的按鍵輸入:

  1. @Override  DialpadFragment.java  
  2.     public void onClick(View view) {  
  3.         /** M: Prevent the event if dialpad is not shown. @{ */  
  4.         if (getActivity() != null  
  5.                 && !((DialtactsActivity)getActivity()).isDialpadShown()) {  
  6.             Log.d(TAG, ”onClick but dialpad is not shown, skip !!!”);  
  7.             return;  
  8.         }  
  9.         /** @} */  
  10.         switch (view.getId()) {  
  11.             case R.id.dialpad_floating_action_button:  
  12.                 mHaptic.vibrate();  
  13.                 handleDialButtonPressed();  
  14.                 break;  
  15.             case R.id.deleteButton: {  
  16.                 keyPressed(KeyEvent.KEYCODE_DEL);  
  17.                 break;  
  18.             }  
  19.             case R.id.digits: {  
  20.                 if (!isDigitsEmpty()) {  
  21.                     mDigits.setCursorVisible(true);  
  22.                 }  
  23.                 break;  
  24.             }  
  25.             case R.id.dialpad_overflow: {  
  26.                 /// M: for plug-in @{  
  27.                 ExtensionManager.getInstance().getDialPadExtension().constructPopupMenu(  
  28.                          mOverflowPopupMenu, mOverflowMenuButton, mOverflowPopupMenu.getMenu());  
  29.                 /// @}  
  30.                 mOverflowPopupMenu.show();  
  31.                 break;  
  32.             }  
  33.         //Added by duyuanfeng for Lenovo dialpad  
  34.                 case R.id.one: {  
  35.                     keyPressed(KeyEvent.KEYCODE_1);  
  36.                     break;  
  37.                 }  
  38.                 case R.id.two: {  
  39.                     keyPressed(KeyEvent.KEYCODE_2);  
  40.                     break;  
  41.                 }  
  42.            // ……  
  43.                 case R.id.star: {  
  44.                     keyPressed(KeyEvent.KEYCODE_STAR);  
  45.                     break;  
  46.                 }  
  47.         //End addition  
  48.             default: {  
  49.                 Log.wtf(TAG, ”Unexpected onClick() event from: ” + view);  
  50.                 return;  
  51.             }  
  52.         }  
  53. }  
@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()中將會把當前輸入傳遞給編輯框

  1. DialpadFragment.java  
DialpadFragment.java

  1. private void keyPressed(int keyCode) {  
  2.         if (getView() == null || getView().getTranslationY() != 0) {  
  3.             return;  
  4.         }  
  5.         mHaptic.vibrate();  
  6.         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);  
  7.         mDigits.onKeyDown(keyCode, event);  
  8.   
  9.         // If the cursor is at the end of the text we hide it.  
  10.         final int length = mDigits.length();  
  11.         if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {  
  12.             mDigits.setCursorVisible(false);  
  13.         }  
  14.         if(length >=128)//songhu add for cu320  
  15.             clearDialpad();  
  16. }  
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的相關方法:

  1. DialpadFragment.java  
DialpadFragment.java
  1. public void afterTextChanged(Editable input) {  
  2.         .  
  3.         if (!mDigitsFilledByIntent &&  
  4.                 SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {  
  5.               
  6.             mDigits.getText().clear();  
  7.         }  
  8.   
  9.         if (isDigitsEmpty()) {  
  10.             mDigitsFilledByIntent = false;  
  11.             mDigits.setCursorVisible(false);  
  12.         }  
  13.   
  14.         if (mDialpadQueryListener != null) {  
  15. //傳遞給mDialpadQueryListener  
  16.             mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());  
  17.         }  
  18.   
  19.         updateDeleteButtonEnabledState();  
  20.     }  
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中實現的

  1. DialtactsActivity.java  
DialtactsActivity.java
  1. public void onDialpadQueryChanged(String query) {  
  2.         if (mSmartDialSearchFragment != null) {  
  3.             mSmartDialSearchFragment.setAddToContactNumber(query);  
  4.         }  
  5.         final String normalizedQuery = SmartDialNameMatcher.normalizeNumber(query,  
  6.                 /* M: [MTK Dialer Search] use mtk enhance dialpad map */  
  7.                 DialerFeatureOptions.isDialerSearchEnabled() ?  
  8.                         SmartDialNameMatcher.SMART_DIALPAD_MAP  
  9.                         : SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);  
  10.   
  11.         if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {  
  12.             if (DEBUG) {  
  13.                 Log.d(TAG, ”onDialpadQueryChanged - new query: ” + query);  
  14.             }  
  15.             if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {  
  16.                   
  17.                 if (!TextUtils.isEmpty(normalizedQuery)) {  
  18.                     mPendingSearchViewQuery = normalizedQuery;  
  19.                 }  
  20.                 return;  
  21.             }  
  22. //傳遞給搜索框  
  23.             mSearchView.setText(normalizedQuery);  
  24.         }  
  25. }  
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(全局搜索,在actionbaredittext裏輸入號碼呈現結果集的fragment)用於顯示當時的搜索結果。對於最常用的用戶在撥號鍵盤輸入內容觸發的搜索,將會加載SmartDialSearchFragment。此時搜索框需要將要搜索的文本傳遞給SmartDialSearchFragment
   在搜索時,由於搜索框註冊了文本監聽器,所以將會觸發TextWatcher,此時需要暫存當前要搜索的文本,並進入搜索模式,然後再將搜索內容交給SmartDialSearchFragment

  1. @DialtactsActivity.java  
@DialtactsActivity.java
  1. /** 
  2. * Listener used to send search queries to the phone search fragment. 
  3. */  
  4. private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {  
  5.         @Override  
  6.         public void beforeTextChanged(CharSequence s, int start, int count, int after) {  
  7.         }  
  8.   
  9.   
  10.         @Override  
  11.         public void onTextChanged(CharSequence s, int start, int before, int count) {  
  12.             final String newText = s.toString();  
  13.             if (newText.equals(mSearchQuery)) {  
  14.                 // If the query hasn’t changed (perhaps due to activity being destroyed  
  15.                 // and restored, or user launching the same DIAL intent twice), then there is  
  16.                 // no need to do anything here.  
  17.                 return;  
  18.             }  
  19.             if (DEBUG) {  
  20.                 Log.d(TAG, ”onTextChange for mSearchView called with new query: ” + newText);  
  21.                 Log.d(TAG, ”Previous Query: ” + mSearchQuery);  
  22.             }  
  23.             mSearchQuery = newText;  
  24.   
  25.   
  26.             // 當搜索的字符串爲變成不爲空的時候顯示搜索界面  
  27.             if (!TextUtils.isEmpty(newText)) {  
  28.                 // Call enterSearchUi only if we are switching search modes, or showing a search  
  29.                 // fragment for the first time.  
  30.                 final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||  
  31.                         (!mIsDialpadShown && mInRegularSearch);  
  32.                 if (!sameSearchMode) {  
  33.                     enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);  
  34.                 }  
  35.             }  
  36.   
  37. //選擇不同的搜索模式  
  38.             if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {  
  39.                 mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);  
  40.             } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {  
  41.                 mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);  
  42.             }  
  43.         }  
  44.         @Override  
  45.         public void afterTextChanged(Editable s) {  
  46.         }  
  47. };   
/**
* 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

先來看一下SmartDialSearchFragment的繼承關係:
    SmartDialSearchFragment
            —-SearchFragment
                —-PhoneNumberPickerFragment
                    —-ContactEntryListFragment<ContactEntryListAdapter>
                        —-Fragment
       SmartDialSearchFragment拿到搜索的文本後,需要傳遞給自己的Adapter才能完成搜索任務,我們現在來分析這個交接的過程。從上面1.3節中我們看到,SmartDialSearchFragment通過setQueryString()拿到了要搜索的字串,我們來查看這個方法,他是在SmartDialSearchFragment的父類ContactEntryListFragment中被實現的:
  1. ContactEntryListFragment.java  
ContactEntryListFragment.java
  1. public void setQueryString(String queryString, boolean delaySelection) {  
  2.         if (!TextUtils.equals(mQueryString, queryString)) {  
  3.             if (mShowEmptyListForEmptyQuery && mAdapter != null && mListView != null) {  
  4.                 if (TextUtils.isEmpty(mQueryString)) {  
  5.                     // Restore the adapter if the query used to be empty.  
  6.                     mListView.setAdapter(mAdapter);  
  7.                 } else if (TextUtils.isEmpty(queryString)) {  
  8.                     // Instantly clear the list view if the new query is empty.  
  9.                     mListView.setAdapter(null);  
  10.                 }  
  11.             }  
  12.   
  13.             mQueryString = queryString;  
  14.             setSearchMode(!TextUtils.isEmpty(mQueryString) || mShowEmptyListForEmptyQuery);  
  15.   
  16.             if (mAdapter != null) {  
  17.                //傳遞給Adapter  
  18.                 mAdapter.setQueryString(queryString);  
  19. //觸發Adapter重新搜索             
  20. reloadData();  
  21.             }  
  22.         }  
  23. }  
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:
  1. SmartDialSearchFragment.java  
SmartDialSearchFragment.java

  1. @Override  
  2.     protected ContactEntryListAdapter createListAdapter() {  
  3.         SmartDialNumberListAdapter adapter = new SmartDialNumberListAdapter(getActivity());  
  4.         adapter.setUseCallableUri(super.usesCallableUri());  
  5.         adapter.setQuickContactEnabled(true);  
  6.         // Set adapter’s query string to restore previous instance state.  
  7.         adapter.setQueryString(getQueryString());  
  8.         return adapter;  
  9. }  
@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的搜索,我們來看一下這個流程:
  1. ContactEntryListFragment.java  
ContactEntryListFragment.java

  1. protected void reloadData() {  
  2.         removePendingDirectorySearchRequests();  
  3.         mAdapter.onDataReload();  
  4.         mLoadPriorityDirectoriesOnly = true;  
  5.         mForceLoad = true;  
  6.     //觸發新的Adapter  
  7.         startLoading();  
  8.     }  
  9.     protected void startLoading() {  
  10.         Log.d(TAG, ”startLoading”);  
  11.         if (mAdapter == null) {  
  12.             // The method was called before the fragment was started  
  13.             Log.d(TAG, ”[statLoading] mAdapter is null”);  
  14.             return;  
  15.         }  
  16.     //配置Adapter要搜索的文本  
  17.         configureAdapter();  
  18.         int partitionCount = mAdapter.getPartitionCount();  
  19.         for (int i = 0; i < partitionCount; i++) {  
  20.             Partition partition = mAdapter.getPartition(i);  
  21.             if (partition instanceof DirectoryPartition) {  
  22.                 DirectoryPartition directoryPartition = (DirectoryPartition)partition;  
  23.                 if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {  
  24.                     if (directoryPartition.isPriorityDirectory() || !mLoadPriorityDirectoriesOnly) {  
  25.                         startLoadingDirectoryPartition(i);  
  26.                     }  
  27.                 }  
  28.             } else {  
  29. //通過LoaderManager進行異步查詢  
  30.                 getLoaderManager().initLoader(i, nullthis);  
  31.             }  
  32.         }  
  33.         // Next time this method is called, we should start loading non-priority directories  
  34.         mLoadPriorityDirectoriesOnly = false;  
  35. }  
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配置了要搜索的文本、排序方法以及顯示主題等信息,由於
  1. *partition instanceof DirectoryPartition = true  
*partition instanceof DirectoryPartition = true

因此就會執行startLoadingDirectoryPartition()方法:


  1. ContactEntryListFragment.java  
  2. private void startLoadingDirectoryPartition(int partitionIndex) {  
  3.         DirectoryPartition partition = (DirectoryPartition)mAdapter.getPartition(partitionIndex);  
  4.         partition.setStatus(DirectoryPartition.STATUS_LOADING);  
  5.         long directoryId = partition.getDirectoryId();  
  6.         if (mForceLoad) {  
  7.             if (directoryId == Directory.DEFAULT) {  
  8.                 loadDirectoryPartition(partitionIndex, partition);  
  9.             } else {  
  10.                 loadDirectoryPartitionDelayed(partitionIndex, partition);  
  11.             }  
  12.         } else {  
  13.             Bundle args = new Bundle();  
  14.             args.putLong(DIRECTORY_ID_ARG_KEY, directoryId);  
  15.             getLoaderManager().initLoader(partitionIndex, args, this);  
  16.         }  
  17. }  
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()方法:
  1. SmartDialSearchFragments.java  
SmartDialSearchFragments.java
  1. public Loader<Cursor> onCreateLoader(int id, Bundle args) {  
  2.         // Smart dialing does not support Directory Load, falls back to normal search instead.  
  3.         if (id == getDirectoryLoaderId()) {  
  4.             return super.onCreateLoader(id, args);  
  5.         } else {  
  6.             final SmartDialNumberListAdapter adapter = (SmartDialNumberListAdapter) getAdapter();  
  7.             /// M: [MTK Dialer Search] @{  
  8.             if (DialerFeatureOptions.isDialerSearchEnabled()) {  
  9.                 DialerSearchCursorLoader loader = new DialerSearchCursorLoader(super.getContext(),  
  10.                         usesCallableUri());  
  11.                 adapter.configureLoader(loader);  
  12.                 return loader;  
  13.             /// @}  
  14.             } else {  
  15. //創建當前的CursorLoader,也就是SmartDialCursorLoader  
  16.                 SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext());  
  17.                 adapter.configureLoader(loader);  
  18.                 return loader;  
  19.             }  
  20.         }  
  21. }  
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中的查詢流程:
  1. DialerSearchCursouLoader.java      
  2.  /** 
  3.      * Configures the query string to be used to find SmartDial matches. 
  4.      * @param query The query string user typed. 
  5.      */  
  6.     public void configureQuery(String query, boolean isSmartQuery) {  
  7.   
  8.   
  9.         Log.d(TAG, ”MTK-DialerSearch, Configure new query to be ” + query);  
  10.   
  11.   
  12.         mQuery = query;  
  13.     //搜索模式  isSmartQuery  
  14.         if (!isSmartQuery) {  
  15.             mQuery = DialerSearchUtils.stripTeleSeparators(query);  
  16.         }  
  17.     //判斷字符串是否合法  
  18.         if (!DialerSearchUtils.isValidDialerSearchString(mQuery)) {  
  19.             mEnableDefaultSearch = true;  
  20.         }  
  21.     }  
  22.   
  23.   
  24.     /** 
  25.      * Queries the Contacts database and loads results in background. 
  26.      * @return Cursor of contacts that matches the SmartDial query. 
  27.      */  
  28.     @Override  
  29.     public Cursor loadInBackground() {  
  30.   
  31.         Log.d(TAG, ”MTK-DialerSearch, Load in background. mQuery: ” + mQuery);  
  32.   
  33.         final DialerSearchHelper dialerSearchHelper = DialerSearchHelper.getInstance(mContext);  
  34.         Cursor cursor = null;  
  35.         if (mEnableDefaultSearch) {  
  36.             cursor = dialerSearchHelper.getRegularDialerSearchResults(mQuery, mUseCallableUri);  
  37.         } else {  
  38.             cursor = dialerSearchHelper.getSmartDialerSearchResults(mQuery);  
  39.         }  
  40.         if (cursor != null) {  
  41.             Log.d(TAG, ”MTK-DialerSearch, loadInBackground, result.getCount: ”  
  42.                     + cursor.getCount());  
  43.   
  44.             return cursor;  
  45.         } else {  
  46.             Log.w(TAG, ”MTK-DialerSearch, —-cursor is null—-“);  
  47.             return null;  
  48.         }  
  49. }  
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.getSmartDialerSearchResults()得到cursor的。

  1. @DialerSearchHelper.java  
  2. /** 
  3. * Query dialerSearch results from contactsProvider, use MTK algorithm. 
  4. * @param query 
  5. * @return DialerSearch result. 
  6. */  
  7. public Cursor getSmartDialerSearchResults(String query) {  
  8.         Log.d(TAG, ”MTK-DialerSearch, getSmartDialerSearchResults, queryFilter: ” + query);  
  9.   
  10.         if (TextUtils.isEmpty(query) || query.length() >= 128) {//songhu add for search anr  
  11.             return null;  
  12.         }  
  13.   
  14.         final ContentResolver resolver = mContext.getContentResolver();  
  15.         Cursor cursor = null;  
  16.         try {  
  17.             int displayOrder = sContactsPrefs.getDisplayOrder();  
  18.             int sortOrder = sContactsPrefs.getSortOrder();  
  19.         //設置Uri的路徑  
  20.             Uri baseUri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, ”dialer_search”);  
  21.         //設置Uri的搜索文本  
  22.             Uri dialerSearchUri = baseUri.buildUpon().appendPath(query).build();  
  23.             Log.d(TAG, ”MTK-DialerSearch, displayOrder: ” + displayOrder + “ ,sortOrder: ”  
  24.                     + sortOrder);  
  25.         //在Uri的path中加入兩個鍵值對,並根據參數查詢字符串  
  26.             Uri dialerSearchParamUri = dialerSearchUri.buildUpon().appendQueryParameter(  
  27.                     ContactsContract.Preferences.DISPLAY_ORDER, String.valueOf(displayOrder))  
  28.                     .appendQueryParameter(ContactsContract.Preferences.SORT_ORDER,  
  29.                             String.valueOf(sortOrder)).build();  
  30.   
  31.             cursor = resolver.query(dialerSearchParamUri, nullnullnullnull);  
  32.   
  33.             Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + String.valueOf(displayOrder)+“SORT_ORDER =”+String.valueOf(sortOrder));  
  34.   
  35.         Log.d(TAG, ”liuhuan DISPLAY_ORDER= ” + ContactsContract.Preferences.DISPLAY_ORDER+“SORT_ORDER =”+ContactsContract.Preferences.SORT_ORDER);  
  36.   
  37.             Log.d(TAG, ”MTK-DialerSearch, cursor.getCount: ” + cursor.getCount());  
  38.   
  39.             return cursor;  
  40.         } catch (Exception e) {  
  41.             Log.w(TAG, ”Exception thrown in MTK-DialerSearch, getSmartDialerSearchResults”, e);  
  42.   
  43.             if (cursor != null) {  
  44.                 cursor.close();  
  45.                 cursor = null;  
  46.             }  
  47.             return null;  
  48.         }  
  49. }  
@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

appendQueryParameter(String ,String);這個方法的官方解釋爲:Encodes the key and value and then appends the parameter to the querystring.官方解釋鏈接:點擊打開鏈接我的理解就是在Uri中將加入一個鍵值對如(name,faker);就查詢name是faker的數據,最後查詢的時候是調用resolver.query()。resolver 是通過getContentResolver得來的,ContentResolver是直譯爲內容解析器,在android中程序間的數據共享是通過Provider/Reslover,提供數據(內容)的就是Provider,Reslover就提供接口對這個數據進行解讀,根據Android官方文檔,query方法的解釋爲:

public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, StringsortOrder){}

第一個參數爲 Uri,android中有很多reslover,爲了區分這些reslover,就需要每個reslover都有一個獨有的標識,而這個Uri就是這個標識;
第二個參數爲 projection,就是要獲取到reslover中的數據的哪些內容,必須聯繫人有name和id,如果只想得到聯繫人的name,那麼就可以設置這個參數:當設置爲null的時候就是獲取reslover中的所有內容;
第三個參數爲 selection :設置條件,比如,我只想得到reslover中,聯繫人爲Faker的相關信息;
第四個參數爲 selectionArgs:這個是配合第三個參數使用的,如果第三個參數中有?,那麼第四個參數就會替換第三個參數;
第五個參數爲 sortOrder:這個是設置reslover中的數據按照什麼排序;
參考文檔:點擊打開鏈接

  1. @ContactEntryListFragment.java  
@ContactEntryListFragment.java
  1. public void onLoadFinished(Loader<Cursor> loader, Cursor data) {  
  2.         Log.d(TAG, ”[onLoadFinished] loader:” + loader + “,data:” + data);  
  3.       /// M: check whether the fragment still in Activity @{  
  4.         if (!isAdded()) {  
  5.             Log.d(TAG, ”onLoadFinished(),This Fragment is not add to the Activity now.data:”  
  6.                     + data);  
  7.             return;  
  8.         }  
  9.         /// @}  
  10.   
  11.         if (!mEnabled) {  
  12.             Log.d(TAG, ”return in onLoad finish,mEnabled:” + mEnabled);  
  13.             return;  
  14.         }  
  15.   
  16.         int loaderId = loader.getId();  
  17.         if (loaderId == DIRECTORY_LOADER_ID) {  
  18.             mDirectoryListStatus = STATUS_LOADED;  
  19.             mAdapter.changeDirectories(data);  
  20.             Log.d(TAG, ”onLoadFinished startloading,loaderId:” + loaderId);  
  21.             startLoading();  
  22.         } else {  
  23.               
  24.             onPartitionLoaded(loaderId, data);  
  25.             if (isSearchMode()) {  
  26.                 int directorySearchMode = getDirectorySearchMode();  
  27.                 if (directorySearchMode != DirectoryListLoader.SEARCH_MODE_NONE) {  
  28.                     if (mDirectoryListStatus == STATUS_NOT_LOADED) {  
  29.                         mDirectoryListStatus = STATUS_LOADING;  
  30.                         getLoaderManager().initLoader(DIRECTORY_LOADER_ID, nullthis);  
  31.                     } else {  
  32.                         startLoading();  
  33.                     }  
  34.                 }  
  35.             } else {  
  36.                 mDirectoryListStatus = STATUS_NOT_LOADED;  
  37.                 getLoaderManager().destroyLoader(DIRECTORY_LOADER_ID);  
  38.             }  
  39. }  
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);
            }
}

最後將查詢結果傳遞給ContactEntryListFragment的onLoadFinished()方法:在onLoadFinished()中,通過onPartitionLoaded()對當前的Adapter所使用的Cursor進行更新,從而刷新列表。

下面總結一下搜索的流程說明:


注:本文參考了點擊打開鏈接,並添加和整理了一些內容大笑

























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章