Android-mvp模式

Android应用开发中,mvp模式是目前比较流行的设计模式。
三层依赖关系如下图:
三层依赖关系
本文记录一下通话界面的对于MVP设计模式的使用。
1.V层即view层的接口定义
(1)接口Ui定义

package com.android.incallui.baseui;

/** Base class for all presenter ui. */
public interface Ui {}

(2)BaseFragment定义,这也是在V层,是所有使用Presenter 和Ui的父亲fragment

package com.android.incallui.baseui;

import android.os.Bundle;
import android.support.v4.app.Fragment;

/** Parent for all fragments that use Presenters and Ui design. */
public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment {
private static final String KEY_FRAGMENT_HIDDEN = "key_fragment_hidden";

  private T presenter;

  protected BaseFragment() {
    presenter = createPresenter();
  }

  public abstract T createPresenter();

  public abstract U getUi();
  
/**
   * Presenter will be available after onActivityCreated().
   *
   * @return The presenter associated with this fragment.
   */
  public T getPresenter() {
    return presenter;
  }

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    presenter.onUiReady(getUi());
  }
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
      presenter.onRestoreInstanceState(savedInstanceState);
      if (savedInstanceState.getBoolean(KEY_FRAGMENT_HIDDEN)) {
        getFragmentManager().beginTransaction().hide(this).commit();
      }
    }
  }
  
  @Override
  public void onDestroyView() {
    super.onDestroyView();
    presenter.onUiDestroy(getUi());
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    presenter.onSaveInstanceState(outState);
    outState.putBoolean(KEY_FRAGMENT_HIDDEN, isHidden());
  }
}

(3)V层接口实现类定义
DialpadFragment 类定义

package com.android.incallui;

import android.content.Context;
import android.os.Bundle;
import android.telephony.PhoneNumberUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.dialer.common.LogUtil;
import com.android.dialer.dialpadview.DialpadKeyButton;
import com.android.dialer.dialpadview.DialpadKeyButton.OnPressedListener;
import com.android.dialer.dialpadview.DialpadView;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.incallui.DialpadPresenter.DialpadUi;
import com.android.incallui.baseui.BaseFragment;
import java.util.Map;

