Engineering dependency
com.android.dialer is the main project and depends on
com.android.contacts.common project and com.android.phone.common project
com.android.contacts.common depends on
com.android.phone.common project and com.android.common project
In addition, some support packages are also introduced as link projects. The above codes are taken from google source code
Key class
DialtactsActivity
com.android.dialer.DialtactsActivity public class DialtactsActivity extends TransactionSafeActivity . . . { // Fragment containing the dialpad that slides into view protected DialpadFragment mDialpadFragment; // Fragment for searching phone numbers using the alphanumeric keyboard. private RegularSearchFragment mRegularSearchFragment; // Fragment for searching phone numbers using the dialpad. private SmartDialSearchFragment mSmartDialSearchFragment; // Fragment containing the speed dial list, call history list, and all contacts list. private ListsFragment mListsFragment; private DialerDatabaseHelper mDialerDatabaseHelper; private FloatingActionButtonController mFloatingActionButtonController; ...... ...... ...... ..... }
There are mainly the following key member variables
com.android.dialer.dialpad .DialpadFragment // Dial fragment
com.android.dialer.list.RegularSearchFragment // Contact search fragment
com.android.dialer.list.SmartDialSearchFragment // Dial up search fragment
com.android.dialer.list.ListsFragment // The TAB page fragment contains three sub fragments: quick contact, recent call record and contact list
com.android.dialer.database.DialerDatabaseHelper // Dial up search database SQLiteOpenHelper object
com.android.contacts.common.widget.FloatingActionButtonController // Suspension button controller
Let's look at the main implementation in onCreate
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.dialtacts_activity); final ActionBar actionBar = getSupportActionBar(); actionBar.setCustomView(R.layout.search_edittext); // Set custom view (SearchEditTextLayout) for actionbar SearchEditTextLayout searchEditTextLayout = (SearchEditTextLayout) actionBar .getCustomView().findViewById(R.id.search_view_container); // Add manager ActionBarController to SearchEditTextLayout mActionBarController = new ActionBarController(this, searchEditTextLayout); final View floatingActionButtonContainer = findViewById( R.id.floating_action_button_container); ImageButton floatingActionButton = (ImageButton) findViewById(R.id.floating_action_button); floatingActionButton.setOnClickListener(this); // Managing hover buttons with FloatingActionButtonController mFloatingActionButtonController = new FloatingActionButtonController(this, floatingActionButtonContainer, floatingActionButton); // Add ListsFragment getFragmentManager().beginTransaction() .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT) .commit(); // Initializing the singleton dialerdatabase helper mDialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(this); SmartDialPrefix.initializeNanpSettings(this); }
ListsFragment
ListsFragment is the main fragment with the following structure:
public class ListsFragment extends Fragment{ private ViewPager mViewPager; private ViewPagerTabs mViewPagerTabs; // Custom TAB label, inherited from HorizontalScrollView private ViewPagerAdapter mViewPagerAdapter; // Hover view when dragging favorite contacts private RemoveView mRemoveView; private View mRemoveViewContent; // Favorite contact fragment private SpeedDialFragment mSpeedDialFragment; // Recent call record fragment private CallLogFragment mHistoryFragment; // Contact list fragment private AllContactsFragment mAllContactsFragment; // Voicemail list fragment private CallLogFragment mVoicemailFragment; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View parentView = inflater.inflate(R.layout.lists_fragment, container, false); mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager); mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager()); mViewPager.setAdapter(mViewPagerAdapter); mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1); mViewPager.setOnPageChangeListener(this); showTab(TAB_INDEX_SPEED_DIAL); ...... ...... ...... ...... mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header); mViewPagerTabs.configureTabIcons(mTabIcons); mViewPagerTabs.setViewPager(mViewPager); addOnPageChangeListener(mViewPagerTabs); mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view); mRemoveViewContent = parentView.findViewById(R.id.remove_view_content); return parentView; } }
ListsFragment can display up to four fragments, and one VisualVoicemailCallLogFragment displays a specific call record (providing video and voice mail service)
The type is Calls.VOICEMAIL_TYPE, which needs to be supported by the operator. This TAB page will be displayed only if such call records exist. Domestic operators do not support it temporarily
OldSpeedDialFragment displays a list of frequently used contacts
// LoaderCallbacks for querying source data private static final class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> { private final OldSpeedDialFragment fragment; // Source data BaseAdapter private final PhoneFavoritesTileAdapter adapter; ContactTileLoaderListener(OldSpeedDialFragment fragment, PhoneFavoritesTileAdapter adapter) { this.fragment = fragment; this.adapter = adapter; } @Override public CursorLoader onCreateLoader(int id, Bundle args) { return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(fragment.getContext()); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { adapter.setContactCursor(data); fragment.setEmptyViewVisibility(adapter.getCount() == 0); FragmentUtils.getParentUnsafe(fragment, HostInterface.class) .setHasFrequents(adapter.getNumFrequents() > 0); } @Override public void onLoaderReset(Loader<Cursor> loader) {} }
DialpadFragment
DialpadFragment displays the dial fragment
Add the following in DialtactsActivity
private void showDialpadFragment(boolean animate) { if (mDialpadFragment == null) { mDialpadFragment = new DialpadFragment(); ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT); } else { ft.show(mDialpadFragment); } }
It is dynamically added during the first display, and the subsequent dynamic control is displayed and hidden
public class DialpadFragment extends Fragment{ private DialpadView mDialpadView; // Dial number panel (including number entry box) private EditText mDigits; // Enter number box private ToneGenerator mToneGenerator; // DTMF audio player private ListView mDialpadChooser; // View displayed during call status private DialpadChooserAdapter mDialpadChooserAdapter; // View adapter displayed during call status @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { // Load different layouts horizontally and vertically final View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, false); fragmentView.buildLayer(); mDialpadView = (DialpadView) fragmentView.findViewById(R.id.dialpad_view); mDialpadView.setCanDigitsBeEdited(true); mDigits = mDialpadView.getDigits(); ...... ........... ...... PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits); // Format the number in the input box // Check for the presence of the keypad View oneButton = fragmentView.findViewById(R.id.one); if (oneButton != null) { // Bind the onPress event of each digital key configureKeypadListeners(fragmentView); } ...... ............ ...... mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser); mDialpadChooser.setOnItemClickListener(this); ...... ..... ...... ...... return fragmentView; } }
The dialing panel layout loaded by horizontal screen and vertical screen is different
DialpadView is a custom view, which is mainly used to display numeric keys and enter number boxes
public class DialpadView extends LinearLayout { private EditText mDigits; // Enter number box private ImageButton mDelete; // Delete button private void setupKeypad() { ...... ............ ...... DialpadKeyButton dialpadKey; TextView numberView; TextView lettersView; ...... ............ ...... for (int i = 0; i < mButtonIds.length; i++) { dialpadKey = (DialpadKeyButton) findViewById(mButtonIds[i]); numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number); lettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters); ...... ............ ...... final RippleDrawable rippleBackground = (RippleDrawable) getDrawableCompat(getContext(), R.drawable.btn_dialpad_key); if (mRippleColor != null) { rippleBackground.setColor(mRippleColor); } numberView.setText(numberString); numberView.setElegantTextHeight(false); dialpadKey.setContentDescription(numberContentDescription); dialpadKey.setBackground(rippleBackground); // Set the background color of digital key water ripple if (lettersView != null) { lettersView.setText(resources.getString(letterIds[i])); } } ...... ............ ...... }
public void animateShow() { // Animation effect of each numeric key when displaying the dial panel ...... ............ ...... for (int i = 0; i < mButtonIds.length; i++) { ...... ............ ...... ViewPropertyAnimator animator = dialpadKey.animate(); if (mIsLandscape) { // Landscape orientation requires translation along the X axis. // For RTL locales, ensure we translate negative on the X axis. dialpadKey.setTranslationX((mIsRtl ? -1 : 1) * mTranslateDistance); animator.translationX(0); } else { // Portrait orientation requires translation along the Y axis. dialpadKey.setTranslationY(mTranslateDistance); animator.translationY(0); } animator.setInterpolator(AnimUtils.EASE_OUT_EASE_IN) .setStartDelay(delay) .setDuration(duration) .setListener(showListener) .start(); } } }
SmartDialSearchFragment RegularSearchFragment
SmartDialSearchFragment displays the dialing search result fragment (displayed when a number is entered in the dialing panel)
RegularSearchFragment displays the contact search result fragment (displayed when characters are entered in the actionbar input box)
Add and remove dynamically when entering or exiting search mode in DialtactsActivity
private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) { ...... ............ ...... if (fragment == null) { if (smartDialSearch) { fragment = new SmartDialSearchFragment(); } else { fragment = ObjectFactory.newRegularSearchFragment(); ...... ............ ...... } transaction.add(R.id.dialtacts_frame, fragment, tag); } else { transaction.show(fragment); } ...... ............ ...... } private void exitSearchUi() { ...... ............ ...... final FragmentTransaction transaction = getFragmentManager().beginTransaction(); if (mSmartDialSearchFragment != null) { transaction.remove(mSmartDialSearchFragment); } if (mRegularSearchFragment != null) { transaction.remove(mRegularSearchFragment); } transaction.commit(); mListsFragment.getView().animate().alpha(1).withLayer(); ...... ............ ...... mActionBarController.onSearchUiExited(); }
Dial search elements can only enter numbers through the dial panel. They support T9 search, but the native does not support Pinyin retrieval
public class SmartDialSearchFragment extends SearchFragment{ @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()); adapter.setListener(this); return adapter; } @Override 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(); SmartDialCursorLoader loader = new SmartDialCursorLoader(super.getContext()); adapter.configureLoader(loader); return loader; } } }
Contact search is entered through the soft keyboard, but T9 search is not supported
public class RegularSearchFragment extends SearchFragment{ @Override protected ContactEntryListAdapter createListAdapter() { RegularSearchListAdapter adapter = new RegularSearchListAdapter(getActivity()); adapter.setDisplayPhotos(true); adapter.setUseCallableUri(usesCallableUri()); adapter.setListener(this); return adapter; } }
It can be seen from the class diagram that the two fragments and the corresponding adapter inherit from the same parent class, and finally derive from the template class ContactEntryListFragment in the ContactsCommon project
public abstract class ContactEntryListFragment<T extends ContactEntryListAdapter> extends Fragment{ private T mAdapter; // Template adapter private View mView; private ListView mListView; private ContactPhotoManager mPhotoManager; // Avatar management protected abstract View inflateView(LayoutInflater inflater, ViewGroup container); protected abstract T createListAdapter(); // Implement the concrete adapter in the subclass @Override // Subclasses override loaders that get data public Loader<Cursor> onCreateLoader(int id, Bundle args) { if (id == DIRECTORY_LOADER_ID) { DirectoryListLoader loader = new DirectoryListLoader(mContext); loader.setDirectorySearchMode(mAdapter.getDirectorySearchMode()); loader.setLocalInvisibleDirectoryEnabled( ContactEntryListAdapter.LOCAL_INVISIBLE_DIRECTORY_ENABLED); return loader; } else { CursorLoader loader = createCursorLoader(mContext); long directoryId = args != null && args.containsKey(DIRECTORY_ID_ARG_KEY) ? args.getLong(DIRECTORY_ID_ARG_KEY) : Directory.DEFAULT; mAdapter.configureLoader(loader, directoryId); return loader; } } }
The ContactEntryListFragment internally encapsulates many operations and binds the ContactEntryListAdapter. The specific details are not detailed here.