Android 10关机界面定制
  R3jekIqJDwNw 2023年11月02日 28 0

1、背景说明

在项目开发过程中,需要对开机界面进行定制,使得产品界面风格统一。

  • 软件版本:Android 10
  • 方案供应商:高通
  • 目的:定制关机UI 系统原始的关机UI:

Android 10关机界面定制_java

定制后的关机UI:

Android 10关机界面定制_android_02

2、关机流程

本文定制的关机界面为长按power键触发的关机界面,首先我们先了解Android 10整理的关机流程,熟悉整理流程后再进行定制开发。 关机流程涉及的代码路径如下

frameworks\base\core\res\res\values\config.xml
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java
frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java
frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java
frameworks\base\services\core\java\com\android\server\policy\PowerAction.java
frameworks\base\services\core\java\com\android\server\policy\WindowManagerPolicy.java
frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java
system\core\init\property_service.cpp
system\core\init\init.cpp
system\core\init\reboot.cpp
system\core\init\reboot_utils.cpp

长按power键关机流程时序图如下:

Android 10关机界面定制_java_03

2.1、Power键响应

这里我们不关注驱动及设备层如何识别及上报物理power事件,重点介绍android层如何拦截及响应power key event。 frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

//分发未经处理的key,由InputManagerService调用
    @Override
    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
        // Note: This method is only called if the initial down was unhandled.
        KeyEvent fallbackEvent = null;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            final KeyCharacterMap kcm = event.getKeyCharacterMap();
            final int keyCode = event.getKeyCode();
            final int metaState = event.getMetaState();
            final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
                    && event.getRepeatCount() == 0;

            // Check for fallback actions specified by the key character map.
            final FallbackAction fallbackAction;
            if (initialDown) {
   				if (!interceptFallback(win, fallbackEvent, policyFlags)) {//对特殊按键(power、vol、home、mute等)进行拦截处理,不分发至app
                    fallbackEvent.recycle();
                    fallbackEvent = null;
                }
            }
            ...
 	}
 ...
    private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
        int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);//key加入队列之前进行拦截
        if ((actions & ACTION_PASS_TO_USER) != 0) {
            long delayMillis = interceptKeyBeforeDispatching(//分发key之前进行拦截
                    win, fallbackEvent, policyFlags);
            if (delayMillis == 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    // Handle special keys.
    ...
        switch (keyCode) {
        ...
        	case KeyEvent.KEYCODE_POWER: {
                EventLogTags.writeInterceptPower(
                        KeyEvent.actionToString(event.getAction()),
                        mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter);
                // Any activity on the power button stops the accessibility shortcut
                cancelPendingAccessibilityShortcutAction();
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactive);//拦截power键down 事件
                } else {
                    interceptPowerKeyUp(event, interactive, canceled);//拦截power键up 事件
                }
                break;
            }
        }
        ...
    }
    ...
     private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
     	...
     	// If the power key has still not yet been handled, then detect short
        // press, long press, or multi press and decide what to do.
        mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
                || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;
     	if (!mPowerKeyHandled) {
            if (interactive) {
                // When interactive, we're already awake.
                // Wait for a long press or for the button to be released to decide what to do.
                if (hasLongPressOnPowerBehavior()) {//长按power键行为
                    if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                        powerLongPress();//处理power键长按
                    } else {
                        ...
                }
            }
    	}
    	...
     }

private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();//从配置文件中获取power键长按行为设置mLongPressOnPowerBehavior
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();
                break;
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Shut Off");
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
           ...
    }

    private int getResolvedLongPressOnPowerBehavior() {
        if (FactoryTest.isLongPressOnPowerOffEnabled()) {
            return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
        }
        return mLongPressOnPowerBehavior;
    }

mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_longPressOnPowerBehavior);

frameworks\base\core\res\res\values\config.xml

<!-- Control the behavior when the user long presses the power button.
            0 - Nothing    											//不处理power键,即什么也不做
            1 - Global actions menu							//关机显示全局行为菜单
            2 - Power off (with confirmation)				//关机前弹出对话框再次确认
            3 - Power off (without confirmation)		//关机前不弹出对话框,直接关机
            4 - Go to voice assist								//转到语言助手
            5 - Go to assistant (Settings.Secure.ASSISTANT)	转到设置助手
    -->
    <integer name="config_longPressOnPowerBehavior">1</integer>//默认为系统全局菜单

