本篇小文對來電接聽InCallScreen界面進行源碼分析,該文基於android5.1進行源碼分析,由於使用了廠商提供的源碼版本,和google官方發佈的版本會有所差異。
1. 效果圖
我們先來看樣機的實際運行效果圖
我們通過圖片定位到:
packages/apps/InCallUI/res/values/array.xml
下面我們來看下array.xml中的代碼
<array name="incoming_call_widget_audio_with_sms_targets">
<item>@drawable/ic_lockscreen_answer</item>
<!--item>@drawable/ic_lockscreen_text</item-->
<item>@null</item>
<item>@drawable/ic_lockscreen_decline</item>
<item>@null</item>"
</array>
我們看到,在這個定義了接聽/拒接/短信這三個功能。
通過“incoming_call_widget_audio_with_sms_targets”來調用這個array,繼續看這個調用的地方
packages/apps/InCallUI/res/layout/answer_fragment.xml
我們來看下這個文件的具體調用:
<com.android.incallui.GlowPadWrapper
...............
dc:targetDrawables="@array/incoming_call_widget_audio_with_sms_targets"
dc:handleDrawable="@drawable/ic_incall_audio_handle"
dc:outerRingDrawable="@drawable/ic_lockscreen_outerring"
......................
我們看到,incoming_call_widget_audio_with_sms_targets在這裏進行了調用,並調用了GlowPadWrapper該函數來進行相關處理,由於對GlowPadWrapper函數不熟悉,下面我們先來介紹一下這個函數的基本情況。
最中間圓圈的按鈕: dc:handleDrawable=”@drawable/ic_incall_audio_handle”
最外面圓圈的顏色設置: dc:outerRingDrawable=”@drawable/ic_lockscreen_outerring”
2. GlowPadWrapper.java
我們看到在GlowPadWrapper.java是繼承GlowPadView而來的,在這個函數裏對相關的空間進行了處理,我們來看一下:
@Override
public void onTrigger(View v, int target) {
Log.d(this, "onTrigger()");
final int resId = getResourceIdForTarget(target);
switch (resId) {
case R.drawable.ic_lockscreen_answer:
mAnswerListener.onAnswer(VideoProfile.VideoState.AUDIO_ONLY, getContext());
mTargetTriggered = true;
break;
case R.drawable.ic_lockscreen_decline:
mAnswerListener.onDecline();
mTargetTriggered = true;
break;
case R.drawable.ic_lockscreen_text:
mAnswerListener.onText();
mTargetTriggered = true;
//SPRD bug 424648 {@
TelecomManager telecomManager = SprdUtils.getTelecommService(getContext());
telecomManager.silenceRinger();
//@}
break;
case R.drawable.ic_lockscreen_answer_video://SPRD: add for can't accept video call
case R.drawable.ic_videocam:
mAnswerListener.onAnswer(VideoProfile.VideoState.BIDIRECTIONAL, getContext());
mTargetTriggered = true;
break;
case R.drawable.ic_toolbar_video_off:
InCallPresenter.getInstance().declineUpgradeRequest(getContext());
mTargetTriggered = true;
break;
default:
// Code should never reach here.
Log.e(this, "Trigger detected on unhandled resource. Skipping.");
}
}
在這裏對
ic_lockscreen_answer 、
ic_lockscreen_decline
ic_lockscreen_text
對接聽,拒接,短信進行了處理,這裏我們先放一下,我們看下GlowPadView.java這個函數的情況。
public class GlowPadView extends View {}
我們看到,其實也是extends了view,所以在這裏我們看到很熟悉的一些函數
protected void onDraw(Canvas canvas){};
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {};
相信大家對view的常用的函數都已經比較熟悉了,我們就不在這裏更進一步的分析,
到這裏我們知道:
GlowPadView.java進行佈局;
GlowPadWrapper.java對按鈕進行監測;
3.0 LayoutInflater.inflate
在往下看之前我們先來看一下inflate這個是起什麼作用的,在
sw/frameworks/base/core/java/android/view/LayoutInflater.java
—->
public View inflate(int resource, ViewGroup root) {}
public View inflate(XmlPullParser parser, ViewGroup root){}
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {}
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {}
在這幾個定義的註釋開頭,我們都能看到這樣一句話,
Inflate a new view hierarchy from the specified XML node.
實例化一個xml到制定的layout裏和setContent有一些區別:
setContentView()一旦調用, layout就會立刻顯示UI;而inflate只會把Layout形成一個以view類實現成的對象,有需要時再用setContentView(view)顯示出來。一般在activity中通過setContentView()將界面顯示出來,但是如果在非activity中如何對控件佈局設置操作了,這就需要LayoutInflater動態加載。
public View inflate(int Resourece,ViewGroup root)
作用:填充一個新的視圖層次結構從指定的XML資源文件中
reSource:View的layout的ID
root: 生成的層次結構的根視圖
return 填充的層次結構的根視圖。如果參數root提供了,那麼root就是根視圖;否則填充的XML文件的根就是根視圖。
public View inflate(int resource, ViewGroup root, boolean attachToRoot)方法三個參數的含義
resource:需要加載佈局文件的id,意思是需要將這個佈局文件中加載到Activity中來操作。
root:需要附加到resource資源文件的根控件,什麼意思呢,就是inflate()會返回一個View對象,如果第三個 參
數attachToRoot爲true,就將這個root作爲根對象返回,否則僅僅將這個root對象的LayoutParams屬性附
加到resource對象的根佈局對象上,也就是佈局文件resource的最外層的View上,比如是一個
LinearLayout或者其它的Layout對象。
attachToRoot:是否將root附加到佈局文件的根視圖上.
4.0 packages/apps/InCallUI/res/layout/call_card_content.xml
來電界面的佈局xml文件是在call_card_content.xml進行,在這裏沒有
綠色的來電信心,號碼/歸屬地/等等 ….: primary_call_info_container
下方4個按鍵的接聽/拒接/短信 : @+id/answerFragment
我們知道,在佈局過程中,有靜態佈局和動態佈局這兩種方式,
setContentView()
LayoutInflater.inflate()
這兩種方式,這兩種方式的主要特點在上面已經介紹過。
既然我們知道,那麼我們就看看這是是怎麼進行調用的,通過全局搜索我們可以看到
packages/apps/InCallUI/src/com/android/incallui/CallCardFragment.java
中對call_card_content進行了引用,
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mDensity = getResources().getDisplayMetrics().density;
mTranslationOffset =
getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset);
**return inflater.inflate(R.layout.**call_card_content**, container, false)**;
}
在這裏我們看到,onCreateView動態加載了call_card_content.xml這個文件,在這裏進行了初始化。
那麼我們如果往上再看的話,又是哪裏來調用這個CallCardFragment.java的呢,
我們來搜下“CallCardFragment”這個字串,我們可以看到,
比較有價值的地方是這個:
packages/apps/InCallUI/res/layout/incall_screen.xml:
我們這裏採用的是從下往上逆向查找的方法來進行源碼分析,這樣的方法更直接直觀的去尋找我們需要的東西,如果我們順着源碼的執行順序來查找,那麼我們一個個的去看每個文件,這樣浪費的時間就比較多了。
到了這裏,我們就對哪裏調用的incall_screen.xml這個文件比較熟悉了,我們回到了
InCallActivity.java
5.0 當前界面來電的不同處理
我們知道,5.1的來電界面解鎖前是直接進入到來電界面的,但是如果是在解鎖後的某一個界面中,這個時候來電,就會現在狀態欄提示有來電,點擊之後才進入到來電界面。那麼我們怎麼來查找這個判斷是否在解鎖前或者後的界面在哪裏呢?我們應該從哪裏入手比較合理呢?
5.1 查找incallactivity的調用
我們知道,啓動一個activity的方法,一般會在AndroidManifest.XML裏面有相關的屬性能進行查找,比如
Intent / resevice …..
還有一個就是我們在代碼中動態的啓動,那麼既然是這樣,啓動一個intent我們知道,必須對包名和類型進行定義。那麼我們先來看下incallactivity.java在AndroidManifest.xml中的定義是怎樣的
<activity android:name="com.android.incallui.InCallActivity"
android:theme="@style/Theme.InCallScreen"
android:label="@string/phoneAppLabel"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:configChanges="keyboardHidden"
android:exported="false"
android:screenOrientation="nosensor" >
</activity>
在這裏,我們能很清楚的看到,這裏沒有intent/action/receiver/service……..
等等這下關鍵得詞,既然在這裏我們得不到啓動該activity的有用信息,那麼我們就換個思路去找找看,我們從動態啓動該activity去想想看到底這個是怎麼啓動的。我們知道5.1之後一個intent的啓動必須要同時對包名和類名進行註冊,但是,包名不是唯一的,但是類名一定是唯一的,所以,我們先從類名入手,我們在InCallUi這個模塊下去搜索用到“InCallActivity”這個類名的地方,我們來看搜索的結果
**xxxxx@build-server-C:xxxxx
我們看到在InCallUi這個模塊裏面,只有這一個地方是用用到InCallActivity.class的,那麼很有可能這個就是突破口,我們進去看看
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java
public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall,
boolean showCircularReveal, boolean newTask) {
final Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_NO_USER_ACTION);
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
intent.setClass(mContext, **InCallActivity**.class);
if (showDialpad) {
intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true);
}
intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall);
intent.putExtra(InCallActivity.SHOW_CIRCULAR_REVEAL_EXTRA, showCircularReveal);
return intent;
}
在這裏,我們看到incallactivity的確是通過一個Intent來啓動的,但是在這裏,我們還無法看到是哪裏啓動的該函數,我們繼續往上搜索:
grep -r “getInCallIntent” package/app/InCallUi/
結果只有兩個地方有調用到這個函數,
public void **showInCall**(final boolean showDialpad, final boolean newOutgoingCall) {
if (mCircularRevealActivityStarted) {
mWaitForRevealAnimationStart = true;
mShowDialpadOnStart = showDialpad;
Log.i(this, "Waiting for circular reveal completion to show InCallActivity");
} else {
Log.i(this, "Showing InCallActivity immediately");
mContext.startActivity(**getInCallIntent**(showDialpad, newOutgoingCall,
newOutgoingCall /* showCircularReveal */));
}
}
public void **onCircularRevealStarted**(final Activity activity) {
mCircularRevealActivityStarted = false;
if (mWaitForRevealAnimationStart) {
mWaitForRevealAnimationStart = false;
mHandler.post(new Runnable() {
@Override
public void run() {
Log.i(this, "Showing InCallActivity after circular reveal");
final Intent intent =
**getInCallIntent**(mShowDialpadOnStart, true, false, false);
activity.startActivity(intent);
mShowDialpadOnStart = false;
}
});
} else if (!mServiceBound) {
CircularRevealActivity.sendClearDisplayBroadcast(mContext);
return;
}
}
到了這裏,我們要做一個判斷,是繼續往showInCall還是往onCircularRevealStarted這個方方向商去查找呢,如果我們對代碼比較熟悉的話,我們的直覺會告訴我們,showInCall這個繼續找下去找到判斷的地方的可能性更大一寫,那麼我們繼續,這裏我們不分析showInCall這個裏面的具體的函數做了什麼功能或者什麼處理,我們的目標是要找到判斷是否先彈出狀態欄提示的地方,所以ok,我們繼續showInCall這個下面去找,
xxxxx@build-server-C:~xx$ grep -r “showInCall” packages/apps/InCallUI/
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: showInCall(false, false/* newOutgoingCall */);
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: showInCall(showDialpad, false /* newOutgoingCall */);
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: showInCall(false /* showDialpad /, !showAccountPicker / newOutgoingCall */);
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: showInCall(false, false);
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: showInCall(false, false);
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: //showInCall(false, false);
packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java: public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) {
xxxxx@build-server-C:~xx$
上面的搜索結果顯示所有的調用都在InCallPresenter.java這個裏面,那麼我們一個個來看看,那個是最可能進去的地方,
public void **answerIncomingCall**(Context context, int videoState) {
......................
Call call = mCallList.getIncomingCall();
if (call != null) {
TelecomAdapter.getInstance().answerCall(call.getId(), videoState);
**showInCall**(false, false/* newOutgoingCall */);
}
}
============================================================================
public void **bringToForeground**(boolean showDialpad) {
...............
if (!isShowingInCallUi() && mInCallState != InCallState.NO_CALLS) {
**showInCall**(showDialpad, false /* newOutgoingCall */);
}
}
==============================================
private InCallState startOrFinishUi(InCallState newState) {
..........................
...............
else if (newState == InCallState.INCALL &&
(CallList.getInstance().getActiveCall() != null
|| CallList.getInstance().getBackgroundCall() != null)) {
if (!isActivityStarted()) {
**showInCall**(false, false);
}
}
====================================================
private boolean startUi(InCallState inCallState) {
..............
if (isCallWaiting) {
if (mProximitySensor != null && mProximitySensor.isScreenReallyOff() && isActivityStarted()) {
...........
return false;
} else {
**showInCall**(false, false);
}
} else {
mStatusBarNotifier.updateNotification(inCallState, mCallList);
//showInCall(false, false);
}
return true;
}
到這裏,我們就能清楚的看到所有調用showInCall的地方,那麼在這幾個調用的地方,我們判斷一下那個地方是最有可能的,我們來看
answerIncomingCall()
bringToForeground()
startOrFinishUi()
startUi()
其實我們光從字符表面的意思也能猜猜一個結果,我選擇“startUi()”,這個更像是開始的地方,那麼我們看
if (isCallWaiting) {
………………………..
showInCall(false, false);
}
} else {
mStatusBarNotifier.updateNotification(inCallState, mCallList);
//showInCall(false, false);
}
return true;
}
我們看到
mStatusBarNotifier.updateNotification(inCallState, mCallList)
這個地方是不是很像狀態欄statusBar相關的,爲了節省時間,我們直接做一個修改,用
showInCall
替換掉
mStatusBarNotifier.updateNotification(inCallState, mCallList)
編譯,然後來重啓,看結果,我們可以看到來電直接進入了接聽界面。
所以這個地方就是我們要找的,對是否彈出狀態欄或者直接進入接聽界面的關鍵地方。
補充一下startUi後的流程:
我們看到調用 startUi()的地方只有一個
private InCallState **startOrFinishUi**(InCallState newState) {
..................
if (!**startUi**(newState)) {}
.....................
}
5.6 如何定義一個系統變量
如何定義一個全局的系統變量:
我們在
framework/base/core/java/android/provider/Settings.java
中定義一個全局變量,
首先,
public static final class System extends NameValueTable {}
在這裏類裏面有要添加兩個地方,
一個是public static final String[] SETTINGS_TO_BACKUP = { }這個裏面,同時在
public static final String[] SETTINGS_TO_BACKUP = {}的外面還需要再添加一次才能成功,
如下面所示:
/**
* @hide
*/
public static final String CALL_HUOER_WINDOW= “call_huoer_window”;
public static final String[] SETTINGS_TO_BACKUP = {
CALL_HUOER_WINDOW,
}
這樣纔是成功的添加了一個android系統變量。
那麼我們怎麼對這個系統變量進行操作呢,我們通過這樣的引用來操作:
settings.System.putString(mContext.getContentResolver(), Settings.System.CALL_HUOER_WINDOW,String.valueOf(true));
通過這種方式來對全局變量的讀和寫。
5.7 判斷電話的狀態的方法:
if (mHallClose > 0) {
Log.d("jojo_setCallState","state="+state +" ;disconnectCause.getCode()="+disconnectCause.getCode());
if (state == Call.State.ACTIVE || state == Call.State.DIALING) {
setEndCallButtonVisible();
} else if (state == Call.State.DISCONNECTED) {
if (disconnectCause.getCode() == DisconnectCause.MISSED ||disconnectCause.getCode() == DisconnectCause.BUSY ||
disconnectCause.getCode() == DisconnectCause.LOCAL) {
launchQuickWindow();
}
mDisConnecting = false;
} else if (state == Call.State.DISCONNECTING) {
if (!mDisConnecting)
launchQuickWindow();
mDisConnecting = true;
}
}
5.8 CallCardFragment.onCreateViewted調用堆棧
jojoo - java.lang.Throwable
at com.android.incallui.CallCardFragment.onViewCreated(CallCardFragment.java:293)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:875)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1045)
at android.app.FragmentManagerImpl.addFragment(FragmentManager.java:1147)
at android.app.FragmentManagerImpl.onCreateView(FragmentManager.java:2116)
at android.app.Activity.onCreateView(Activity.java:5345)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:733)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:806)
at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
at android.view.LayoutInflater.inflate(LayoutInflater.java:365)
at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:379)
at android.app.Activity.setContentView(Activity.java:2154)
at com.android.incallui.InCallActivity.onCreate(InCallActivity.java:181)
at android.app.Activity.performCreate(Activity.java:6012)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2410)
at android.app.ActivityThread.access
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5345)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:947)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:742)
5.9 CallCardFragment.setEndCallButtonEnabled()調用堆棧
jojo_QU - java.lang.Throwable
at com.android.incallui.CallCardFragment.setEndCallButtonEnabled(CallCardFragment.java:1125)
at com.android.incallui.CallCardPresenter.onStateChange(CallCardPresenter.java:266)
at com.android.incallui.InCallPresenter.onCallListChange(InCallPresenter.java:454)
at com.android.incallui.InCallPresenter.updateActivity(InCallPresenter.java:408)
at com.android.incallui.InCallPresenter.setActivity(InCallPresenter.java:318)
at com.android.incallui.InCallActivity.onStart(InCallActivity.java:249)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1236)
at android.app.Activity.performStart(Activity.java:6031)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2311)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2410)
at android.app.ActivityThread.access
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5345)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:947)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:742)
6.0 CallCardFragment.setCallState()調用堆棧
java.lang.Throwable
at com.android.incallui.InCallPresenter.startOrFinishUi(InCallPresenter.java:969)
at com.android.incallui.InCallPresenter.onIncomingCall(InCallPresenter.java:501)
at com.android.incallui.CallList.onIncoming(CallList.java:137)
at com.android.incallui.CallList
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5345)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:947)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:742)