Android dial application source code analysis

Keywords: Java Android source code

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.

Posted by zszucs on Tue, 16 Nov 2021 22:54:57 -0800