Android8.1 MTK平臺 SystemUI原始碼分析之 網路訊號欄顯示重新整理
SystemUI系列文章
Android8.1 MTK平臺 SystemUI原始碼分析之 Notification流程
Android8.1 MTK平臺 SystemUI原始碼分析之 電池時鐘重新整理
Android 8.1平臺SystemUI 導航欄載入流程解析
一、從佈局說起
前面的文章分析過,網路訊號欄這塊屬於 system_icon_area,裡面包含藍芽、wifi、VPN、網絡卡、SIM卡網路型別、
資料流量符號、SIM卡訊號格、電池、時鐘。
先來看下 system_icon_area 對應的佈局檔案 system_icons.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/system_icons" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical"> <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/statusIcons" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal"/> <include layout="@layout/signal_cluster_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/signal_cluster_margin_start"/> <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="match_parent" android:layout_width="0dp" /> </LinearLayout>
看到裡面的 signal_cluster_view.xml 正是我們要找的訊號欄佈局檔案,內容有點多,下面只擷取我們關心的
vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\signal_cluster_view.xml
<com.android.systemui.statusbar.SignalClusterView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/signal_cluster" android:layout_height="match_parent" android:layout_width="wrap_content" android:gravity="center_vertical" android:orientation="horizontal" android:paddingEnd="@dimen/signal_cluster_battery_padding" > ... vpn ... 網絡卡 ... wifi 手機訊號欄 <LinearLayout android:id="@+id/mobile_signal_group" android:layout_height="wrap_content" android:layout_width="wrap_content" > </LinearLayout> 未插入SIM卡 <FrameLayout android:id="@+id/no_sims_combo" android:layout_height="wrap_content" android:layout_width="wrap_content" android:contentDescription="@string/accessibility_no_sims"> <com.android.systemui.statusbar.AlphaOptimizedImageView android:theme="?attr/lightIconTheme" android:id="@+id/no_sims" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/stat_sys_no_sims" /> <com.android.systemui.statusbar.AlphaOptimizedImageView android:theme="?attr/darkIconTheme" android:id="@+id/no_sims_dark" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/stat_sys_no_sims" android:alpha="0.0" /> </FrameLayout> <View android:id="@+id/wifi_airplane_spacer" android:layout_width="@dimen/status_bar_airplane_spacer_width" android:layout_height="4dp" android:visibility="gone" /> 飛航模式 <ImageView android:id="@+id/airplane" android:layout_height="wrap_content" android:layout_width="wrap_content" /> </com.android.systemui.statusbar.SignalClusterView>
可以看到最外層是自定義 SignalClusterView,xml裡包含了 vpn、網絡卡、wifi、手機訊號欄、未插入SIM卡、飛航模式對應的 view,那麼接下來看下 SignalClusterView 程式碼
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\SignalClusterView.java
public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback, SecurityController.SecurityControllerCallback, Tunable,DarkReceiver public SignalClusterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); Resources res = getResources(); mMobileSignalGroupEndPadding = res.getDimensionPixelSize(R.dimen.mobile_signal_group_end_padding); mMobileDataIconStartPadding = res.getDimensionPixelSize(R.dimen.mobile_data_icon_start_padding); mWideTypeIconStartPadding = res.getDimensionPixelSize(R.dimen.wide_type_icon_start_padding); mSecondaryTelephonyPadding = res.getDimensionPixelSize(R.dimen.secondary_telephony_padding); mEndPadding = res.getDimensionPixelSize(R.dimen.signal_cluster_battery_padding); mEndPaddingNothingVisible = res.getDimensionPixelSize( R.dimen.no_signal_cluster_battery_padding); TypedValue typedValue = new TypedValue(); res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); mIconScaleFactor = typedValue.getFloat(); //網路相關控制器 mNetworkController = Dependency.get(NetworkController.class); //安全相關控制器 mSecurityController = Dependency.get(SecurityController.class); updateActivityEnabled(); /// M: Add for Plugin feature @ { mStatusBarExt = OpSystemUICustomizationFactoryBase.getOpFactory(context) .makeSystemUIStatusBar(context); /// @ } mIsWfcEnable = SystemProperties.get("persist.mtk_wfc_support").equals("1"); }
看到 SignalClusterView 繼承自 LinearLayout,實現了 NetworkController、SecurityController(這倆類控制圖示的重新整理邏輯)
構造方法中通過 Dependency.get() 例項化 NetworkController、SecurityController這倆核心
類,跟進 Dependent 類中大概看了下 get()方法,其實就是通過單例模式來進行管理,裡面維護了一
個數據型別為 ArrayMap 的 DependencyProvider 集合物件,通過 put和 get 來存取。
接著回到 SignalClusterView 中看下控制元件都是怎麼初始化的?
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mVpn = findViewById(R.id.vpn);
mEthernetGroup = findViewById(R.id.ethernet_combo);
mEthernet = findViewById(R.id.ethernet);
mEthernetDark = findViewById(R.id.ethernet_dark);
mWifiGroup = findViewById(R.id.wifi_combo);
mWifi = findViewById(R.id.wifi_signal);
mWifiDark = findViewById(R.id.wifi_signal_dark);
mWifiActivityIn = findViewById(R.id.wifi_in);
mWifiActivityOut= findViewById(R.id.wifi_out);
mAirplane = findViewById(R.id.airplane);
mNoSims = findViewById(R.id.no_sims);
mNoSimsDark = findViewById(R.id.no_sims_dark);
mNoSimsCombo = findViewById(R.id.no_sims_combo);
mWifiAirplaneSpacer = findViewById(R.id.wifi_airplane_spacer);
mWifiSignalSpacer = findViewById(R.id.wifi_signal_spacer);
mMobileSignalGroup = findViewById(R.id.mobile_signal_group);
maybeScaleVpnAndNoSimsIcons();
}
這裡初始化了一堆剛剛佈局檔案裡的控制元件,onFinishInflate() 在 xml 佈局檔案被載入完成後就會調
用,我們看到佈局檔案中都沒有給控制元件設定對應的 background icon,而且有的 visibility 為
gone,那麼訊號欄圖示是如何設定對應的icon和顯示的呢?
二、SignalCluterView 詳解
SignalCluterView 中呼叫頻率很高的方法 apply() 就是幕後黑手,通過該方法控制一系列圖示的更
新, 然而 SignalCallback 的如下每個回撥最終都呼叫 apply()
1、setWifiIndicators() wifi開關狀態、流量上下行
2、setMobileDataIndicators() 手機網路型別、訊號強度、流量上下行、volte圖示
3、setSubs() SIM卡識別結束
4、setNoSims() 未插入SIM卡狀態
5、setEthernetIndicators() 網絡卡狀態
6、setIsAirplaneMode() 飛航模式是否開啟
7、setMobileDataEnabled() SIM卡資料流量是否開啟
1、apply()
private void apply() {
if (mWifiGroup == null) return;
//vpn圖示
if (mVpnVisible) {
if (mLastVpnIconId != mVpnIconId) {
setIconForView(mVpn, mVpnIconId);
mLastVpnIconId = mVpnIconId;
}
mIconLogger.onIconShown(SLOT_VPN);
mVpn.setVisibility(View.VISIBLE);
} else {
mIconLogger.onIconHidden(SLOT_VPN);
mVpn.setVisibility(View.GONE);
}
if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
//網絡卡圖示
if (mEthernetVisible) {
if (mLastEthernetIconId != mEthernetIconId) {
setIconForView(mEthernet, mEthernetIconId);
setIconForView(mEthernetDark, mEthernetIconId);
mLastEthernetIconId = mEthernetIconId;
}
mEthernetGroup.setContentDescription(mEthernetDescription);
mIconLogger.onIconShown(SLOT_ETHERNET);
mEthernetGroup.setVisibility(View.VISIBLE);
} else {
mIconLogger.onIconHidden(SLOT_ETHERNET);
mEthernetGroup.setVisibility(View.GONE);
}
if (DEBUG) Log.d(TAG,
String.format("ethernet: %s",
(mEthernetVisible ? "VISIBLE" : "GONE")));
//wifi圖示
if (mWifiVisible) {
if (mWifiStrengthId != mLastWifiStrengthId) {
setIconForView(mWifi, mWifiStrengthId);
setIconForView(mWifiDark, mWifiStrengthId);
mLastWifiStrengthId = mWifiStrengthId;
}
mIconLogger.onIconShown(SLOT_WIFI);
mWifiGroup.setContentDescription(mWifiDescription);
mWifiGroup.setVisibility(View.VISIBLE);
} else {
mIconLogger.onIconHidden(SLOT_WIFI);
mWifiGroup.setVisibility(View.GONE);
}
if (DEBUG) Log.d(TAG,
String.format("wifi: %s sig=%d",
(mWifiVisible ? "VISIBLE" : "GONE"),
mWifiStrengthId));
//wifi資料上下行圖示
mWifiActivityIn.setVisibility(mWifiIn ? View.VISIBLE : View.GONE);
mWifiActivityOut.setVisibility(mWifiOut ? View.VISIBLE : View.GONE);
boolean anyMobileVisible = false;
/// M: Support for [Network Type on Statusbar]
/// A spacer is set between networktype and WIFI icon @ {
if (FeatureOptions.MTK_CTA_SET) {
anyMobileVisible = true;
}
/// @ }
//SIM 卡組圖示
int firstMobileTypeId = 0;
for (PhoneState state : mPhoneStates) {
//PhoneState中的另一個apply()方法,對應網路型別、訊號格等
if (state.apply(anyMobileVisible)) {
if (!anyMobileVisible) {
firstMobileTypeId = state.mMobileTypeId;
anyMobileVisible = true;
}
}
}
if (anyMobileVisible) {
mIconLogger.onIconShown(SLOT_MOBILE);
} else {
mIconLogger.onIconHidden(SLOT_MOBILE);
}
//飛航模式圖示
if (mIsAirplaneMode) {
if (mLastAirplaneIconId != mAirplaneIconId) {
setIconForView(mAirplane, mAirplaneIconId);
mLastAirplaneIconId = mAirplaneIconId;
}
mAirplane.setContentDescription(mAirplaneContentDescription);
mIconLogger.onIconShown(SLOT_AIRPLANE);
mAirplane.setVisibility(VISIBLE);
} else {
mIconLogger.onIconHidden(SLOT_AIRPLANE);
mAirplane.setVisibility(View.GONE);
}
//wifi和飛航模式間隔
if (mIsAirplaneMode && mWifiVisible) {
mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
} else {
mWifiAirplaneSpacer.setVisibility(View.GONE);
}
if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) {
mWifiSignalSpacer.setVisibility(View.VISIBLE);
} else {
mWifiSignalSpacer.setVisibility(View.GONE);
}
//未插入SIM卡圖示組
if (mNoSimsVisible) {
mIconLogger.onIconShown(SLOT_MOBILE);
mNoSimsCombo.setVisibility(View.VISIBLE);
if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) {
mNoSimsCombo.setTag(mSimDetected);
/// M:alps03596830 Don't show lack of signal when airplane mode is on.
if (mSimDetected && !mIsAirplaneMode) {
SignalDrawable d = new SignalDrawable(mNoSims.getContext());
d.setDarkIntensity(0);
mNoSims.setImageDrawable(d);
mNoSims.setImageLevel(SignalDrawable.getEmptyState(4));
SignalDrawable dark = new SignalDrawable(mNoSims.getContext());
dark.setDarkIntensity(1);
mNoSimsDark.setImageDrawable(dark);
mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4));
} else {
mNoSims.setImageResource(R.drawable.stat_sys_no_sims);
mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims);
}
}
} else {
mIconLogger.onIconHidden(SLOT_MOBILE);
mNoSimsCombo.setVisibility(View.GONE);
}
/// M: Add for Plugin feature @ {
mStatusBarExt.setCustomizedNoSimsVisible(mNoSimsVisible);
mStatusBarExt.setCustomizedAirplaneView(mNoSimsCombo, mIsAirplaneMode);
/// @ }
boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
|| anyMobileVisible || mVpnVisible || mEthernetVisible;
setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
}
通過上面的程式碼發現 apply 控制了 VPN、網絡卡、wifi、飛航模式、未插入SIM 這幾種圖示的顯示和隱
藏,我們看到裡面有另外一個 state.apply(anyMobileVisible) 用來控制 SIM卡相關的圖示,接下
來看下都有哪些圖示呢?
2、內部類 PhoneState
PhoneState 是 SignalClusterView 中一個內部類,控制volte、網路型別、資料是否開啟、訊號格
數、漫遊等圖示
private class PhoneState {
//SIM卡id
private final int mSubId;
//SIM卡組是否可見
private boolean mMobileVisible = false;
//訊號格數圖示、資料流量是否開啟圖示(關閉是X,開啟是網路型別小圖示4G/3G)
private int mMobileStrengthId = 0, mMobileTypeId = 0;
///M: Add for [Network Type and volte on Statusbar]
//網路型別圖示
private int mNetworkIcon = 0;
//volte圖示
private int mVolteIcon = 0;
private int mLastMobileStrengthId = -1;
private int mLastMobileTypeId = -1;
private boolean mIsMobileTypeIconWide;
//網路型別描述,運營商型別,或只能撥打緊急號碼
private String mMobileDescription, mMobileTypeDescription;
//整個PhoneState根本局,SIM訊號欄組
private ViewGroup mMobileGroup;
//訊號格控制元件、流量圖示控制元件、是否漫遊控制元件
private ImageView mMobile, mMobileDark, mMobileType, mMobileRoaming;
public boolean mRoaming;
//手機流量上下行控制元件
private ImageView mMobileActivityIn;
private ImageView mMobileActivityOut;
public boolean mActivityIn;
public boolean mActivityOut;
/// M: Add for new features @ {
// Add for [Network Type and volte on Statusbar]
//網路型別控制元件
private ImageView mNetworkType;
//volte控制元件
private ImageView mVolteType;
private boolean mIsWfcCase;
/// @ }
/// M: Add for plugin features. @ {
private boolean mDataActivityIn, mDataActivityOut;
private ISystemUIStatusBarExt mPhoneStateExt;
/// @ }
public PhoneState(int subId, Context context) {
//載入 mobile_signal_group_ext 佈局檔案
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
.inflate(R.layout.mobile_signal_group_ext, null);
/// M: Add data group for plugin feature. @ {
mPhoneStateExt = OpSystemUICustomizationFactoryBase.getOpFactory(context)
.makeSystemUIStatusBar(context);
mPhoneStateExt.addCustomizedView(subId, context, root);
/// @ }
setViews(root);
mSubId = subId;
}
//控制元件初始化
public void setViews(ViewGroup root) {
mMobileGroup = root;
mMobile = root.findViewById(R.id.mobile_signal);
mMobileDark = root.findViewById(R.id.mobile_signal_dark);
mMobileType = root.findViewById(R.id.mobile_type);
///M: Add for [Network Type and volte on Statusbar]
mNetworkType = (ImageView) root.findViewById(R.id.network_type);
mVolteType = (ImageView) root.findViewById(R.id.volte_indicator_ext);
mMobileRoaming = root.findViewById(R.id.mobile_roaming);
mMobileActivityIn = root.findViewById(R.id.mobile_in);
mMobileActivityOut = root.findViewById(R.id.mobile_out);
// TODO: Remove the 2 instances because now the drawable can handle darkness.
mMobile.setImageDrawable(new SignalDrawable(mMobile.getContext()));
SignalDrawable drawable = new SignalDrawable(mMobileDark.getContext());
drawable.setDarkIntensity(1);
mMobileDark.setImageDrawable(drawable);
}
public boolean apply(boolean isSecondaryIcon) {
Log.e(TAG, "apply() mMobileVisible = " + mMobileVisible
+ ", mIsAirplaneMode = " + mIsAirplaneMode
+ ", mIsWfcEnable = " + mIsWfcEnable
+ ", mIsWfcCase = " + mIsWfcCase
+ ", mVolteIcon = " + mVolteIcon);
if (mMobileVisible && !mIsAirplaneMode) {
Log.e(TAG, "apply() into this code 1.. mMobileStrengthId=="+mMobileStrengthId);
//設定訊號格數
if (mLastMobileStrengthId != mMobileStrengthId) {
mMobile.getDrawable().setLevel(mMobileStrengthId);
mMobileDark.getDrawable().setLevel(mMobileStrengthId);
mLastMobileStrengthId = mMobileStrengthId;
}
//設定流量是否開啟
if (mLastMobileTypeId != mMobileTypeId) {
if (!mPhoneStateExt.disableHostFunction()) {
mMobileType.setImageResource(mMobileTypeId);
}
mLastMobileTypeId = mMobileTypeId;
}
mMobileGroup.setContentDescription(mMobileTypeDescription
+ " " + mMobileDescription);
mMobileGroup.setVisibility(View.VISIBLE);
showViewInWfcCase();
} else {
if (mIsAirplaneMode && (mIsWfcEnable && mVolteIcon != 0)) {
Log.e(TAG, "apply() into this code 2..");
/// M:Bug fix for show vowifi icon in flight mode
mMobileGroup.setVisibility(View.VISIBLE);
hideViewInWfcCase();
} else {
Log.e(TAG, "apply() into this code 3..");
if (DEBUG) {
Log.d(TAG, "setVisibility as GONE, this = " + this
+ ", mMobileVisible = " + mMobileVisible
+ ", mIsAirplaneMode = " + mIsAirplaneMode
+ ", mIsWfcEnable = " + mIsWfcEnable
+ ", mVolteIcon = " + mVolteIcon);
}
mMobileGroup.setVisibility(View.GONE);
}
}
/// M: Set all added or customised view. @ {
//更新網路型別和volte圖示
setCustomizeViewProperty();
/// @ }
// When this isn't next to wifi, give it some extra padding between the signals.
mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
0, 0, 0);
mMobile.setPaddingRelative(
mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding,
0, 0, 0);
mMobileDark.setPaddingRelative(
mIsMobileTypeIconWide ? mWideTypeIconStartPadding : mMobileDataIconStartPadding,
0, 0, 0);
if (true) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
(mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
Log.e(TAG, "mActivityIn="+mActivityIn+" mActivityOut="+mActivityOut);
if(!mIsWfcCase) {
//更新流量是否開啟可見性
mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE :View.GONE);
//漫遊圖示
mMobileRoaming.setVisibility(mRoaming ? View.VISIBLE : View.GONE);
//流量上下行圖示
mMobileActivityIn.setVisibility(mActivityIn ? View.VISIBLE : View.GONE);
mMobileActivityOut.setVisibility(mActivityOut ? View.VISIBLE : View.GONE);
}
/// M: Add for support plugin featurs. @ {
//可通過op01/2/3等載入SystemUI,從6.0延伸來的
setCustomizedOpViews();
/// @ }
return mMobileVisible;
}
......
}
PhoneState 中的成員變數較多,我在程式碼裡都已經加了註釋了,就不細說了。載入的佈局檔案為
vendor\mediatek\proprietary\packages\apps\SystemUI\res_ext\layout\mobile_signal_group_ext.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Support [Network Type on Statusbar] The layout to wrap original
mobile_signal_group and add image view for show network Type -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
>
<ImageView
android:id="@+id/volte_indicator_ext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
/>
<ImageView
android:id="@+id/network_type"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:visibility="gone"
/>
<include layout="@layout/mobile_signal_group"/>
</LinearLayout>
佈局檔案中對應了 volte 和當前網路型別大圖示, 再包含了 mobile_signal_group,裡面就是上面提到的各種控制元件
vendor\mediatek\proprietary\packages\apps\SystemUI\res\layout\mobile_signal_group.xml
3、state.apply(anyMobileVisible) 呼叫流程
通過上面的分析可以總結下 訊號欄的呼叫流程
NetworkControllerImpl.java 中註冊了SIM卡狀態改變廣播(ACTION_SIM_STATE_CHANGED),當收
到廣播通知後呼叫到 notifyAllListeners()
通知所有的監聽,mobileSignalController、mWifiSignalController、
mEthernetSignalController,分別對應手機訊號顯示、wifi訊號、網絡卡顯示通知
我們看到 mobileSignalController 中的回撥 notifyListeners(SignalCallback callback),
在進行一系列的賦值操作後,最終回撥到
SignalClusterView 中的 setMobileDataIndicators(),給 PhoneState 的成員變數賦值,
最後通過 apply()進行更新
4、PhoneState建立
通過剛剛對 PhoneState 類的介紹,發現通過構造方法 PhoneState(int subId, Context context) 可獲取 PhoneState 物件
搜尋當前檔案找到在 inflatePhoneState(int subId) 中例項化 PhoneState
@Override
public void setSubs(List<SubscriptionInfo> subs) {
if (DEBUG) {
Log.d(TAG, "setSubs, this = " + this + ", size = " + subs.size()
+ ", subs = " + subs);
}
if (hasCorrectSubs(subs)) {//判斷SIM卡是否改變
if (DEBUG) {
Log.d(TAG, "setSubs, hasCorrectSubs and return");
}
return;
}
mPhoneStates.clear();
if (mMobileSignalGroup != null) {
mMobileSignalGroup.removeAllViews();//移除手機訊號組中的所有view
}
final int n = subs.size();//SIM卡數量
Log.d(TAG, "setSubs-clear subsize:" + subs.size() + "mStes" + mPhoneStates + ":" + this);
for (int i = 0; i < n; i++) {
inflatePhoneState(subs.get(i).getSubscriptionId());
}
if (isAttachedToWindow()) {
applyIconTint();//圖示根據背景色動態變化
}
}
private PhoneState inflatePhoneState(int subId) {
PhoneState state = new PhoneState(subId, mContext);
if (mMobileSignalGroup != null) {
mMobileSignalGroup.addView(state.mMobileGroup);//新增view到手機訊號組中
}
Log.d(TAG, "inflatePhoneState add subId:" + subId + ",state" + state + ":" + this);
mPhoneStates.add(state);//儲存PhoneState 物件,方便快速修改狀態
return state;
}
當SIM卡插入識別後將回調 setSubs(),先判斷SIM卡是否改變,有效則移除 mobileGroup 中已新增
所有view,遍歷SIM卡數量,通過 subId 建立 PhoneState,並將對應的view新增到 mobileGroup 中。
然後將 PhoneState物件儲存到集合中,方便快速修改狀態
這裡簡單說下 subId subid對應卡,slotid對應卡槽
slotid或者phoneid是指卡槽,雙卡機器的卡槽1值為0,卡槽2值為1,依次類推
subid的值從1開始,每插入一個新卡,subId的值就會加1。
插入雙卡後資料庫中就會有subid值為1和2的兩個資料條目,
拔卡插卡交換卡槽後,資料庫並不會增加新項,只有插入一張新的sim卡才會增加一條id為3的資料條目
詳細的介紹請看這篇 subId、slotId
5、SIM卡插入後更新圖示流程
PhoneState 建立成功了並存到集合中,當收到 setMobileDataIndicators()回撥後
給 PhoneState 成員變數賦值,賦值結束通過apply()更新
還記得上面說過的 apply() 中更新SIM卡圖示的邏輯吧,
遍歷 mPhoneStates 集合,呼叫PhoneState的apply()將成員變數值設定給對應的控制元件
int firstMobileTypeId = 0;
for (PhoneState state : mPhoneStates) {
if (state.apply(anyMobileVisible)) {
if (!anyMobileVisible) {
firstMobileTypeId = state.mMobileTypeId;
anyMobileVisible = true;
}
}
}
那麼 setMobileDataIndicators() 是從哪裡回撥過來的呢?
分析找到 MobileSignalController 中的 notifyListeners()
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\MobileSignalController.java
@Override
public void notifyListeners(SignalCallback callback) {
//獲取資源ID組
MobileIconGroup icons = getIcons();
String contentDescription = getStringIfExists(getContentDescription());
String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
//移動資料是否開啟
final boolean dataDisabled = mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
&& mCurrentState.userSetup;
/// M: Customize the signal strength icon id. @ {
//當前手機訊號格數資源ID
int iconId = getCurrentIconId();
//使用者可自定義的資源ID,該方法將目前的iconId原值賦給了iconID,如需定製可在此修改
iconId = mStatusBarExt.getCustomizeSignalStrengthIcon(
mSubscriptionInfo.getSubscriptionId(),
iconId,
mSignalStrength,
mDataNetType,
mServiceState);
/// @ }
// Show icon in QS when we are connected or data is disabled.
//是否顯示移動資料圖示
boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
//是否顯示 mobileGroup、訊號格數、SIM卡資訊描述
IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
iconId, contentDescription);
int qsTypeIcon = 0;
IconState qsIcon = null;
String description = null;
// Only send data sim callbacks to QS.
if (mCurrentState.dataSim) {
qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
qsIcon = new IconState(mCurrentState.enabled
&& !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
//狀態列顯示只能撥打緊急電話或當前的網路型別
description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
}
//資料下行
boolean activityIn = mCurrentState.dataConnected
&& !mCurrentState.carrierNetworkChangeMode
&& mCurrentState.activityIn;
//資料上行
boolean activityOut = mCurrentState.dataConnected
&& !mCurrentState.carrierNetworkChangeMode
&& mCurrentState.activityOut;
showDataIcon &= mCurrentState.isDefault || dataDisabled;
//移動資料型別資源ID,關閉是X,開啟是小的網路型別4G/3G/2G
int typeIcon = showDataIcon ? icons.mDataType : 0;
/// M: Add for lwa.
typeIcon = mCurrentState.lwaRegState == NetworkTypeUtils.LWA_STATE_CONNCTED
&& showDataIcon ? NetworkTypeUtils.LWA_ICON : typeIcon;
/** M: Support [Network Type on StatusBar], change the implement methods.
* Get the network icon base on service state.
* Add one more parameter for network type.
* @ { **/
//當前網路型別資源ID
int networkIcon = mCurrentState.networkIcon;
/// M: Support volte icon.Bug fix when airplane mode is on go to hide volte icon
//VOlTE資源ID
int volteIcon = mCurrentState.airplaneMode && !isImsOverWfc()
? 0 : mCurrentState.volteIcon;
/// M: when data disabled, common show data icon as x, but op do not need show it @ {
mStatusBarExt.isDataDisabled(mSubscriptionInfo.getSubscriptionId(), dataDisabled);
/// @ }
/// M: Customize the data type icon id. @ {
//可自定義移動資料型別資源ID(比如常見的上下箭頭)
typeIcon = mStatusBarExt.getDataTypeIcon(
mSubscriptionInfo.getSubscriptionId(),
typeIcon,
mDataNetType,
mCurrentState.dataConnected ? TelephonyManager.DATA_CONNECTED :
TelephonyManager.DATA_DISCONNECTED,
mServiceState);
/// @ }
/// M: Customize the network type icon id. @ {
//可自定義網路型別資源ID
networkIcon = mStatusBarExt.getNetworkTypeIcon(
mSubscriptionInfo.getSubscriptionId(),
networkIcon,
mDataNetType,
mServiceState);
callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, networkIcon, volteIcon,
qsTypeIcon,activityIn, activityOut, dataContentDescription, description,
icons.mIsWide, mSubscriptionInfo.getSubscriptionId(), mCurrentState.roaming);
/// M: update plmn label @{
mNetworkController.refreshPlmnCarrierLabel();
/// @}
}
這個方法比較重要,上面寫了簡單的註釋,接下來我們會詳細看下每個資源ID都是如何獲取的?
在這之前我們先介紹下幾個重要的 Bean 類
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\SignalController.java
State
static class State {
boolean connected;
boolean enabled;
boolean activityIn;
boolean activityOut;
int level;
IconGroup iconGroup;
int inetCondition;
int rssi;
...
}
IconGroup
static class IconGroup {
final int[][] mSbIcons;
final int[][] mQsIcons;
final int[] mContentDesc;
final int mSbNullState;
final int mQsNullState;
final int mSbDiscState;
final int mQsDiscState;
final int mDiscContentDesc;
// For logging.
final String mName;
....
}
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\MobileSignalController.java
MobileState
static class MobileState extends SignalController.State {
String networkName;//當前網路型別
String networkNameData;//移動資料網路型別
boolean dataSim;
boolean dataConnected;//資料是否連線
boolean isEmergency;//是否是緊急電話模式
boolean airplaneMode;//是否是飛航模式
boolean carrierNetworkChangeMode;//SIM卡網路型別是否改變
boolean isDefault;
boolean userSetup;//是否是使用者操作
boolean roaming;//是否漫遊
/// M: Add for 4G+W
int lwaRegState = NetworkTypeUtils.LWA_STATE_UNKNOWN;
/// M: For network type big icon.
int networkIcon;//網路型別大圖示資源ID
/// M: Add for data network type.
int dataNetType;//移動資料網路型別
/// M: Add for op network tower type.
int customizedState;//自定義狀態
/// M: Add for op signal strength tower icon.
int customizedSignalStrengthIcon;//自定義訊號格資源ID
/// M: Add for volte @{
int imsRegState = ServiceState.STATE_POWER_OFF;
int imsCap;
int volteIcon;//volte資源ID
......
}
MobileIconGroup
static class MobileIconGroup extends SignalController.IconGroup {
final int mDataContentDescription; // mContentDescriptionDataType
final int mDataType;//移動資料網路型別資源ID
final boolean mIsWide;
final int mQsDataType;//下拉快捷訪問資源
...
}
好了重要的Bean類介紹完了,接下來又要說一個重要的方法了 updateTelephony()
還是在 MobileSignalController.java 中
private final void updateTelephony() {
if (DEBUG && FeatureOptions.LOG_ENABLE) {
Log.d(mTag, "updateTelephonySignalStrength: hasService=" + hasService()
+ " ss=" + mSignalStrength);
}
//連線狀態,是否在服務中
mCurrentState.connected = hasService() && mSignalStrength != null;
handleIWLANNetwork();
if (mCurrentState.connected) {
//SIM 卡訊號格數級別 0~4格
if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
mCurrentState.level = mSignalStrength.getCdmaLevel();
} else {
mCurrentState.level = mSignalStrength.getLevel();
}
/// M: Customize the signal strength level. @ {
//客戶可自定義
mCurrentState.level = mStatusBarExt.getCustomizeSignalStrengthLevel(
mCurrentState.level, mSignalStrength, mServiceState);
/// @ }
}
//當前網路型別獲取對應的圖示組
if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
} else {
mCurrentState.iconGroup = mDefaultIcons;
}
/// M: Add for data network type.
//資料網路型別
mCurrentState.dataNetType = mDataNetType;
//資料狀態
mCurrentState.dataConnected = mCurrentState.connected
&& mDataState == TelephonyManager.DATA_CONNECTED;
/// M: Add for op network tower type.
mCurrentState.customizedState = mStatusBarExt.getCustomizeCsState(mServiceState,
mCurrentState.customizedState);
/// M: Add for op signal strength tower icon.
mCurrentState.customizedSignalStrengthIcon = mStatusBarExt.getCustomizeSignalStrengthIcon(
mSubscriptionInfo.getSubscriptionId(),
mCurrentState.customizedSignalStrengthIcon,
mSignalStrength,
mDataNetType,
mServiceState);
mCurrentState.roaming = isRoaming();
if (isCarrierNetworkChangeActive()) {
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
} else if (isDataDisabled()) {//資料未開啟,對應x
mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}
if (isEmergencyOnly() != mCurrentState.isEmergency) {
mCurrentState.isEmergency = isEmergencyOnly();
mNetworkController.recalculateEmergency();
}
// Fill in the network name if we think we have it.
//當前網路運營商
if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
&& !TextUtils.isEmpty(mServiceState.getOperatorAlphaShort())) {
mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
}
/// M: For network type big icon. 網路型別大圖示
mCurrentState.networkIcon =
NetworkTypeUtils.getNetworkTypeIcon(mServiceState, mConfig, hasService());
/// M: For volte type icon. volte圖示
mCurrentState.volteIcon = getVolteIcon();
//通知更新,最終回撥到notifyListeners()中
notifyListenersIfNecessary();
}
基本上獲取資源ID的方法都在 updateTelephony()中了,那麼都在那裡呼叫了 updateTelephony()?
MobileSignalController中構造方法初始化了 MobilePhoneStateListener 分別監聽了
mPhone.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_SERVICE_STATE//服務狀態改變,可用、不可用
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS//訊號強度改變,用於獲取dbm、asu
| PhoneStateListener.LISTEN_CALL_STATE//電話狀態改變,空閒、來電、通話
| PhoneStateListener.LISTEN_DATA_CONNECTION_STATE//資料網路連線狀態,網路斷開、正在連線中、已連線上
| PhoneStateListener.LISTEN_DATA_ACTIVITY//資料上下行狀態
| PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);//網路狀態傳送改變
class MobilePhoneStateListener extends PhoneStateListener {
public MobilePhoneStateListener(int subId, Looper looper) {
super(subId, looper);
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
...
updateTelephony();
}
@Override
public void onServiceStateChanged(ServiceState state) {
...
updateTelephony();
}
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
...
updateTelephony();
}
@Override
public void onDataActivity(int direction) {
...
setActivity(direction);
}
@Override
public void onCarrierNetworkChange(boolean active) {
...
updateTelephony();
}
/// M: Add for Plugin feature. @{
@Override
public void onCallStateChanged(int state, String incomingNumber) {
...
updateTelephony();
}
/// @}
};
LISTEN_SIGNAL_STRENGTHS、LISTEN_CALL_STATE、LISTEN_CARRIER_NETWORK_CHANGE
這三個監聽應該是我們平常用較多的,好了說了這麼久,接下來重要看獲取資源ID的具體方法了
5.1、Vlote資源ID
mCurrentState.volteIcon = getVolteIcon();
private int getVolteIcon() {
int icon = 0;
if (isImsOverWfc()) {
boolean needShowWfcSysIcon = mStatusBarExt.needShowWfcIcon();
if (needShowWfcSysIcon) {
icon = NetworkTypeUtils.WFC_ICON;
}
} else if (isImsOverVoice() && isLteNetWork()) {
if (mCurrentState.imsRegState == ServiceState.STATE_IN_SERVICE) {
//volte可用
icon = NetworkTypeUtils.VOLTE_ICON;
} else if(FeatureOptions.MTK_CT_MIXED_VOLTE_SUPPORT &&
SIMHelper.isSecondaryCSIMForMixedVolte(mSubscriptionInfo.getSubscriptionId()) &&
mCurrentState.imsRegState == ServiceState.STATE_OUT_OF_SERVICE) {
if (DEBUG) {
Log.d(mTag, "set dis volte icon");
}//volte不可用
icon = NetworkTypeUtils.VOLTE_DIS_ICON;
}
}
/// M: add for disconnected volte feature. @{
mStatusBarExt.setImsRegInfo(mSubscriptionInfo.getSubscriptionId(),
mCurrentState.imsRegState, isImsOverWfc(), isImsOverVoice());
/// @}
return icon;
}
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\mediatek\systemui\statusbar\networktype\NetworkTypeUtils.java
public static final int VOLTE_ICON = R.drawable.stat_sys_volte;
5.2、網路型別大圖示資源ID
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\mediatek\systemui\statusbar\networktype\NetworkTypeUtils.java
mCurrentState.networkIcon =
NetworkTypeUtils.getNetworkTypeIcon(mServiceState, mConfig, hasService());
public static int getNetworkTypeIcon(ServiceState serviceState, Config config,
boolean hasService) {
if (!hasService) {
// Not in service, no network type. 未註冊成功,比如廢卡、停機卡
return 0;
}
//通過 serviceState 獲取當前註冊的網路型別
int tempNetworkType = getNetworkType(serviceState);
Integer iconId = sNetworkTypeIcons.get(tempNetworkType);
if (iconId == null) {
iconId = tempNetworkType == TelephonyManager.NETWORK_TYPE_UNKNOWN ? 0 :
config.showAtLeast3G ? R.drawable.stat_sys_network_type_3g :
R.drawable.stat_sys_network_type_g;
}
return iconId.intValue();
}
private static int getNetworkType(ServiceState serviceState) {
int type = TelephonyManager.NETWORK_TYPE_UNKNOWN;
if (serviceState != null) {
type = serviceState.getDataNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN ?
serviceState.getDataNetworkType() : serviceState.getVoiceNetworkType();
}
return type;
}
//網路型別-資源ID 4g/3g/2g/e/1x
static final Map<Integer, Integer> sNetworkTypeIcons = new HashMap<Integer, Integer>() {
{
// For CDMA 3G
put(TelephonyManager.NETWORK_TYPE_EVDO_0, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_EVDO_A, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_EVDO_B, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_EHRPD, R.drawable.stat_sys_network_type_3g);
// For CDMA 1x
put(TelephonyManager.NETWORK_TYPE_CDMA, R.drawable.stat_sys_network_type_1x);
put(TelephonyManager.NETWORK_TYPE_1xRTT, R.drawable.stat_sys_network_type_1x);
// Edge
put(TelephonyManager.NETWORK_TYPE_EDGE, R.drawable.stat_sys_network_type_e);
// 3G
put(TelephonyManager.NETWORK_TYPE_UMTS, R.drawable.stat_sys_network_type_3g);
// For 4G
put(TelephonyManager.NETWORK_TYPE_LTE, R.drawable.stat_sys_network_type_4g);
// 3G
put(TelephonyManager.NETWORK_TYPE_HSDPA, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_HSUPA, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_HSPA, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_HSPAP, R.drawable.stat_sys_network_type_3g);
put(TelephonyManager.NETWORK_TYPE_IWLAN, 0);
}
};
5.3、移動資料型別資源ID
//mNetworkToIconLookup 和上面的網路型別 Map有點類似,鍵都是網路型別,
//不同的是,這次的key是上面介紹過的 MobileIconGroup
if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
} else {
mCurrentState.iconGroup = mDefaultIcons;
}
if (isCarrierNetworkChangeActive()) {
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
} else if (isDataDisabled()) {
mCurrentState.iconGroup = TelephonyIcons.DATA_DISABLED;
}
vendor\mediatek\proprietary\packages\apps\SystemUI\src\com\android\systemui\statusbar\policy\TelephonyIcons.java
class TelephonyIcons {
//***** Data connection icons
//狀態列快捷訪問,其實和下面的差多不
static final int QS_DATA_G = R.drawable.ic_qs_signal_g;
static final int QS_DATA_3G = R.drawable.ic_qs_signal_3g;
static final int QS_DATA_E = R.drawable.ic_qs_signal_e;
static final int QS_DATA_H = R.drawable.ic_qs_signal_h;
static final int QS_DATA_1X = R.drawable.ic_qs_signal_1x;
static final int QS_DATA_4G = R.drawable.ic_qs_signal_4g;
static final int QS_DATA_4G_PLUS = R.drawable.ic_qs_signal_4g_plus;
static final int QS_DATA_LTE = R.drawable.ic_qs_signal_lte;
static final int QS_DATA_LTE_PLUS = R.drawable.ic_qs_signal_lte_plus;
static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
//此處的圖示為小圖示,網路型別
static final int ICON_LTE = R.drawable.stat_sys_data_fully_connected_lte;
static final int ICON_LTE_PLUS = R.drawable.stat_sys_data_fully_connected_lte_plus;
static final int ICON_G = R.drawable.stat_sys_data_fully_connected_g;
static final int ICON_E = R.drawable.stat_sys_data_fully_connected_e;
static final int ICON_H = R.drawable.stat_sys_data_fully_connected_h;
static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g;
static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g;
static final int ICON_4G_PLUS = R.drawable.stat_sys_data_fully_connected_4g_plus;
static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x;
//流量未開啟
static final int ICON_DATA_DISABLED = R.drawable.stat_sys_data_disabled;
static final int QS_ICON_DATA_DISABLED = R.drawable.ic_qs_data_disabled;
...
static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
"DataDisabled",
null,
null,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
0, 0,
0,
0,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
R.string.accessibility_cell_data_off,
TelephonyIcons.ICON_DATA_DISABLED,//這個值對應的就是 移動資料型別資源ID
false,
TelephonyIcons.QS_ICON_DATA_DISABLED
);
}
MobileIconGroup 的倒數第三個引數就是 移動資料型別資源ID
5.4、訊號格數資源ID
if (mCurrentState.connected) {
if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
mCurrentState.level = mSignalStrength.getCdmaLevel();
} else {
mCurrentState.level = mSignalStrength.getLevel();
}
/// M: Customize the signal strength level. @ {
mCurrentState.level = mStatusBarExt.getCustomizeSignalStrengthLevel(
mCurrentState.level, mSignalStrength, mServiceState);
/// @ }
}
訊號格數對應的是 SignalDrawable,通過 setLevel()來控制顯示幾格,其實以上的大部分資源ID都是
通過 Vector 標籤繪製而來的,裡面都是一堆 path,開始看可能會覺得很迷糊,可以把xml檔案拷貝到
AS中進行預覽,再學上一些基礎語法就可對簡單的圖形進行自定義修改。比方說6.0的訊號格數是通過
vector 繪製的,格與格之間是有間隔,而8.1是通過 SignalDrawable繪製,是一個填滿的三角形
修改前樣式
修改後樣式
來看下 mSignalStrength.getLevel() 方法
frameworks/base/telephony/java/android/telephony/SignalStrength.java
public int getLevel() {
int level = 0;
if (isGsm) { //移動或聯通卡
level = getLteLevel(); //首先獲取4G訊號格
if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { //未獲取到
level = getTdScdmaLevel(); //獲取移動或聯通的3G訊號格
if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {//仍然未獲取
level = getGsmLevel(); //獲取移動或聯通的2G訊號格
}
}
} else {//電信
int cdmaLevel = getCdmaLevel(); //獲取電信2G訊號格
int evdoLevel = getEvdoLevel(); //獲取電信3G訊號格
if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
/* We don't know evdo, use cdma */
level = cdmaLevel;
} else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
/* We don't know cdma, use evdo */
level = evdoLevel;
} else {
/* We know both, use the lowest level */
level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
}
}
if (DBG) log("getLevel=" + level);
return level;
}
在此介紹下手機是幾模的配置:GSM是移動和聯通公用的band;LTE從編碼方式上分為TDD和FDD,從頻段上分有各種不同的band
移動:GSM、TDSCDMA、LTE(TDD)
聯通:GSM、WCDMA、LTE(FDD)
電信:CDMA、EVDO、LTE(FDD)
因此如果手機支援GSM、WCDMA、TDSCDMA、TDD-LTE、FDD-LTE 這是五模;加上 CDMA、EVDO 就是七模
5.5、漫遊資源ID R.drawable.stat_sys_roaming
大寫的R
三、總結
訊號欄的定製還是很容易的,只要理清楚了控制元件和對應的回撥邏輯,加上日誌列印,就能搞定你想要的效果。
四、相關資源
這裡附上我定製使用的drawable檔案
drawable.