/** Fragment for call control buttons */
public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadUi>
    implements DialpadUi, OnKeyListener, OnClickListener, OnPressedListener {
    
    /** Hash Map to map a view id to a character */
  private static final Map<Integer, Character> displayMap = new ArrayMap<>();
  /** Set up the static maps */
  static {
    // Map the buttons to the display characters
    displayMap.put(R.id.one, '1');
    displayMap.put(R.id.two, '2');
    displayMap.put(R.id.three, '3');
    displayMap.put(R.id.four, '4');
    displayMap.put(R.id.five, '5');
    displayMap.put(R.id.six, '6');
    displayMap.put(R.id.seven, '7');
    displayMap.put(R.id.eight, '8');
    displayMap.put(R.id.nine, '9');
    displayMap.put(R.id.zero, '0');
    displayMap.put(R.id.pound, '#');
    displayMap.put(R.id.star, '*');
  }
  
  private final int[] buttonIds =
      new int[] {
        R.id.zero,
        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.pound
      };
      
      private EditText dtmfDialerField;
  // KeyListener used with the "dialpad digits" EditText widget.
  private DtmfKeyListener dtmfKeyListener;
  private DialpadView dialpadView;
  private int currentTextColor;
  
  @Override
  public void onClick(View v) {
    if (v.getId() == R.id.dialpad_back) {
      Logger.get(getContext())
          .logImpression(DialerImpression.Type.IN_CALL_DIALPAD_CLOSE_BUTTON_PRESSED);
      getActivity().onBackPressed();
    }
  }
  @Override
  public boolean onKey(View v, int keyCode, KeyEvent event) {
    Log.d(this, "onKey:  keyCode " + keyCode + ", view " + v);

    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
      int viewId = v.getId();
      if (displayMap.containsKey(viewId)) {
        switch (event.getAction()) {
          case KeyEvent.ACTION_DOWN:
            if (event.getRepeatCount() == 0) {
              getPresenter().processDtmf(displayMap.get(viewId));
            }
            break;
          case KeyEvent.ACTION_UP:
            getPresenter().stopDtmf();
            break;
          default: // fall out
        }
        // do not return true [handled] here, since we want the
        // press / click animation to be handled by the framework.
      }
    }
    return false;
  }
  @Override
  public DialpadPresenter createPresenter() {
    return new DialpadPresenter();
  }

  @Override
  public DialpadPresenter.DialpadUi getUi() {
    return this;
  }

  // TODO(klp) Adds hardware keyboard listener
  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    final View parent = inflater.inflate(R.layout.incall_dialpad_fragment, container, false);
    dialpadView = (DialpadView) parent.findViewById(R.id.dialpad_view);
    dialpadView.setCanDigitsBeEdited(false);
    dialpadView.setBackgroundResource(R.color.incall_dialpad_background);
    dtmfDialerField = (EditText) parent.findViewById(R.id.digits);
    if (dtmfDialerField != null) {
      LogUtil.i("DialpadFragment.onCreateView", "creating dtmfKeyListener");
      dtmfKeyListener = new DtmfKeyListener(getPresenter());
      dtmfDialerField.setKeyListener(dtmfKeyListener);
      // remove the long-press context menus that support
      // the edit (copy / paste / select) functions.
      dtmfDialerField.setLongClickable(false);
      dtmfDialerField.setElegantTextHeight(false);
      configureKeypadListeners();
    }
    View backButton = dialpadView.findViewById(R.id.dialpad_back);
    backButton.setVisibility(View.VISIBLE);
    backButton.setOnClickListener(this);

    return parent;
  }
  
  @Override
  public void onResume() {
    super.onResume();
    updateColors();
  }
  
  public void updateColors() {
    int textColor = InCallPresenter.getInstance().getThemeColorManager().getPrimaryColor();

    if (currentTextColor == textColor) {
      return;
    }

    DialpadKeyButton dialpadKey;
    for (int i = 0; i < buttonIds.length; i++) {
      dialpadKey = (DialpadKeyButton) dialpadView.findViewById(buttonIds[i]);
      ((TextView) dialpadKey.findViewById(R.id.dialpad_key_number)).setTextColor(textColor);
    }

    currentTextColor = textColor;
  }
  
  @Override
  public void onDestroyView() {
    dtmfKeyListener = null;
    super.onDestroyView();
  }
  
  /**
   * Getter for Dialpad text.
   *
   * @return String containing current Dialpad EditText text.
   */
  public String getDtmfText() {
    return dtmfDialerField.getText().toString();
  }

  /**
   * Sets the Dialpad text field with some text.
   *
   * @param text Text to set Dialpad EditText to.
   */
  public void setDtmfText(String text) {
    dtmfDialerField.setText(PhoneNumberUtils.createTtsSpannable(text));
    /// M: ALPS03175530 set the focus to the end of the text
    dtmfDialerField.setSelection(dtmfDialerField.length());
  }

  /** Starts the slide up animation for the Dialpad keys when the Dialpad is revealed. */
  public void animateShowDialpad() {
    final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
    dialpadView.animateShow();
  }
  
  @Override
  public void appendDigitsToField(char digit) {
    if (dtmfDialerField != null) {
      // TODO: maybe *don't* manually append this digit if
      // mDialpadDigits is focused and this key came from the HW
      // keyboard, since in that case the EditText field will
      // get the key event directly and automatically appends
      // whetever the user types.
      // (Or, a cleaner fix would be to just make mDialpadDigits
      // *not* handle HW key presses.  That seems to be more
      // complicated than just setting focusable="false" on it,
      // though.)
      dtmfDialerField.getText().append(digit);
    }
  }
  
  /** Called externally (from InCallScreen) to play a DTMF Tone. */
  /* package */ boolean onDialerKeyDown(KeyEvent event) {
    Log.d(this, "Notifying dtmf key down.");
    if (dtmfKeyListener != null) {
      return dtmfKeyListener.onKeyDown(event);
    } else {
      return false;
    }
  }
  
  /** Called externally (from InCallScreen) to cancel the last DTMF Tone played. */
  public boolean onDialerKeyUp(KeyEvent event) {
    Log.d(this, "Notifying dtmf key up.");
    if (dtmfKeyListener != null) {
      return dtmfKeyListener.onKeyUp(event);
    } else {
      return false;
    }
  }
  
  private void configureKeypadListeners() {
    DialpadKeyButton dialpadKey;
    for (int i = 0; i < buttonIds.length; i++) {
      dialpadKey = (DialpadKeyButton) dialpadView.findViewById(buttonIds[i]);
      dialpadKey.setOnKeyListener(this);
      dialpadKey.setOnClickListener(this);
      dialpadKey.setOnPressedListener(this);
    }
  }
  
  @Override
  public void onPressed(View view, boolean pressed) {
    if (pressed && displayMap.containsKey(view.getId())) {
      Logger.get(getContext())
          .logImpression(DialerImpression.Type.IN_CALL_DIALPAD_NUMBER_BUTTON_PRESSED);
      Log.d(this, "onPressed: " + pressed + " " + displayMap.get(view.getId()));
      getPresenter().processDtmf(displayMap.get(view.getId()));
    }
    if (!pressed) {
      Log.d(this, "onPressed: " + pressed);
      getPresenter().stopDtmf();
    }
  }
  
  /**
   * LinearLayout with getter and setter methods for the translationY property using floats, for
   * animation purposes.
   */
  public static class DialpadSlidingLinearLayout extends LinearLayout {

    public DialpadSlidingLinearLayout(Context context) {
      super(context);
    }

    public DialpadSlidingLinearLayout(Context context, AttributeSet attrs) {
      super(context, attrs);
    }
    
    public DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
      super(context, attrs, defStyle);
    }

    public float getYFraction() {
      final int height = getHeight();
      if (height == 0) {
        return 0;
      }
      return getTranslationY() / height;
    }
    
    public void setYFraction(float yFraction) {
      setTranslationY(yFraction * getHeight());
    }
  }

  /// M: ------------------------------- MediaTek feature ---------------------------
  /**
   * M: Used for making the fragment's state is latest.
   * @param hidden Dialpad fragment is hidden or show
   */
  @Override
  public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (!hidden) {
      updateColors();
    }
  }
}

2.P层
(1)Presenter抽象类定义

package com.android.incallui.baseui;

import android.os.Bundle;

/** Base class for Presenters. */
public abstract class Presenter<U extends Ui> {

  private U ui;
  
  /**
   * Called after the UI view has been created. That is when fragment.onViewCreated() is called.
   *
   * @param ui The Ui implementation that is now ready to be used.
   */
  public void onUiReady(U ui) {
    this.ui = ui;
  }

  /** Called when the UI view is destroyed in Fragment.onDestroyView(). */
  public final void onUiDestroy(U ui) {
    onUiUnready(ui);
    this.ui = null;
  }
  
  /**
   * To be overriden by Presenter implementations. Called when the fragment is being destroyed but
   * before ui is set to null.
   */
  public void onUiUnready(U ui) {}

  public void onSaveInstanceState(Bundle outState) {}

  public void onRestoreInstanceState(Bundle savedInstanceState) {}

  public U getUi() {
    return ui;
  }
}

(2)Presenter类的接口实现类DialpadPresenter定义

package com.android.incallui;

import android.telephony.PhoneNumberUtils;
import com.android.incallui.DialpadPresenter.DialpadUi;
import com.android.incallui.baseui.Presenter;
import com.android.incallui.baseui.Ui;
import com.android.incallui.call.CallList;
import com.android.incallui.call.DialerCall;
import com.android.incallui.call.TelecomAdapter;