接着上面powerLongPress()往下走 frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

private void powerLongPress() {
	    final int behavior = getResolvedLongPressOnPowerBehavior();
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();//config_longPressOnPowerBehavior为1则调用mGlobalActions.showDialog();
                break;
            //config_longPressOnPowerBehavior为2则调用mWindowManagerFuncs.shutdown();
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Shut Off");
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
           ...
    }

    void showGlobalActionsInternal() {
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
        }
        final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
        // since it took two seconds of long press to bring this up,
        // poke the wake lock so they have some time to see the dialog.
        mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
    }

2.2、显示关机确认框界面

这里先按源码流程case 1分析,case 2的后续定制关机界面中会介绍。 frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java

public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
        if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
        if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) {
            return;
        }
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = deviceProvisioned;
        mShowing = true;
        if (mGlobalActionsAvailable) {//全局行为可使用
            mHandler.postDelayed(mShowTimeout, 5000);
            mGlobalActionsProvider.showGlobalActions();
        } else {
            // SysUI isn't alive, show legacy menu.SysUI 不可用则显示传统关机菜单
            ensureLegacyCreated();
            mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
        }
    }

沿着传统关机流程继续分析 frameworks\base\services\core\java\com\android\server\policy\LegacyGlobalActions.java

public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        if (mDialog != null) {//关机对话框已存在
            mDialog.dismiss();
            mDialog = null;
            // Show delayed, so that the dismiss of the previous dialog completes
            mHandler.sendEmptyMessage(MESSAGE_SHOW);
        } else {
            handleShow();
        }
    }

    private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();//创建新的对话框,加载关机选项,设置点击选项
        prepareDialog();//更新静音、飞行等各种模式

        ...
            if (mDialog != null) {
                WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
                attrs.setTitle("LegacyGlobalActions");
                mDialog.getWindow().setAttributes(attrs);
                mDialog.show();
                mDialog.getWindow().getDecorView().setSystemUiVisibility(
                        View.STATUS_BAR_DISABLE_EXPAND);
            }
        }
    }

private ActionsDialog createDialog() {

        mItems = new ArrayList<Action>();
        String[] defaultActions = mContext.getResources().getStringArray(
                com.android.internal.R.array.config_globalActionsList);//设置默认的全局行为选项

        ArraySet<String> addedKeys = new ArraySet<String>();
        for (int i = 0; i < defaultActions.length; i++) {
            String actionKey = defaultActions[i];
            if (addedKeys.contains(actionKey)) {
                // If we already have added this, don't add it again.
                continue;
            }
            if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {//关机模式
                mItems.add(new PowerAction(mContext, mWindowManagerFuncs));
            } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {//飞行模式
                mItems.add(mAirplaneModeOn);
            }
            ...
            addedKeys.add(actionKey);//将默认关机选项加入global action menu
        }
		...
        ActionsDialog dialog = new ActionsDialog(mContext, params);
        dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
        dialog.getListView().setItemsCanFocus(true);
        dialog.getListView().setLongClickable(true);
        dialog.getListView().setOnItemLongClickListener(
                new AdapterView.OnItemLongClickListener() {
                    @Override
                    public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
                            long id) {
                        final Action action = mAdapter.getItem(position);
                        if (action instanceof LongPressAction) {
                            return ((LongPressAction) action).onLongPress();//处理选项点击事件
                        }
                        return false;
                    }
        });
       ...
        return dialog;
    }

全局行为模式定义如下: frameworks\base\core\res\res\values\config.xml

<!-- Defines the default set of global actions. Actions may still be disabled or hidden based
         on the current state of the device.
         Each item must be one of the following strings:
         "power" = Power off
         "settings" = An action to launch settings
         "airplane" = Airplane mode toggle
         "bugreport" = Take bug report, if available
         "silent" = silent mode
         "users" = list of users
         "restart" = restart device
         "emergency" = Launch emergency dialer
         "lockdown" = Lock down device until the user authenticates
         "logout" =  Logout the current user
         -->
    <string-array translatable="false" name="config_globalActionsList">
        <item>power</item> 			//关机
        <item>restart</item>		//重启
        <item>lockdown</item>		//锁屏
        <item>logout</item>			//注销账户
        <item>bugreport</item>		//上报错误
        <item>screenshot</item>		//截屏
        <item>emergency</item>		//紧急
    </string-array>

