天天看點

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

描述:當手機已經在通話中的時候,界面回到桌面,再打開Dialer app的時候,手機就會提示并讓你确認到底你是想打開已經在通話的界面還是不管正在通話中的電話而重新撥打新的通話。

假如此刻你想打開正在通話的界面,android的源碼不是直接在

DialtactsActivity

startActivity

,而是經過系統通話服務最終啟動。

這個過程裡,代碼從

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 mBinderImpl

ITelecomService

的實體,實作了

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接口的使用。