Android 当点击飞行模式都发生了什么?

今天中午午休时,我把手机开飞行模式了,能看到 WiFi 、蜂窝数据和蓝牙都关闭了,心想,这时候还能收到短信吗?顺着好奇心,我们不妨来研究一下源码,看看点击飞行模式都发生了什么?

基于 Android 9.0 源码分析。

AirplaneModeTile#handleClick

飞行模式设置入口,下拉状态栏,点击飞行模式图标,我们就从这里看起,其他入口逻辑差不多。这个源码位于AOSP/frameworks/base/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java直接看其点击事件。

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
public class AirplaneModeTile extends QSTileImpl<BooleanState> {
//省略其他代码
@Override
public void handleClick() {
boolean airplaneModeEnabled = mState.value;
MetricsLogger.action(mContext, getMetricsCategory(), !airplaneModeEnabled);
if (!airplaneModeEnabled && Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0);
return;
}
setEnabled(!airplaneModeEnabled);
}
private void setEnabled(boolean enabled) {
final ConnectivityManager mgr =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mgr.setAirplaneMode(enabled);
}
//省略其他代码
}

接下来调用 ConnectivityManager#setAirplaneMode 方法。

ConnectivityManager#setAirplaneMode

AOSP/frameworks/base/core/java/android/net/ConnectivityManager.java

1
2
3
4
5
6
7
8
9
10
11
12
@RequiresPermission(anyOf = {
android.Manifest.permission.NETWORK_SETTINGS,
android.Manifest.permission.NETWORK_SETUP_WIZARD,
android.Manifest.permission.NETWORK_STACK})
@SystemApi
public void setAirplaneMode(boolean enable) {
try {
mService.setAirplaneMode(enable);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

mService 是 IConnectivityManager,是个接口,其实现看 ConnectivityService#setAirplaneMode。

ConnectivityService#setAirplaneMode

/AOSP/frameworks/base/services/core/java/com/android/server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void setAirplaneMode(boolean enable) {
enforceNetworkStackSettingsOrSetup();
final long ident = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, encodeBool(enable));
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.putExtra("state", enable);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(ident);
}
}

这里设置了飞行模式状态的系统变量以及发送 ACTION_AIRPLANE_MODE_CHANGED 系统广播,接下来看看该广播的接受。经过查找,ACTION_AIRPLANE_MODE_CHANGED 广播接受有好几次,,而手机开启或关闭飞行模式时,主要是开启或关闭 Radio 无线通信,其处理逻辑在 PhoneGlobals 类中。另外 WiFi 、蜂窝数据和蓝牙处理都能相应追踪到。

补充:什么是 Radio?Radio 是无线通信模块的驱动程序,负责网络通信。

PhoneGlobals

AOSP/packages/services/Telephony/src/com/android/phone/PhoneGlobals.java

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
public class PhoneGlobals extends ContextWrapper {
//省略其他代码
private void handleAirplaneModeChange(Context context, int newMode) {
int cellState = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.CELL_ON, PhoneConstants.CELL_ON_FLAG);
boolean isAirplaneNewlyOn = (newMode == 1);
switch (cellState) {
case PhoneConstants.CELL_OFF_FLAG:
// Airplane mode does not affect the cell radio if user
// has turned it off.
break;
case PhoneConstants.CELL_ON_FLAG:
maybeTurnCellOff(context, isAirplaneNewlyOn);
break;
case PhoneConstants.CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG:
maybeTurnCellOn(context, isAirplaneNewlyOn);
break;
}
}
/*
* Returns true if the radio must be turned off when entering airplane mode.
*/
private boolean isCellOffInAirplaneMode(Context context) {
String airplaneModeRadios = Settings.Global.getString(context.getContentResolver(),
Settings.Global.AIRPLANE_MODE_RADIOS);
return airplaneModeRadios == null
|| airplaneModeRadios.contains(Settings.Global.RADIO_CELL);
}
private void setRadioPowerOff(Context context) {
Log.i(LOG_TAG, "Turning radio off - airplane");
Settings.Global.putInt(context.getContentResolver(), Settings.Global.CELL_ON,
PhoneConstants.CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG);
SystemProperties.set("persist.radio.airplane_mode_on", "1");
Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT, 0);
PhoneUtils.setRadioPower(false);
}
private void setRadioPowerOn(Context context) {
Log.i(LOG_TAG, "Turning radio on - airplane");
Settings.Global.putInt(context.getContentResolver(), Settings.Global.CELL_ON,
PhoneConstants.CELL_ON_FLAG);
Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT,
1);
SystemProperties.set("persist.radio.airplane_mode_on", "0");
PhoneUtils.setRadioPower(true);
}
private void maybeTurnCellOff(Context context, boolean isAirplaneNewlyOn) {
if (isAirplaneNewlyOn) {
// If we are trying to turn off the radio, make sure there are no active
// emergency calls. If there are, switch airplane mode back to off.
TelecomManager tm = (TelecomManager) context.getSystemService(TELECOM_SERVICE);
if (tm != null && tm.isInEmergencyCall()) {
// Switch airplane mode back to off.
ConnectivityManager.from(this).setAirplaneMode(false);
Toast.makeText(this, R.string.radio_off_during_emergency_call, Toast.LENGTH_LONG)
.show();
Log.i(LOG_TAG, "Ignoring airplane mode: emergency call. Turning airplane off");
} else if (isCellOffInAirplaneMode(context)) {
setRadioPowerOff(context);
} else {
Log.i(LOG_TAG, "Ignoring airplane mode: settings prevent cell radio power off");
}
}
}
private void maybeTurnCellOn(Context context, boolean isAirplaneNewlyOn) {
if (!isAirplaneNewlyOn) {
setRadioPowerOn(context);
}
}
/**
* Receiver for misc intent broadcasts the Phone app cares about.
*/
private class PhoneAppBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
int airplaneMode = Settings.Global.getInt(getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, AIRPLANE_OFF);
// Treat any non-OFF values as ON.
if (airplaneMode != AIRPLANE_OFF) {
airplaneMode = AIRPLANE_ON;
}
handleAirplaneModeChange(context, airplaneMode);
} //省略其他代码
}
}
//省略其他代码
}

PhoneUtils.setRadioPower 会继续调用 GsmCdmaPhone#setRadioPower,调用 mSST.setRadioPower,最终由 mSST 对象向 RIL 对象发起关闭或开启 Radio 无线通信模块的请求,这里就不细看了,有兴趣可以自己继续跟下去。到这里我们就对“Android 当点击飞行模式都发生了什么?”流程有了大致了解,就酱紫,Over。



联系作者

我的微信公众号:吴小龙同学,欢迎关注交流~