接着PowerAction.java onLongPress() frameworks\base\services\core\java\com\android\server\policy\PowerAction.java

@Override
    public boolean onLongPress() {
        UserManager um = mContext.getSystemService(UserManager.class);
        if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
            mWindowManagerFuncs.rebootSafeMode(true);
            return true;
        }
        return false;
    }

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

@Override
    public void rebootSafeMode(boolean confirm) {
        // Pass in the UI context, since ShutdownThread requires it (to show UI).
        ShutdownThread.rebootSafeMode(ActivityThread.currentActivityThread().getSystemUiContext(),
                confirm);
    }

2.3、显示关机进度框

进入关机线程ShutdownThread frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

public static void rebootSafeMode(final Context context, boolean confirm) {
        ...
        shutdownInner(context, confirm);
    }

private static void shutdownInner(final Context context, boolean confirm) {
		...
        if (confirm) {
            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
            if (sConfirmDialog != null) {
                sConfirmDialog.dismiss();
            }
            sConfirmDialog = new AlertDialog.Builder(context)//创建关机确认对话框
                    .setTitle(mRebootSafeMode
                            ? com.android.internal.R.string.reboot_safemode_title
                            : com.android.internal.R.string.power_off)
                    .setMessage(resourceId)
                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            beginShutdownSequence(context);//选择确认关机,开始执行关机流程
                        }
                    })
                    .setNegativeButton(com.android.internal.R.string.no, null)
                    .create();
            closer.dialog = sConfirmDialog;
            sConfirmDialog.setOnDismissListener(closer);
            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
            sConfirmDialog.show();
        } else {
            beginShutdownSequence(context);
        }
    }

    private static void beginShutdownSequence(Context context) {
		...
        /* If shutdown animation enabled, notify bootanimation module to play
           shutdown animation by set prop */
        final boolean shutdownAnimationEnabled = context.getResources()
                .getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);
        if (shutdownAnimationEnabled) {
            SystemProperties.set("sys.powerctl", "shutdownanim");
            SystemProperties.set("service.bootanim.exit", "0");
            SystemProperties.set("ctl.start", "bootanim");
        }
        sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框
       	...
        if (SecurityLog.isLoggingEnabled()) {
            SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
        }
        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

 private static ProgressDialog showShutdownDialog(Context context) {
        // Throw up a system dialog to indicate the device is rebooting / shutting down.
        ProgressDialog pd = new ProgressDialog(context);

        // Path 1: Reboot to recovery for update
        //   Condition: mReason startswith REBOOT_RECOVERY_UPDATE
        //
        //  Path 1a: uncrypt needed
        //   Condition: if /cache/recovery/uncrypt_file exists but
        //              /cache/recovery/block.map doesn't.
        //   UI: determinate progress bar (mRebootHasProgressBar == True)
        //
        // * Path 1a is expected to be removed once the GmsCore shipped on
        //   device always calls uncrypt prior to reboot.
        //
        //  Path 1b: uncrypt already done
        //   UI: spinning circle only (no progress bar)
        //
        // Path 2: Reboot to recovery for factory reset
        //   Condition: mReason == REBOOT_RECOVERY
        //   UI: spinning circle only (no progress bar)
        //
        // Path 3: Regular reboot / shutdown
        //   Condition: Otherwise
        //   UI: spinning circle only (no progress bar)

        // mReason could be "recovery-update" or "recovery-update,quiescent".
        if (mReason != null && mReason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
            // We need the progress bar if uncrypt will be invoked during the
            // reboot, which might be time-consuming.
            mRebootHasProgressBar = RecoverySystem.UNCRYPT_PACKAGE_FILE.exists()
                    && !(RecoverySystem.BLOCK_MAP_FILE.exists());
            pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
            if (mRebootHasProgressBar) {
                pd.setMax(100);
                pd.setProgress(0);
                pd.setIndeterminate(false);
                pd.setProgressNumberFormat(null);
                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_prepare));
            } else {
                if (showSysuiReboot()) {
                    return null;
                }
                pd.setIndeterminate(true);
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_update_reboot));
            }
        } else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
            if (RescueParty.isAttemptingFactoryReset()) {
                // We're not actually doing a factory reset yet; we're rebooting
                // to ask the user if they'd like to reset, so give them a less
                // scary dialog message.
                pd.setTitle(context.getText(com.android.internal.R.string.power_off));
                pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
                pd.setIndeterminate(true);
            } else {
                // Factory reset path. Set the dialog message accordingly.
                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
                pd.setMessage(context.getText(
                            com.android.internal.R.string.reboot_to_reset_message));
                pd.setIndeterminate(true);
            }
        } else {
            if (showSysuiReboot()) {
                return null;
            }
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
        }
        pd.setCancelable(false);
        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
		pd.show();
        return pd;
    }

