专栏首页吴小龙同學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 条评论
登录 后参与评论

相关文章

  • 找寻gvcf失败的原因(java啊java,配置啊配置)

    因为我只想保留recal后的bam和,call出来的gvcf文件,但是发现有些样本根本就走不通这个流程,就需要debug了。

    生信技能树
  • 深入理解JVM虚拟机5:虚拟机字节码执行引擎

    本文转自:https://www.cnblogs.com/snailclimb/p/9086337.html

    Java技术江湖
  • Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理

    本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看

    Java技术江湖
  • 用过XA分布式事务吗?

    下图说明了一个DTP系统的本地实例,其中AP调用TM来构造事务。这些框表示X/Open DTP模型中的软件组件。箭头指示控制流的方向。

    Criss@陈磊
  • 面试官:解释一下Java字节码文件中的JVM指令

    Java 之所以流行,一个很重要的原因就是它的跨平台特性,Compile Once, Run Anywhere,编译一次,到处运行。即 Java 源码只需要编译...

    南风
  • 前后端分离,我怎么就选择了 Spring Boot + Vue 技术栈?

    前两天又有小伙伴私信松哥,问题还是职业规划,Java 技术栈路线这种,实际上对于这一类问题我经常不太敢回答,每个人的情况都不太一样,而小伙伴也很少详细介绍自己的...

    Java技术江湖
  • 新手也能看懂的线程池学习总结

    线程池提供了一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成任务的数量。

    范蠡
  • Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO

    本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看

    Java技术江湖
  • TagLayout自定义流式布局

    这是一个继承ViewGourp来实现的自定义布局。他的核心只有一个,即当子View的宽度超出自身最大宽度时,自动换行。那么,我们先来看核心代码:

    饮水思源为名
  • Java 8,Jenkins,Jacoco和Sonar进行持续集成

    本文的范围是解释安装和设置必要工具的所有步骤,以使Java 8的CI服务器完全正常运行。请注意,该证明已在Windows 7的开发人员机器上完成,但很容易做到。...

    八音弦

扫码关注云+社区

领取腾讯云代金券