来电接听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:xxxxx greprInCallActivity.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.access 800(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.access 800(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.CallList 1.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)

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值