pd.show()将各自模式的关机进度对话框显示,直至系统关机。至此,定制化关机界面所需处理的流程到这里就可以结束了,但为了进一步了解关机流程我们继续深入follow。 在beginShutdownSequence()方法最后开启了一个线程,执行了run() frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

/**
     * Makes sure we handle the shutdown gracefully.
     * Shuts off power regardless of radio state if the allotted time has passed.
     */
    public void run() {
        ...
        final IActivityManager am =
                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));
        if (am != null) {
            try {
                am.shutdown(MAX_BROADCAST_TIME);//关机前关闭AMS
            } catch (RemoteException e) {
            }
        }
        if (mRebootHasProgressBar) {
            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
        }
        ...
        final PackageManagerService pm = (PackageManagerService)
            ServiceManager.getService("package");
        if (pm != null) {
            pm.shutdown();//关机前关闭PMS
        }
        // Shutdown radios.
        shutdownRadios(MAX_RADIO_WAIT_TIME);//关机前关闭radios
        if (mRebootHasProgressBar) {
            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);
        }
		...
        // Remaining work will be done by init, including vold shutdown
        rebootOrShutdown(mContext, mReboot, mReason);//进入重启或关机
    }

public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
        ...           
        final boolean shutdownAnimEnabled = context.getResources().getBoolean(
                com.android.internal.R.bool.config_shutdownAnimationEnabled);//是否启用关机动画

        if (shutdownAnimEnabled) {
            final int shutdownAnimDuration = context.getResources().getInteger(
                    com.android.internal.R.integer.config_shutdownAnimationDurationMs);
            int sleepDuration = reboot ? shutdownAnimDuration
                    : shutdownAnimDuration - SHUTDOWN_VIBRATE_MS;
            try {
                if (sleepDuration > 0) {
                    Thread.sleep(sleepDuration);
                }
            } catch (InterruptedException unused) {
            }
        }

        if (reboot) {
            Log.i(TAG, "Rebooting, reason: " + reason);
            PowerManagerService.lowLevelReboot(reason);//reboot重启流程
            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
            reason = null;
        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
            // vibrate before shutting down
            Vibrator vibrator = new SystemVibrator(context);
            try {
                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
            } catch (Exception e) {
                // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
                Log.w(TAG, "Failed to vibrate during shutdown.", e);
            }

        // Shutdown power
        Log.i(TAG, "Performing low-level shutdown...");
        PowerManagerService.lowLevelShutdown(reason);
    }

frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java

public static void lowLevelShutdown(String reason) {
        if (reason == null) {
            reason = "";
        }
        SystemProperties.set("sys.powerctl", "shutdown," + reason);//设置系统控制属性sys.powerctl=shutdown
    }

在设置系统属性流程中对于特殊属性值的改变需进行监听,做特殊处理. 具体属性设置流程可以参考我的另一篇文档:Android 系统属性(SystemProperties)介绍 system\core\init\property_service.cpp

static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) {
    ...
    property_changed(name, value);
    return PROP_SUCCESS;
}

property_changed()中获取sys.powerctl值传给shutdown_command,并设置关机标志位do_shutdown为true system\core\init\init.cpp

void property_changed(const std::string& name, const std::string& value) {
    // If the property is sys.powerctl, we bypass the event queue and immediately handle it.
    // This is to ensure that init will always and immediately shutdown/reboot, regardless of
    // if there are other pending events to process or if init is waiting on an exec service or
    // waiting on a property.
    // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
    // commands to be executed.
    if (name == "sys.powerctl") {
        // Despite the above comment, we can't call HandlePowerctlMessage() in this function,
        // because it modifies the contents of the action queue, which can cause the action queue
        // to get into a bad state if this function is called from a command being executed by the
        // action queue.  Instead we set this flag and ensure that shutdown happens before the next
        // command is run in the main init loop.
        // TODO: once property service is removed from init, this will never happen from a builtin,
        // but rather from a callback from the property service socket, in which case this hack can
        // go away.
        shutdown_command = value;
        do_shutdown = true;//设置do shutdown关机标志
    }
	...
}

int SecondStageMain(int argc, char** argv) {
	...
	//监听do_shutdown值变化,为true时调用HandlePowerctlMessage()
	while (true) {
        // By default, sleep until something happens.
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {//发送关机msg
                shutting_down = true;
            }
        }
        ...
    }
    ...
}

system\core\init\reboot.cpp

bool HandlePowerctlMessage(const std::string& command) {
    ...
    if (cmd_params.size() > 3) {
        command_invalid = true;
    } else if (cmd_params[0] == "shutdown") {//关机
        cmd = ANDROID_RB_POWEROFF;
        if (cmd_params.size() == 2) {
            if (cmd_params[1] == "userrequested") {
                // The shutdown reason is PowerManager.SHUTDOWN_USER_REQUESTED.
                // Run fsck once the file system is remounted in read-only mode.
                run_fsck = true;
            } else if (cmd_params[1] == "thermal") {
                // Turn off sources of heat immediately.
                TurnOffBacklight();//息屏,关背光
                // run_fsck is false to avoid delay
                cmd = ANDROID_RB_THERMOFF;
            }
        }
    } else if (cmd_params[0] == "reboot") {//重启
        cmd = ANDROID_RB_RESTART2;
        if (cmd_params.size() >= 2) {
            reboot_target = cmd_params[1];
            // adb reboot fastboot should boot into bootloader for devices not
            // supporting logical partitions.
            if (reboot_target == "fastboot" &&
                !android::base::GetBoolProperty("ro.boot.dynamic_partitions", false)) {
                reboot_target = "bootloader";
            }
            // When rebooting to the bootloader notify the bootloader writing
            // also the BCB.
            ...
    auto shutdown_handler = [cmd, command, reboot_target, run_fsck](const BuiltinArguments&) {
        DoReboot(cmd, command, reboot_target, run_fsck);
        return Success();
    };
    ...
    return true;
}

system\core\init\reboot_utils.cpp

void __attribute__((noreturn)) RebootSystem(unsigned int cmd, const std::string& rebootTarget) {
    LOG(INFO) << "Reboot ending, jumping to kernel";

    if (!IsRebootCapable()) {
        // On systems where init does not have the capability of rebooting the
        // device, just exit cleanly.
        exit(0);
    }
    switch (cmd) {
        case ANDROID_RB_POWEROFF:
            reboot(RB_POWER_OFF);//进行关机
            break;
			...
    }
    // In normal case, reboot should not return.
    PLOG(ERROR) << "reboot call returned";
    abort();
}

reboot后续调用kernel相关进行硬件层面的关机流程,到这里关机流程跑完了,在流程中也发现了息屏动作是如何设置的。

3、定制关机界面

从上述关机流程中不难看出有多种方式实现关机界面的定制化,这里给出两种方案。需要注意的是定制的关机界面实际上是两个界面:关机关机确认界面和关机进度界面,所以在定制时需要将两个界面都替换,并保持风格统一。

3.1、GobalActionsMenu关机界面定制:

frameworks\base\services\core\java\com\android\server\policy\GlobalActions.java

public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
        mContext = context;
        mHandler = new Handler();
        mWindowManagerFuncs = windowManagerFuncs;

        mGlobalActionsProvider = LocalServices.getService(GlobalActionsProvider.class);
        if (mGlobalActionsProvider != null) {
            mGlobalActionsProvider.setGlobalActionsListener(this);//注册监听
        } else {
            Slog.i(TAG, "No GlobalActionsProvider found, defaulting to LegacyGlobalActions");
        }
    }
	...
    public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
        ...
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = deviceProvisioned;
        mShowing = true;
        if (mGlobalActionsAvailable) {//全局行为可使用
            mHandler.postDelayed(mShowTimeout, 5000);
            mGlobalActionsProvider.showGlobalActions();//显示全局关机选项界面
        } else {
            // SysUI isn't alive, show legacy menu.SysUI 不可用则显示传统关机菜单
            ensureLegacyCreated();
            mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);
        }
    }

frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsImpl.java

@Override
    public void showGlobalActions(GlobalActionsManager manager) {
        if (mDisabled) return;
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActionsDialog(mContext, manager);
        }
        mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
                mDeviceProvisionedController.isDeviceProvisioned(),
                mPanelExtension.get());
        KeyguardUpdateMonitor.getInstance(mContext).requestFaceAuth();
    }

frameworks\base\packages\SystemUI\src\com\android\systemui\globalactions\GlobalActionsDialog.java

public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
            GlobalActionsPanelPlugin panelPlugin) {
        mKeyguardShowing = keyguardShowing;
        mDeviceProvisioned = isDeviceProvisioned;
        mPanelPlugin = panelPlugin;
        if (mDialog != null) {
            mDialog.dismiss();
            mDialog = null;
            // Show delayed, so that the dismiss of the previous dialog completes
            mHandler.sendEmptyMessage(MESSAGE_SHOW);
        } else {
            handleShow();
        }
    }
	...
	//在这里才真正实例化关机dialog并show出来,因此在这里进行定制化
    private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();
        //Mart!nHu Patch Start
        Dialog mNewDialog = ceateNewDialog();//创建定制关机选择框界面
        //Mart!nHu Patch End
        prepareDialog();

        // If we only have 1 item and it's a simple press action, just do this action.
        if (mAdapter.getCount() == 1
                && mAdapter.getItem(0) instanceof SinglePressAction
                && !(mAdapter.getItem(0) instanceof LongPressAction)) {
            ((SinglePressAction) mAdapter.getItem(0)).onPress();
        } else {
            WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
            attrs.setTitle("ActionsDialog");
            attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
            mDialog.getWindow().setAttributes(attrs);
            //Mart!nHu Patch Start
            //mDialog.show();//隐藏默认关机界面
            mNewDialog.show();//显示定制关机界面
            //Mart!nHu Patch End
            mWindowManagerFuncs.onGlobalActionsShown();
        }
    }
//Mart!nHu Patch Start
    private Dialog createShutDownConfirmDialog(){
        if(mShutDownConfirmDialog != null){
            return mShutDownConfirmDialog;
        }
        mShutDownConfirmDialog = new Dialog(mContext,com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
                    // Window initialization
        Window window = mShutDownConfirmDialog.getWindow();
        window.requestFeature(Window.FEATURE_NO_TITLE);
        // Inflate the decor view, so the attributes below are not overwritten by the theme.
        window.getDecorView();
        window.setGravity(Gravity.CENTER);
        window.setBackgroundDrawableResource(android.R.color.transparent);
        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.addFlags(
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
        mShutDownConfirmDialog.setContentView(com.android.systemui.R.layout.shutdown_confirm_dialog);//使用定制布局
        TextView tv_confirm = mShutDownConfirmDialog.findViewById(com.android.systemui.R.id.btn_confirm);
        TextView tv_cancel = mShutDownConfirmDialog.findViewById(com.android.systemui.R.id.btn_cancel);
        //设置点击事件
        tv_confirm.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mShutDownConfirmDialog.dismiss();
                mWindowManagerFuncs.shutdown();

            }
        });
        tv_cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mShutDownConfirmDialog.dismiss();
            }
        });
        return mShutDownConfirmDialog;
     }
//Mart!nHu Patch End

3.2、ShutdownThread关机界面定制

frameworks\base\core\res\res\values\config.xml