/** Logic for call buttons. */
public class DialpadPresenter extends Presenter<DialpadUi>
    implements InCallPresenter.InCallStateListener {

  private DialerCall call;

  @Override
  public void onUiReady(DialpadUi ui) {
    super.onUiReady(ui);
    InCallPresenter.getInstance().addListener(this);
    call = CallList.getInstance().getOutgoingOrActive();
  }

  @Override
  public void onUiUnready(DialpadUi ui) {
    super.onUiUnready(ui);
    InCallPresenter.getInstance().removeListener(this);
  }
  
  @Override
  public void onStateChange(
      InCallPresenter.InCallState oldState,
      InCallPresenter.InCallState newState,
      CallList callList) {
    call = callList.getOutgoingOrActive();
    Log.d(this, "DialpadPresenter mCall = " + call);
  }
  
  /**
   * Processes the specified digit as a DTMF key, by playing the appropriate DTMF tone, and
   * appending the digit to the EditText field that displays the DTMF digits sent so far.
   */
  public final void processDtmf(char c) {
    Log.d(this, "Processing dtmf key " + c);
    
    // if it is a valid key, then update the display and send the dtmf tone.
    if (PhoneNumberUtils.is12Key(c) && call != null) {
      Log.d(this, "updating display and sending dtmf tone for '" + c + "'");
      // Append this key to the "digits" widget.
      DialpadUi dialpadUi = getUi();
      if (dialpadUi != null) {
        dialpadUi.appendDigitsToField(c);
      }
      // Plays the tone through Telecom.
      TelecomAdapter.getInstance().playDtmfTone(call.getId(), c);
    } else {
      Log.d(this, "ignoring dtmf request for '" + c + "'");
    }
  }
  
  /** Stops the local tone based on the phone type. */
  public void stopDtmf() {
    if (call != null) {
      Log.d(this, "stopping remote tone");
      TelecomAdapter.getInstance().stopDtmfTone(call.getId());
    }
  }

  public interface DialpadUi extends Ui {

    void appendDigitsToField(char digit);
  }
}

3.M层
TelecomAdapter类就是model层的实现

package com.android.incallui.call;

import android.app.Notification;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Looper;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.telecom.InCallService;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import java.util.List;

/** Wrapper around Telecom APIs. */
public class TelecomAdapter implements InCallServiceListener {

  private static final String ADD_CALL_MODE_KEY = "add_call_mode";

  private static TelecomAdapter instance;
  private InCallService inCallService;

  private TelecomAdapter() {}
  
  @MainThread
  public static TelecomAdapter getInstance() {
    if (!Looper.getMainLooper().isCurrentThread()) {
      throw new IllegalStateException();
    }
    if (instance == null) {
      instance = new TelecomAdapter();
    }
    return instance;
  }
  
  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
  public static void setInstanceForTesting(TelecomAdapter telecomAdapter) {
    instance = telecomAdapter;
  }

  @Override
  public void setInCallService(InCallService inCallService) {
    this.inCallService = inCallService;
  }
  
  @Override
  public void clearInCallService() {
    inCallService = null;
  }

  private android.telecom.Call getTelecomCallById(String callId) {
    DialerCall call = CallList.getInstance().getCallById(callId);
    return call == null ? null : call.getTelecomCall();
  }
  
  public void mute(boolean shouldMute) {
    if (inCallService != null) {
      inCallService.setMuted(shouldMute);
    } else {
      LogUtil.e("TelecomAdapter.mute", "mInCallService is null");
    }
  }

