來電接聽InCallScreen界面源碼分析

本篇小文對來電接聽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:xxxxxgreprInCallActivity.classpackages/apps/InCallUI/packages/apps/InCallUI/src/com/android/incallui/InCallPresenter.java:intent.setClass(mContext,InCallActivity.class);xxxxx@buildserverC: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.access800(ActivityThread.java:151)atandroid.app.ActivityThread H.handleMessage(ActivityThread.java:1313)
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.access800(ActivityThread.java:151)atandroid.app.ActivityThread H.handleMessage(ActivityThread.java:1313)
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.CallList1.onCallAdded(CallList.java:79)atandroid.telecom.Phone.fireCallAdded(Phone.java:276)atandroid.telecom.Phone.internalAddCall(Phone.java:119)atandroid.telecom.InCallService 1.handleMessage(InCallService.java:73)
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)

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