<!-- Control the behavior when the user long presses the power button.
            0 - Nothing    											//不处理power键,即什么也不做
            1 - Global actions menu							//关机显示全局行为菜单
            2 - Power off (with confirmation)				//关机前弹出对话框再次确认
            3 - Power off (without confirmation)		//关机前不弹出对话框,直接关机
            4 - Go to voice assist								//转到语言助手
            5 - Go to assistant (Settings.Secure.ASSISTANT)	转到设置助手
    -->
    //Mart!nHu Patch Start  
    <integer name="config_longPressOnPowerBehavior">2</integer>//选用带确认的关机界面
    //Mart!nHu Patch End

修改后系统关机界面如下:

Android 10关机界面定制_java_04

在powerLongPress()调用mWindowManagerFuncs.shutdown() frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java

private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();//从配置文件中获取power键长按行为设置mLongPressOnPowerBehavior
        switch (behavior) {
            case LONG_PRESS_POWER_NOTHING:
                break;
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
                        "Power - Long Press - Global Actions");
                showGlobalActionsInternal();
                break;
            case LONG_PRESS_POWER_SHUT_OFF:
            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                mPowerKeyHandled = true;
                mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
           ...
    }

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

@Override
    public void shutdown(boolean confirm) {
        // Pass in the UI context, since ShutdownThread requires it (to show UI).
        ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
                PowerManager.SHUTDOWN_USER_REQUESTED, confirm);
    }

frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java

//Mart!nHu Patch Start
private static Dialog sCustomizedConfirmDialog;
//Mart!nHu Patch End

