微信号:gh_f86c241826a6

介绍:不止于技术分享,不取悦他人,写给懂的你看~

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

2019-06-14 07:44 吴小龙同學

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

基于 Android 9.0 源码分析。

AirplaneModeTile#handleClick

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

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

@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

@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

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。

推荐阅读

为什么你学不好 Android,因为不成体系

从“Hello World”开始 Android

Android 亮度自动调节是如何实现的?



 
吴小龙同学 更多文章 如果你突然被裁员了,你的 Plan B 是什么? Android转型人工智能、大数据免费全套教程,都在这儿! 从“Hello World”开始 Android 为什么你学不好 Android,因为不成体系 Android persistent,打赌你一定没有用过
猜您喜欢 小按钮,大学问 【ReactNative For Android】框架启动核心路径剖析 Azure云中配置R Server 聊聊代码规范 Dagger 实现细节及 Android 公众号推荐