  public void setAudioRoute(int route) {
    if (inCallService != null) {
      inCallService.setAudioRoute(route);
    } else {
      LogUtil.e("TelecomAdapter.setAudioRoute", "mInCallService is null");
    }
  }
  
  public void merge(String callId) {
    android.telecom.Call call = getTelecomCallById(callId);
    if (call != null) {
      List<android.telecom.Call> conferenceable = call.getConferenceableCalls();
      if (!conferenceable.isEmpty()) {
        call.conference(conferenceable.get(0));
        // It's safe to clear restrict count for merge action.
        DialerCall.clearRestrictedCount();
      } else {
        if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE)) {
          call.mergeConference();
          // It's safe to clear restrict count for merge action.
          DialerCall.clearRestrictedCount();
        }
      }
    } else {
      LogUtil.e("TelecomAdapter.merge", "call not in call list " + callId);
    }
  }
  
  public void swap(String callId) {
    android.telecom.Call call = getTelecomCallById(callId);
    if (call != null) {
      if (call.getDetails().can(android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE)) {
        call.swapConference();
      }
    } else {
      LogUtil.e("TelecomAdapter.swap", "call not in call list " + callId);
    }
  }
  
  public void addCall() {
    if (inCallService != null) {
      Intent intent = new Intent(Intent.ACTION_DIAL);
      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

      // when we request the dialer come up, we also want to inform
      // it that we're going through the "add call" option from the
      // InCallScreen / PhoneUtils.
      intent.putExtra(ADD_CALL_MODE_KEY, true);
      try {
        LogUtil.d("TelecomAdapter.addCall", "Sending the add DialerCall intent");
        inCallService.startActivity(intent);
      } catch (ActivityNotFoundException e) {
        // This is rather rare but possible.
        // Note: this method is used even when the phone is encrypted. At that moment
        // the system may not find any Activity which can accept this Intent.
        LogUtil.e("TelecomAdapter.addCall", "Activity for adding calls isn't found.", e);
      }
    }
  }
  
  public void playDtmfTone(String callId, char digit) {
    android.telecom.Call call = getTelecomCallById(callId);
    if (call != null) {
      call.playDtmfTone(digit);
    } else {
      LogUtil.e("TelecomAdapter.playDtmfTone", "call not in call list " + callId);
    }
  }
  
  public void stopDtmfTone(String callId) {
    android.telecom.Call call = getTelecomCallById(callId);
    if (call != null) {
      call.stopDtmfTone();
    } else {
      LogUtil.e("TelecomAdapter.stopDtmfTone", "call not in call list " + callId);
    }
  }
  
  public void postDialContinue(String callId, boolean proceed) {
    android.telecom.Call call = getTelecomCallById(callId);
    if (call != null) {
      call.postDialContinue(proceed);
    } else {
      LogUtil.e("TelecomAdapter.postDialContinue", "call not in call list " + callId);
    }
  }
  
  public boolean canAddCall() {
    if (inCallService != null) {
      return inCallService.canAddCall();
    }
    return false;
  }
  
  /**
   * Start a foreground notification. Calling it multiple times with the same id only updates the
   * existing notification. Whoever called this function are responsible for calling {@link
   * #stopForegroundNotification()} to remove the notification.
   */
   public void startForegroundNotification(int id, Notification notification) {
    Assert.isNotNull(
        inCallService, "No inCallService available for starting foreground notification");
    inCallService.startForeground(id, notification);
  }
  /**
   * Stop a started foreground notification. This does not stop {@code mInCallService} from running.
   */
  public void stopForegroundNotification() {
    if (inCallService != null) {
      inCallService.stopForeground(true /*removeNotification*/);
    } else {
      LogUtil.e(
          "TelecomAdapter.stopForegroundNotification",
          "no inCallService available for stopping foreground notification");
    }
  }
}

model层就是完全的数据逻辑层。

猜你喜欢

转载自blog.csdn.net/songqinging/article/details/89641964