Android7.0 InCallUI 源码分析
AndroidManifest.xml
源码路径:packages\apps\Dialer\InCallUI
注:android7.0之前packages\apps\InCallUI
InCallUI\AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.incallui">
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="23" />
</manifest>
InCallUI的组件声明在Dialer\AndroidManifest.xml
<!-- Main in-call UI activity. This is never launched directly from outside the phone app; instead, it's either launched by the OutgoingCallBroadcaster (for outgoing calls), or as the fullScreenIntent of a notification (for incoming calls.) -->
IncallActivity不能通过外部APP直接启动,只能通过广播的方式启动,或者是能全屏的通知(比如:来电)
<activity android:name="com.android.incallui.InCallActivity"
android:theme="@style/Theme.InCallScreen"
android:label=""
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:configChanges="smallestScreenSize|screenLayout|keyboardHidden"
android:exported="false"
android:screenOrientation="nosensor"
android:directBootAware="true"
android:resizeableActivity="true">
</activity>
// android:excludeFromRecents="true" 不让activity显示在Recent screens中
所以要想属性生效设置该属性的 Activity 必须是 Task 的根 Activity。如果在某个 Task 非根 Activity 中设置 android:excludeFromRecents 是没有任何效果的。
// android:screenOrientation="nosensor" 不受重力感应横竖屏切换
// android:directBootAware="true"
在此 Direct Boot Mode 下 APP 比较适合做一些:
- Alarm、clock 类的操作
- 需要做重要的或紧急的通知
- 底层服务类
使用场景,比如手机丢了,捡到的人解不开锁,也做不了啥操作,现在任意工作在Direct Boot Mode 下的APP都可以“安全地”跑起来,和服务器建链,更加全方位的和捡手机的人进行沟通。更多的场景还需要更大的脑洞。
APP在进入DBM 后会收到系统的广播消息: Intent.ACTION_LOCKED_BOOT_COMPLETED —— 新增的。
用户解锁手机后,APP会收到另一条: Intent.ACTION_BOOT_COMPLETED —— 一直都有
// android:resizeableActivity="true" 支持分屏任务,android7.0以下不能设置该属性
InCallActivity
packages\apps\Dialer\InCallUI\src\com\android\incallui\InCallActivity.java
布局解析
InCallActivity的根布局
// setContentView(R.layout.incall_screen);
incall_screen.xml
<!-- In-call Phone UI; see InCallActivity.java. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:id="@+id/main" >
</FrameLayout>
IncallActivity的布局相对复杂,静态布局和动态布局相结合,下文会介绍CallCardFragment。
相关类图
类图说明:InCallActivity继承了TransactionSafeActivity,IncallActivity类负责UI展示,InCallPresenter是InCallActivity的代理类,负责相关的逻辑处理,通过回调与InCallActivity进行交互。
InCallActivity的 onCreate()方法中添加了亮屏、悬浮锁屏界面,过滤无关的触摸事件的Flag
// set this flag so this activity will stay in front of the keyguard
// Have the WindowManager filter out touch events that are "too fat".
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
getWindow().addFlags(flags);
InCallActivity中相关Fragment如下:
private CallButtonFragment mCallButtonFragment;
private CallCardFragment mCallCardFragment;
private AnswerFragment mAnswerFragment;
private DialpadFragment mDialpadFragment;
private ConferenceManagerFragment mConferenceManagerFragment;
...
...
// CallCardFragment
boolean showContactPhoto = !VideoCallPresenter.showIncomingVideo(videoState, state);
CallCardFragment:用于显示联系人信息及通话时间等;
CallButtonFragment:通话界面下方的控制按钮。
DialpadFragment:拨号盘显示控件。
AnswerFragment:来电控制控件,用于操作接听/拒接/短信快捷回复。
ConferenceManagerFragment:会议电话的界面。
VideoCallFragment:视屏通话控件,在CallCardFragment中调用。
private void showFragment(String tag, boolean show, boolean executeImmediately) {
Trace.beginSection("showFragment - " + tag);
final FragmentManager fm = getFragmentManagerForTag(tag);
if (fm == null) {
Log.w(TAG, "Fragment manager is null for : " + tag);
return;
}
Fragment fragment = fm.findFragmentByTag(tag);
if (!show && fragment == null) {
// Nothing to show, so bail early.
return;
}
final FragmentTransaction transaction = fm.beginTransaction();
if (show) {
if (fragment == null) {
fragment = createNewFragmentForTag(tag);
transaction.add(getContainerIdForFragment(tag), fragment, tag);
} else {
transaction.show(fragment);
}
Logger.logScreenView(getScreenTypeForTag(tag), this);
} else {
transaction.hide(fragment);
}
transaction.commitAllowingStateLoss();
if (executeImmediately) {
fm.executePendingTransactions();
}
Trace.endSection();
}
通过tag懒加载Fragment
private Fragment createNewFragmentForTag(String tag) {
if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
mDialpadFragment = new DialpadFragment();
return mDialpadFragment;
} else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
if (AccessibilityUtil.isTalkBackEnabled(this)) {
mAnswerFragment = new AccessibleAnswerFragment();
} else {
mAnswerFragment = new GlowPadAnswerFragment();
}
return mAnswerFragment;
} else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
mConferenceManagerFragment = new ConferenceManagerFragment();
return mConferenceManagerFragment;
} else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
mCallCardFragment = new CallCardFragment();
return mCallCardFragment;
}
throw new IllegalStateException("Unexpected fragment: " + tag);
}
CallCardFragment
packages\apps\Dialer\InCallUI\src\com\android\incallui\CallCardFragment.java
InCallActivity中的几个fragment都继承了BaseFragment,每个Fragment都有一个Presenter(降低代码耦合性); BaseFragment、Presenter都采用了泛型设计:
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
CallCardFragment的关系类图如下:
public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment{};其中几个重要的方法有
public abstract T createPresenter();
public abstract U getUi();
protected BaseFragment() {
//当实例一个对应的Fragment时,会初始化Fragment对的Presenter
mPresenter = createPresenter();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActi