Android安全模式

注意
本文最后更新于 2024-04-28,文中内容可能已过时。

原文链接:https://ovea-y.cn/android_safemode/

![[cover-11.png]]

引言

每个Android用户都可能遇到设备行为异常的时刻——应用冻结,系统崩溃,或是更加神秘的软件故障。在这海量复杂的智能手机世界中,有一个隐藏的救世主等待着被召唤:Android的安全模式。就像为您的设备提供一剂急救药一样,安全模式是解决许多常见问题的关键。

在我们的生活中,智能手机早已成为必不可少的伴侣,我们依赖它们来进行通讯、娱乐、工作和探索数字化世界。不过,随着应用数量的增加和设备功能的扩展,系统可能会变得不稳定。这就是安全模式进入游戏的时刻。启用安全模式意味着您的手机将在一个最小化的环境中运行,禁用所有第三方应用,并允许您在不受潜在有害应用或功能影响的情况下,对设备进行故障排除。

在本篇博客中,我们将深入探索Android安全模式的力量和用途。我会指导您如何轻松地进入安全模式,分析安全模式的工作原理,并如何利用它来恢复设备正常运行。无论您是Android初学者还是资深用户,理解如何使用安全模式都是一个宝贵的技能。所以,让我们开始这一旅程,揭开Android安全模式如何成为您紧急救援工具的秘密。

功能入口

安全模式的“入口”,位于SystemServer中,在startOtherServices阶段(前面还有两个大阶段,用于启动核心的Service)进行检测。

java

final boolean safeMode = wm.detectSafeMode();

检测方法位于WindowManagerService中的detectSafeMode方法,在满足以下任意条件后即可进入Android的安全模式

  • 检测"MENU"按键是否按下
  • 检测"S"按键是否被按下
  • 检测"DPAD"按键是否被按下
  • 检测鼠标按键是否被按下
  • 检测“音量下键”是否被按下(常用)
  • 设置了“persist.sys.safemode”属性(持久化属性,进入安全模式后会被自动清除)
    • WMS可以以安全模式重启 —— rebootSafeMode
  • 设置了“ro.sys.safemode”属性(内存属性,重启后丢失)

⚠️ 除了音量下键,其他的Input事件在手机、平板是不常见的,如果忽视了对Input设备的键码映射,复用了“不存在的设备”,这里可能出现Input异常导致的非正常进入安全模式问题,具体内容我们之后再做分析。

💡通过属性触发,可以帮助厂商更好的定制自己的安全模式。具体内容本篇文章不细谈,未来将单独进行介绍。

DPAD是什么?见下图 ![[Screenshot 2024-03-18 at 13.02.41.png]]

  • 设置Settings.Global.SAFE_BOOT_DISALLOWED(safe_boot_disallowed)为非0值。

以下是参考代码:

system app或system自身做法:

java

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
   ContentResolver resolver = context.getContentResolver();
   Settings.Global.putInt(resolver, "safe_boot_disallowed", 1);
}

DeviceOwner应用的做法:

DevicePolicy添加了DISALLOW_SAFE_BOOT策略后,会自动设置SAFE_BOOT_DISALLOWED属性

java

DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminName = new ComponentName(context, AdminReceiver.class);

if (dpm.isDeviceOwnerApp(context.getPackageName())) {
    dpm.addUserRestriction(adminName, UserManager.DISALLOW_SAFE_BOOT);
}

java

    public boolean detectSafeMode() {
	    // 等待Input设备就绪(超时时间是1000ms)
        if (!mInputManagerCallback.waitForInputDevicesReady(
                INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) {
            ...
        }

		// 判断安全模式是否禁用
        if (Settings.Global.getInt(
                mContext.getContentResolver(), Settings.Global.SAFE_BOOT_DISALLOWED, 0) != 0) {
            return false;
        }

		// 检测MENU按键是否按下(目前大部分机器都没有)
        int menuState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY,
                KeyEvent.KEYCODE_MENU);
        // 检测"S"按键是否被按下
        int sState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, KeyEvent.KEYCODE_S);
        // 检测"DPAD"按键是否被按下
        int dpadState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD,
                KeyEvent.KEYCODE_DPAD_CENTER);
        // 检测鼠标按键是否被按下
        int trackballState = mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL,
                InputManagerService.BTN_MOUSE);
        // 检测“音量下键”是否被按下(常用)
        int volumeDownState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY,
                KeyEvent.KEYCODE_VOLUME_DOWN);
        mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0
                || volumeDownState > 0;
	    // 设置了“persist.sys.safemode”或“ro.sys.safemode”属性
        try {
            if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0
                    || SystemProperties.getInt(ShutdownThread.RO_SAFEMODE_PROPERTY, 0) != 0) {
                mSafeMode = true;
                // 清楚“persist.sys.safemode”属性
                SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
            }
        } catch (IllegalArgumentException e) {
        }
        // 一旦进入安全模式,就会有“ro.sys.safemode”属性标识
        if (mSafeMode) {
            ProtoLog.i(WM_ERROR, "SAFE MODE ENABLED (menu=%d s=%d dpad=%d"
                    + " trackball=%d)", menuState, sState, dpadState, trackballState);
            // May already be set if (for instance) this process has crashed
            if (SystemProperties.getInt(ShutdownThread.RO_SAFEMODE_PROPERTY, 0) == 0) {
                SystemProperties.set(ShutdownThread.RO_SAFEMODE_PROPERTY, "1");
            }
        } else {
            ProtoLog.i(WM_ERROR, "SAFE MODE not enabled");
        }
        // PhoneWindowManager的setSafeMode,为了通过振动来给用户反馈。
        mPolicy.setSafeMode(mSafeMode);
        return mSafeMode;
    }



	// ##### PhoneWindowManager.java
    @Override
    public void setSafeMode(boolean safeMode) {
        mSafeMode = safeMode;
        if (safeMode) {
            performHapticFeedback(HapticFeedbackConstants.SAFE_MODE_ENABLED, true,
                    "Safe Mode Enabled");
        }
    }

