本流程图基于MTK平台 Android 7.0,普通来电,本流程只作为沟通学习使用
通过前面 Android 7.0 Phone_MT来电流程 的流程分析中我们可以发现,最后是将来电的信息和状态传送到了 dialer 的 incallUI 里面,在 PhoneStatusBar.java 的addNotification方法中通过判断 isHeadsUped 的值来确定是显示 HeadsUp 还是全屏的AnswerFragment ,这里我们重点介绍一下 AnswerFragment 的相关知识。
相关类图
![这里写图片描述](https://img-blog.csdn.net/20170109135503690?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWljaGFlbF95dA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
说明:
- AnswerFragment是一个基类,它只提供了公共的接口和一些方法,没有界面显示
- GlowPadAnswerFragment和AccessibleAnswerFragment是它的子类,里面包含具体的界面显示和部分逻辑
- GlowPadAnswerFragment是我们通常意义上看到的AnswerFragment(GlowPadView)
- AccessibleAnswerFragment当我们打开了Accessibility里面的TalkBack Services后会启用这个界面
- AnswerFragment界面是CallCardFragment界面的一部分
- InCallPresenter实例化了InCallActivity和AnswerPresenter,它们两个一起控制着AnswerFragment的显示和隐藏
GlowPadAnswerFragment
类图说明
![这里写图片描述](https://img-blog.csdn.net/20170110112156445?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWljaGFlbF95dA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
说明:
- GlowPadAnswerFragment 加载了 answer_fragment 这个布局
- answer_fragment 里面只定义了一个自定义view, GlowPadWrapper
- GlowPadWrapper 继承自 GlowPadView
- GlowPadView 继承自 View
首先看看启动流程图
![这里写图片描述](https://img-blog.csdn.net/20170111151005617?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWljaGFlbF95dA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
再看看界面
![这里写图片描述](https://img-blog.csdn.net/20170111094259532?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWljaGFlbF95dA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
说明
- 红色方框中的界面就是我们的GlowPadAnswerFragment
- 这里显示了三种状态下的界面,1.未触摸中心点的phone图标、2.触摸了并向右移动到接听图标、3.触摸了但没有移动
通过上面类图的介绍,我们可以知道这个界面最终是由 GlowPadView 这个自定义view显示的,下面我们就来大致了解一下上面类图中各个类的内容和作用:
GlowPadAnswerFragment
这个类的代码量很少,主要就是加载 answer_fragment 布局,并通过 onShowAnswerUi 动态控制动画的开始和停止,通过showTargets 方法动态调整整个 GlowPadView 的资源文件,包括:显示几个图标,图标的描述字符串的改变,几个图标的改变。
@Override
public void onShowAnswerUi(boolean shown) {
Log.d(this, "Show answer UI: " + shown);
if (shown) {
mGlowpad.startPing();
} else {
mGlowpad.stopPing();
}
}
/**
* Sets targets on the glowpad according to target set identified by the parameter.
*
* @param targetSet Integer identifying the set of targets to use.
*/
@Override
public void showTargets(int targetSet, int videoState) {
Log.d(this, "showTargets targetSet 2 == " + targetSet);
final int targetResourceId;
final int targetDescriptionsResourceId;
final int directionDescriptionsResourceId;
final int handleDrawableResourceId;
mGlowpad.setVideoState(videoState);
switch (targetSet) {
case TARGET_SET_FOR_AUDIO_WITH_SMS:
targetResourceId = R.array.incoming_call_widget_audio_with_sms_targets;
targetDescriptionsResourceId =
R.array.incoming_call_widget_audio_with_sms_target_descriptions;
directionDescriptionsResourceId =
R.array.incoming_call_widget_audio_with_sms_direction_descriptions;
handleDrawableResourceId = R.drawable.ic_incall_audio_handle;
break;
case TARGET_SET_FOR_VIDEO_WITHOUT_SMS:
targetResourceId = R.array.incoming_call_widget_video_without_sms_targets;
targetDescriptionsResourceId =
R.array.incoming_call_widget_video_without_sms_target_descriptions;
directionDescriptionsResourceId =
R.array.incoming_call_widget_video_without_sms_direction_descriptions;
handleDrawableResourceId = R.drawable.ic_incall_video_handle;
break;
.............省略部分代码;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
answer_fragment
<com.android.incallui.GlowPadWrapper
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dc="http://schemas.android.com/apk/res-auto"
android:id="@+id/glow_pad_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:layout_centerHorizontal="true"
android:gravity="center"
android:background="@color/glowpad_background_color"
android:layout_marginBottom="@dimen/glowpadview_margin_bottom"
dc:targetDrawables="@array/incoming_call_widget_audio_with_sms_targets"
dc:targetDescriptions="@array/incoming_call_widget_audio_with_sms_target_descriptions"
dc:directionDescriptions="@array/incoming_call_widget_audio_with_sms_direction_descriptions"
dc:handleDrawable="@drawable/ic_incall_audio_handle"
dc:outerRingDrawable="@drawable/ic_lockscreen_outerring"
dc:outerRadius="@dimen/glowpadview_target_placement_radius"
dc:innerRadius="@dimen/glowpadview_inner_radius"
dc:snapMargin="@dimen/glowpadview_snap_margin"
dc:feedbackCount="1"
dc:vibrationDuration="20"
dc:glowRadius="@dimen/glowpadview_glow_radius"
dc:pointDrawable="@drawable/ic_lockscreen_glowdot"
dc:allowScaling="true"
/>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
参考:http://blog.csdn.net/yihongyuelan/article/details/14000363 这个链接是4.2的版本有点老了,但是大致一样,有些细节不一样,不一样的地方上面的代码注释也有说明,可以对比查看。
GlowPadWrapper
这个类其实就是 GlowPadView 的包装,它起到一个中间层的作用,GlowPadAnswerFragment通过它来控制 GlowPadView 的动画启停,GlowPadView 通过它来反馈用户的 touch 和 select 事件给 AnswerFragment ,做对应的逻辑处理。
private final Handler mPingHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PING_MESSAGE_WHAT:
triggerPing();
break;
}
}
};
private void triggerPing() {
Log.d(this, "triggerPing(): " + mPingEnabled + " " + this);
if (mPingEnabled && !mPingHandler.hasMessages(PING_MESSAGE_WHAT)) {
ping();
if (ENABLE_PING_AUTO_REPEAT) {
mPingHandler.sendEmptyMessageDelayed(PING_MESSAGE_WHAT, PING_REPEAT_DELAY_MS);
}
}
@Override
public void onTrigger(View v, int target) {
Log.d(this, "onTrigger() view=" + v + " target=" + target);
final int resId = getResourceIdForTarget(target);
if (resId == R.drawable.ic_lockscreen_answer) {
mAnswerFragment.onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
mTargetTriggered = true;
} else if (resId == R.drawable.ic_lockscreen_decline) {
mAnswerFragment.onDecline(getContext());
mTargetTriggered = true;
} else if (resId == R.drawable.ic_lockscreen_text) {
mAnswerFragment.onText();
mTargetTriggered = true;
} else if (resId == R.drawable.ic_videocam || resId == R.drawable.ic_lockscreen_answer_video) {
mAnswerFragment.onAnswer(mVideoState, getContext());
mTargetTriggered = true;
} else if (resId == R.drawable.ic_lockscreen_decline_video) {
mAnswerFragment.onDeclineUpgradeRequest(getContext());
mTargetTriggered = true;
} else {
Log.e(this, "Trigger detected on unhandled resource. Skipping.");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
GlowPadView
具体的 GlowPadView 的实现类,里面包含了view的绘画和动画的定义,还有一些监听和回调,这部分内容后面再做详细的分析。
LOG信息
01-11 10:29:48.872 D/InCall ( 5589): AnswerPresenter - onIncomingCall: com.android.incallui.AnswerPresenter@95a864f
01-11 10:29:49.895 D/InCall ( 5589): AnswerPresenter - Showing incoming for call id: Call_0 com.android.incallui.AnswerPresenter@95a864f
//界面的启动和初始化
01-11 10:29:49.962 D/InCall ( 5589): GlowPadWrapper - class created com.android.incallui.GlowPadWrapper{ebc6975 VFED..... ......I. 0,0-0,0 #7f0a0094 app:id/glow_pad_view}
01-11 10:29:49.962 D/InCall ( 5589): GlowPadWrapper - onFinishInflate()
01-11 10:29:49.967 D/InCall ( 5589): GlowPadAnswerFragment - Creating view for answer fragment GlowPadAnswerFragment{f709f47 #2 id=0x7f0a00c4 tag_answer_fragment}
01-11 10:29:49.967 D/InCall ( 5589): GlowPadAnswerFragment - Created from activitycom.android.incallui.InCallActivity@494e8d4
//界面显示和动画
01-11 10:29:49.968 D/InCall ( 5589): GlowPadAnswerFragment - Show answer UI: true
01-11 10:29:49.968 D/InCall ( 5589): GlowPadWrapper - startPing
01-11 10:29:49.968 D/InCall ( 5589): GlowPadWrapper - triggerPing(): true com.android.incallui.GlowPadWrapper{ebc6975 VFED..... ......I. 0,0-0,0 #7f0a0094 app:id/glow_pad_view}
01-11 10:29:49.973 D/InCall ( 5589): GlowPadAnswerFragment - showTargets targetSet 1 == 1
01-11 10:29:49.973 D/InCall ( 5589): GlowPadAnswerFragment - showTargets targetSet 2 == 1
01-11 10:29:49.974 D/InCall ( 5589): AnswerPresenter - getVideoUpgradeRequestCall call =null
AccessibleAnswerFragment
这个界面是Android 7.0 新加入的界面,主要是给有生理障碍的人士使用,在 InCallActivity 里面通过判断是否启动无障碍功能里面的 TalkBack 服务,来决定是否显示 AccessibleAnswerFragment
if (AccessibilityUtil.isTalkBackEnabled(this)) {
mAnswerFragment = new AccessibleAnswerFragment();
} else {
mAnswerFragment = new GlowPadAnswerFragment();
}
界面图片
![这里写图片描述](https://img-blog.csdn.net/20170111175826921?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWljaGFlbF95dA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到这个界面相对于 GlowPadAnswerFragment 界面就简单得多了,只有三个 imageview 没有复杂的动画在里面。
AccessibleAnswerFragment
这个类的主要作用就是监听并处理用户的点击和滑动事件,从而调用 AnswerFragment 里面的接听、挂断、短信回复这些方法。相关的方法如下:
mAnswer = group.findViewById(R.id.accessible_answer_fragment_answer);
mAnswer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Answer Button Clicked");
onAnswer(VideoProfile.STATE_AUDIO_ONLY, getContext());
}
});
mDecline = group.findViewById(R.id.accessible_answer_fragment_decline);
mDecline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Decline Button Clicked");
onDecline(getContext());
}
});
mText = group.findViewById(R.id.accessible_answer_fragment_text);
mText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "Text Button Clicked");
onText();
}
});
private boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
if (hasPendingDialogs()) {
return false;
}
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
}
return true;
} else if (Math.abs(diffY) > SWIPE_THRESHOLD) {
if (diffY > 0) {
onSwipeDown();
} else {
onSwipeUp();
}
return true;
}
return false;
}
@Override
public void onResume() {
super.onResume();
InCallActivity activity = (InCallActivity) getActivity();
activity.setDispatchTouchEventListener(mTouchListener);
}
private class TouchListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
accessible_answer_fragment
这个文件是 GlowPadAnswerFragment 的布局文件,里面内容也很简单,通过相对布局和线性布局来得到我们上图下半部分的来电界面.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:background="@color/glowpad_background_color">
<RelativeLayout
android:id="@+id/accessible_answer_fragment_answer"
android:orientation="vertical"
android:layout_width="120dp"
android:layout_height="120dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/ic_lockscreen_answer_activated_layer"
android:layout_centerInParent="true">
</ImageView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/description_target_answer"
android:textSize="12sp"
android:textColor="@color/accessible_answer_hint_text_color"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dp"/>
</RelativeLayout>
.....省略部分代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
总结
- 来电界面有两个,一个是正常的 GlowPadAnswerFragment 一个是无障碍服务开启后显示的 AccessibleAnswerFragment (这个是7.0 新加的 fragment 通过判读 TalkBack 服务是否开启来决定是否显示这个fragment)
- GlowPadAnswerFragment 显示的其实就是 GlowPadView 这个自定义view