Android Dialer源碼分析之通話中主動顯示IncallActivity

描述:當手機已經在通話中的時候,界面回到桌面,再打開Dialer app的時候,手機就會提示並讓你確認到底你是想打開已經在通話的界面還是不管正在通話中的電話而重新撥打新的通話。
假如此刻你想打開正在通話的界面,android的源碼不是直接在DialtactsActivitystartActivity,而是經過系統通話服務最終啓動。
這個過程裏,代碼從Dialer走到Framework,然後從Framework走到Telecom,再從Telecom走到Framework,再從Framework走到Dialer。跨進程通信的過程比較值得回味。


當DialtactsActivity啓動後,會去檢查是否正在通話中,
如果在通話中,就會讓Dialpadframgent顯示一個Listview形式的DialpadChooser,當點擊回到通話界面的選項時,會會回調到onItemClick

@Override
  public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    DialpadChooserAdapter.ChoiceItem item =
        (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
    int itemId = item.id;
    if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD) {
      returnToInCallScreen(true);
    } else if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL) {
      returnToInCallScreen(false);
    } else if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL) {
      showDialpadChooser(false);
    } else {
      LogUtil.w("DialpadFragment.onItemClick", "Unexpected itemId: " + itemId);
    }
  }

第二項DIALPAD_CHOICE_RETURN_TO_CALL
returnToInCallScreen(false)
這裏銷燬DialtactsActivity並調用TelecomUtil去處理顯示InCallScreen;

  private void returnToInCallScreen(boolean showDialpad) {
    TelecomUtil.showInCallScreen(getActivity(), showDialpad);
    getActivity().finish();
  }

TelecomUtil

  public static void showInCallScreen(Context context, boolean showDialpad) {
    if (hasReadPhoneStatePermission(context)) {
      try {
        getTelecomManager(context).showInCallScreen(showDialpad);
      } catch (SecurityException e) {
        // Just in case
        LogUtil.w(TAG, "TelecomManager.showInCallScreen called without permission.");
      }
    }
  }

getTelecomManager獲取TelecomManager

  private static TelecomManager getTelecomManager(Context context) {
    return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
  }

TelecomManager

	public void showInCallScreen(boolean showDialpad) {
        ITelecomService service = getTelecomService();
        if (service != null) {
            try {
                service.showInCallScreen(showDialpad, mContext.getOpPackageName());
            } catch (RemoteException e) {
                Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
            }
        }
    }

TelecomManager是屬於Framework
\frameworks\base\telecomm\java\android\telecom\TelecomManager.java

getTelecomService()

    private ITelecomService getTelecomService() {
        if (mTelecomServiceOverride != null) {
            return mTelecomServiceOverride;
        }
        return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
    }

要找到實現ITelecomService的實體,
發現是在TelecomServiceImpl裏,
packages\services\Telecomm\src\com\android\server\telecom\TelecomServiceImpl.java

private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub()
內部類ITelecomService.Stub mBinderImplITelecomService的實體,實現了ITelecomService接口方法。

@Override
public void showInCallScreen(boolean showDialpad, String callingPackage) {
    try {
        Log.startSession("TSI.sICS");
        if (!canReadPhoneState(callingPackage, "showInCallScreen")) {
            return;
        }
        synchronized (mLock) {
            long token = Binder.clearCallingIdentity();
            try {
                mCallsManager.getInCallController().bringToForeground(showDialpad);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    } finally {
        Log.endSession();
    }
}

檢查token過後就用CallsManager獲取InCallController,調用bringToForeground

    InCallController getInCallController() {
        return mInCallController;
    }

這個InCallController是在CallsManager構造方法裏新建的

   mInCallController = new InCallController(
           context, mLock, this, systemStateProvider, defaultDialerCache, mTimeoutsAdapter,
           emergencyCallHelper);

InCallController:

    void bringToForeground(boolean showDialpad) {
        if (!mInCallServices.isEmpty()) {
            for (IInCallService inCallService : mInCallServices.values()) {
                try {
                    inCallService.bringToForeground(showDialpad);
                } catch (RemoteException ignored) {
                }
            }
        } else {
            Log.w(this, "Asking to bring unbound in-call UI to foreground.");
        }
    }

然後就是調用IInCallService這個遠程接口來處理,
誰實現了這個遠程接口呢?

答案在IncallService的內部類InCallServiceBinder裏。
InCallService是在Framewrok裏的,
\frameworks\base\telecomm\java\android\telecom\InCallService.java

InCallServiceBinder extends IInCallService.Stub
---
@Override
public void bringToForeground(boolean showDialpad) {
    mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
}

通過Handler來發送message,

  case MSG_BRING_TO_FOREGROUND:
      mPhone.internalBringToForeground(msg.arg1 == 1);
      break;

然後這裏的mPhone對象是new出來的。

mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,
        getApplicationContext().getApplicationInfo().targetSdkVersion);
mPhone.addListener(mPhoneListener);

同時,mPhone還設置了監聽器,
mPhoneListener也是一個內部類對象,

前面internalBringToForeground

    final void internalBringToForeground(boolean showDialpad) {
        fireBringToForeground(showDialpad);
    }
    ---------
    private void fireBringToForeground(boolean showDialpad) {
        for (Listener listener : mListeners) {
            listener.onBringToForeground(this, showDialpad);
        }
    }

就是去通知所有監聽器調用onBringToForeground
也就是InCallService裏的Phone.Listener

	@Override
	public void onBringToForeground(Phone phone, boolean showDialpad) {
		InCallService.this.onBringToForeground(showDialpad);
	}

可是InCallService是個抽象父類

    public void onBringToForeground(boolean showDialpad) {
    }

於是需要看子類實現。

InCallServiceImpl extends InCallService
packages\apps\Dialer\java\com\android\incallui\InCallServiceImpl.java
這裏InCallServiceImpl是Dialer的UI部分,到了這一步是回到了Dialer包裏,

  @Override
  public void onBringToForeground(boolean showDialpad) {
    InCallPresenter.getInstance().onBringToForeground(showDialpad);
  }

然後就回到了InCallPresenter

前面講過,InCallPresenter裏檢測狀態後才調用了showInCall打開IncallUI。

  public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
    mContext.startActivity(
        InCallActivity.getIntent(
            mContext, showDialpad, newOutgoingCall, false /* forFullScreen */));
  }

這樣整個流程結束。


在這個流程裏看到了很多設計模式,
比如
TelecomManager 只是TelecomService的代理。真實的服務又是TelecomServiceImpl。代理模式很明顯。
Phone保存了List<Listener>,再用for循環通知所有Listener,觀察者模式很明顯。

然後就是跨進程通信了,有ITelecomService IInCallService 這兩個AIDL接口的使用。

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