安全模式作用

  1. 自动打开系统的飞行模式

java

        // Before things start rolling, be sure we have decided whether
        // we are in safe mode.
        final boolean safeMode = wm.detectSafeMode();
        if (safeMode) {
            // If yes, immediately turn on the global setting for airplane mode.
            // Note that this does not send broadcasts at this stage because
            // subsystems are not yet up. We will send broadcasts later to ensure
            // all listeners have the chance to react with special handling.
            Settings.Global.putInt(context.getContentResolver(),
                    Settings.Global.AIRPLANE_MODE_ON, 1);
        } else if (context.getResources().getBoolean(R.bool.config_autoResetAirplaneMode)) {
            Settings.Global.putInt(context.getContentResolver(),
                    Settings.Global.AIRPLANE_MODE_ON, 0);
        }


            // Enable airplane mode in safe mode. setAirplaneMode() cannot be called
            // earlier as it sends broadcasts to other services.
            // TODO: This may actually be too late if radio firmware already started leaking
            // RF before the respective services start. However, fixing this requires changes
            // to radio firmware and interfaces.
            if (safeMode) {
                t.traceBegin("EnableAirplaneModeInSafeMode");
                try {
                    connectivityF.setAirplaneMode(true);
                } catch (Throwable e) {
                    reportWtf("enabling Airplane Mode during Safe Mode bootup", e);
                }
                t.traceEnd();
            }

java

            t.traceBegin("StartFontManagerService");
            mSystemServiceManager.startService(new FontManagerService.Lifecycle(context, safeMode));
            t.traceEnd();

禁用字体更新数据

java

    @Nullable
    private UpdatableFontDir createUpdatableFontDir() {
        // Never read updatable font files in safe mode.
        if (mIsSafeMode) return null;
        ...
    }

通知AMS进入安全模式,并现实安全模式水印

java

        if (safeMode) {
	        // 此处会通知PMS进入安全模式 
            mActivityManagerService.enterSafeMode();
        }

		...

        if (safeMode) {
            mActivityManagerService.showSafeModeOverlay();
        }

java

    public final void enterSafeMode() {
        synchronized(this) {
            // It only makes sense to do this before the system is ready
            // and started launching other packages.
            if (!mSystemReady) {
                try {
                    AppGlobals.getPackageManager().enterSafeMode();
                } catch (RemoteException e) {
                }
            }

            mSafeMode = true;
        }
    }

checkPackageStartable方法中,会判断某个包是否可以启动,在进入安全模式的情况下,只有系统App可以正常启动,而非系统App都无法启动。

java

    @PackageManagerService.PackageStartability
    @Override
    public int getPackageStartability(boolean safeMode, @NonNull String packageName, int callingUid,
            @UserIdInt int userId) {
        final boolean ceStorageUnlocked = StorageManager.isCeStorageUnlocked(userId);
        final PackageStateInternal ps = getPackageStateInternal(packageName);
        if (ps == null || shouldFilterApplication(ps, callingUid, userId)
                || !ps.getUserStateOrDefault(userId).isInstalled()) {
            return PackageManagerService.PACKAGE_STARTABILITY_NOT_FOUND;
        }

        if (safeMode && !ps.isSystem()) { // 安全模式下,非System App都不允许启动
            return PackageManagerService.PACKAGE_STARTABILITY_NOT_SYSTEM;
        }

        if (mFrozenPackages.containsKey(packageName)) {
            return PackageManagerService.PACKAGE_STARTABILITY_FROZEN;
        }

        if (!ceStorageUnlocked && !AndroidPackageUtils.isEncryptionAware(ps.getPkg())) {
            return PackageManagerService.PACKAGE_STARTABILITY_DIRECT_BOOT_UNSUPPORTED;
        }
        return PackageManagerService.PACKAGE_STARTABILITY_OK;
    }
  • ApplicationPackageManager
  • PackageManagerHelper (Launcher3 loadWorkspaceImpl)
  • BluetoothManagerService
  • ShadowPackageManager

java

        try {
            // TODO: use boot phase and communicate this flag some other way
            mDisplayManagerService.systemReady(safeMode);
        } catch (Throwable e) {
            reportWtf("making Display Manager Service ready", e);
        }

禁用非必要的显示适配器 shouldRegisterNonEssentialDisplayAdaptersLocked。 进入安全模式后,下面两个方法将不会执行。也就是下面两种显示将会被禁用:

  1. WiFi Display Adapter(无线网络显示。可以投射屏幕到智能电视、平板等其他屏幕上)
  2. Overlay Display Adapter(虚拟屏幕。通常用于测试,可以在不使用物理显示设备时模拟屏幕显示。可以用于屏幕录制等)

java

	registerOverlayDisplayAdapterLocked();
	registerWifiDisplayAdapterLocked();

java

 mSystemServiceManager.setSafeMode(safeMode);

所有代理将不被信任

禁止第三方语音助理相关功能

禁止检查Package变化

  • AppWidgetService

判断进入安全模式的方法

java

public final boolean isSafeMode() {
	return getManager().isSafeMode();
}

附录

Zygote运行在安全模式 EnableDebugFeatures

  • 禁用JIT

原文链接:https://ovea-y.cn/android_safemode/

相关内容