专栏首页吴小龙同學Android 当点击飞行模式都发生了什么?

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

今天中午午休时,我把手机开飞行模式了,能看到 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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Flutter 初尝:从 Java 无缝过渡

    准备阶段 下载 Flutter SDK 如果不能科学上网,可以声明 PUB_HOSTED_URL 和 FLUTTER_STORAGE_BASE_URL 两个环境...

    吴小龙同學
  • Android 9.0 SystemUI 主要视图 SystemBars

    SystemBars 是 SystemUI 主要视图,接下来看看它是如何呈现?基于 AOSP 9.0 分析。

    吴小龙同學
  • Android 9.0 SystemUI NavigationBar

    导航栏有返回(back),桌面(home),最近任务(recent),本篇主要学习这三个是如何加载的,点击事件在哪里写的?基于 AOSP 9.0 分析。

    吴小龙同學
  • Flutter中如何使用WillPopScope的示例代码

    WillPopScope用于处理是否离开当前页面,在Flutter中有多种方式可以离开当前页面,比如AppBar、CupertinoNavigationBar上...

    砸漏
  • 分布式不确定数据库中概率Top-k控制查询(CS)

    在许多真实世界的应用程序中,如业务规划和传感器数据监控,一个重要但具有挑战性的任务是对对象进行排序。(产品、文档或空间对象),并有效地返回得分最高的对象。在实践...

    用户8440711
  • How is Aggregation designed

    这里的bindItem实际上不是一个function,而是Javascript对象的一个attribute,该attribute指向了一个匿名函数,其实现是 t...

    Jerry Wang
  • go modules:使用 mod 管理项目依赖包,通过vendor实现一键分发编译包

    在go语言1.11版本之前,没有modules机制,所有软件包都在安装在$GOPATH/src目录下。不同项目如果引用了同一个软件包的不同版本,就会造成编译麻烦...

    程序员LIYI
  • plotly-express-7-Dash利用滑动条实现数据选择

    皮大大
  • 详解串行通信协议及其FPGA实现(一)

    好久没更新文章了,这篇文章写写停停,用了近一周的时间,终于写完了,谢谢大家的关注。本篇文章介绍,串口协议数据帧格式、串行通信的工作方式、电平标准、编码方式及Ve...

    单片机点灯小能手
  • Dji TT扩展件与TT使用USB端串口通讯.上

    HardwareSerial类在声明对象时接收一个输入参数(0、1、2),分别代表

    云深无际

扫码关注云+社区

领取腾讯云代金券