public static void shutdown(final Context context, String reason, boolean confirm) {
        mReboot = false;
        mRebootSafeMode = false;
        mReason = reason;
        shutdownInner(context, confirm);
    }

    private static void shutdownInner(final Context context, boolean confirm) {
        ...

        final int longPressBehavior = context.getResources().getInteger(
                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
        final int resourceId = mRebootSafeMode
                ? com.android.internal.R.string.reboot_safemode_confirm
                : (longPressBehavior == 2
                        ? com.android.internal.R.string.shutdown_confirm_question
                        : com.android.internal.R.string.shutdown_confirm);

        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);

        if (confirm) {
            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
            if (sConfirmDialog != null) {
                sConfirmDialog.dismiss();
            }
            sConfirmDialog = new AlertDialog.Builder(context)
                    .setTitle(mRebootSafeMode
                            ? com.android.internal.R.string.reboot_safemode_title
                            : com.android.internal.R.string.power_off)
                    .setMessage(resourceId)
                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            beginShutdownSequence(context);
                        }
                    })
                    .setNegativeButton(com.android.internal.R.string.no, null)
                    .create();
            closer.dialog = sConfirmDialog;
            sConfirmDialog.setOnDismissListener(closer);
            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
            //Mart!nHu Patch Start
            //sConfirmDialog.show();//隐藏默认关机确认框
            if(sCustomizedConfirmDialog != null) {
                sCustomizedConfirmDialog.dismiss();
                sCustomizedConfirmDialog = null;
            }
            sCustomizedConfirmDialog = createCustomizedConfirmDialog(context);//创建定制关机确认框
            closer.dialog = sCustomizedConfirmDialog;
            sCustomizedConfirmDialog.setOnDismissListener(closer);
            sCustomizedConfirmDialog.show();//显示定制关机界面
        } else {
            beginShutdownSequence(context);
        }
    }
    
    private static Dialog CustomizedConfirmDialog(final Context context) {
        sCustomizedConfirmDialog = new Dialog(context);
        Window window = sCustomizedConfirmDialog.getWindow();
        setCustomizedShutdownWindow(window);//对定制dialog所在window进行配置
        sCustomizedConfirmDialog.setContentView(com.android.internal.R.layout.shutdown_confirm_dialog);
        sCustomizedConfirmDialog.setCancelable(false);//设置点击空白处不关闭dialog
        TextView shutdownConfirmMsg = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.tv_shutdown_confirm_msg);
        TextView tv_confirm = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.btn_confirm);
        TextView tv_cancel = sCustomizedConfirmDialog.findViewById(com.android.internal.R.id.btn_cancel);
        shutdownConfirmMsg.setText(com.android.internal.R.string.shutdown_confirm_question);
        tv_confirm.setText(com.android.internal.R.string.yes);
        tv_cancel.setText(com.android.internal.R.string.no);
        tv_confirm.setOnClickListener(new View.OnClickListener() { 
            @Override
            public void onClick(View view) {
                sCustomizedConfirmDialog.dismiss();
                beginShutdownSequence(context);//选择确认后执行后续关机流程
            }
        });
        tv_cancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sCustomizedConfirmDialog.dismiss();
            }
        });
        return sCustomizedConfirmDialog;
    }

    private static void setCustomizedShutdownWindow(Window window) {
        window.requestFeature(Window.FEATURE_NO_TITLE);//去掉window标题栏
        window.getDecorView();
        window.setGravity(Gravity.CENTER);//居中显示
        window.setBackgroundDrawableResource(android.R.color.transparent);//设置背景透明
        window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
        window.addFlags(
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
        window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
    }

    private static void beginShutdownSequence(Context context) {
		...
        /* If shutdown animation enabled, notify bootanimation module to play
           shutdown animation by set prop */
        final boolean shutdownAnimationEnabled = context.getResources()
                .getBoolean(com.android.internal.R.bool.config_shutdownAnimationEnabled);
        if (shutdownAnimationEnabled) {
            SystemProperties.set("sys.powerctl", "shutdownanim");
            SystemProperties.set("service.bootanim.exit", "0");
            SystemProperties.set("ctl.start", "bootanim");
        }
        sInstance.mProgressDialog = showShutdownDialog(context);//显示关机进度框,这里也需要进行定制
       	...
        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

private static ProgressDialog showShutdownDialog(Context context) {
        // Throw up a system dialog to indicate the device is rebooting / shutting down.
        ProgressDialog pd = new ProgressDialog(context);
		...
        } else {
            if (showSysuiReboot()) {
                return null;
            }
            //Mart!nHu Patch Start
            pd.setTitle(context.getText(com.android.internal.R.string.power_off));
            pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
            pd.setIndeterminate(true);
            sCustomizedShuttingDownDialog = createCustomizedShuttingDownDialog(context);//创建定制关机进度框
        }
        pd.setCancelable(false);
        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
        if(sCustomizedShuttingDownDialog != null) {
            sCustomizedShuttingDownDialog.show();
        } else {
            pd.show();
        }
        return pd;
    }

    private static Dialog createCustomizedShuttingDownDialog(final Context context) {
        if(sCustomizedShuttingDownDialog != null) {
            return sCustomizedShuttingDownDialog;
        }
        sCustomizedShuttingDownDialog = new Dialog(context);
        Window window = sCustomizedShuttingDownDialog.getWindow();
        setCustomizedShutdownWindow(window);
        sCustomizedShuttingDownDialog.setContentView(com.android.internal.R.layout.shuttingdown_dialog);
        sCustomizedShuttingDownDialog.setCancelable(false);
        ImageView shuttingDownImage = sCustomizedShuttingDownDialog.findViewById(com.android.internal.R.id.tv_shutting_down);
        //定制关机动画
        AnimatedImageDrawable shuttingDownGif = (AnimatedImageDrawable) context.getDrawable(com.android.internal.R.drawable.shutting_down);
        shuttingDownImage.setImageDrawable(shuttingDownGif);
        shuttingDownGif.start();//播放关机动画
        TextView message = sCustomizedShuttingDownDialog.findViewById(com.android.internal.R.id.tv_shutting_down_msg);
        message.setText(com.android.internal.R.string.shutdown_progress);
        return sCustomizedShuttingDownDialog;
    }
     //Mart!nHu Patch End

4、总结

本文从代码流程角度,大致的梳理了系统power键关机从framework开始的后续流程,通过代码流程了解到:

  • 系统默认关机界面有多种样式及模式
  • 关机界面加载关机选项的实现过程
  • 通过设置sys.powerctl=shutdown,init进程循环监听该属性变化并触发shutdown流程
  • 定制关机界面通常需要定制关机确认界面以及关机进度条界面
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  3I1N9ysrcSyk   2023年12月08日   31   0   0 javahapi数据交换
  DF5J4hb0hcmT   2023年12月07日   50   0   0 javaArthas
R3jekIqJDwNw