1. 程式人生 > >Android原始碼解析(二十八)-->電源開關機按鍵事件流程

Android原始碼解析(二十八)-->電源開關機按鍵事件流程

前面我們講解了系統截圖按鍵處理流程,HOME按鍵處理流程,今天再來講解一下電源開關機按鍵事件流程,當然這也是系統按鍵處理流程方面的最後一篇部落格了。

和截圖按鍵、HOME按鍵的處理流程類似,電源按鍵由於也是系統級別的按鍵,所以對其的事件處理邏輯是和截圖按鍵、HOME按鍵類似,不在某一個App中,而是在PhoneWindowManager的dispatchUnhandledKey方法中。所以和前面兩篇類似,這裡我們也是從PhoneWindowManager的dispatchUnhandledKey方法開始我們今天電源開關機按鍵的事件流程分析。

下面首先看一下dispatchUnhandledKey方法的實現邏輯:

public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
        ...
        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) { fallbackAction = kcm.getFallbackAction(keyCode, metaState); } else { fallbackAction = mFallbackActions.get(keyCode); } if (fallbackAction != null) { if (DEBUG_INPUT) { Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode + " metaState=" + Integer.toHexString(fallbackAction.metaState)); } final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; fallbackEvent = KeyEvent.obtain( event.getDownTime(), event.getEventTime(), event.getAction(), fallbackAction.keyCode, event.getRepeatCount(), fallbackAction.metaState, event.getDeviceId(), event.getScanCode(), flags, event.getSource(), null); if (!interceptFallback(win, fallbackEvent, policyFlags)) { fallbackEvent.recycle(); fallbackEvent = null; } if (initialDown) { mFallbackActions.put(keyCode, fallbackAction); } else if (event.getAction() == KeyEvent.ACTION_UP) { mFallbackActions.remove(keyCode); fallbackAction.recycle(); } } } ... return fallbackEvent; }

通過前面兩篇文章的分析
android原始碼解析(二十六)–>截圖事件流程  
android原始碼解析(二十七)–>HOME事件流程
我們知道關於系統按鍵的處理邏輯被下放到了interceptFallback方法中,所以我們繼續看一下interceptFallback方法的實現邏輯。

private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
        int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
        if ((actions & ACTION_PASS_TO_USER) != 0) {
            long delayMillis = interceptKeyBeforeDispatching(
                    win, fallbackEvent, policyFlags);
            if (delayMillis == 0) {
                return true;
            }
        }
        return false;
    }

通過分析interceptFallback方法的原始碼,我們知道關於電源按鍵的處理邏輯在interceptKeyBeforeQueueing方法中,所以我們需要繼續看一下interceptKeyBeforeQueueing方法中關於電源按鍵的處理邏輯。

public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        ...
            case KeyEvent.KEYCODE_POWER: {
                result &= ~ACTION_PASS_TO_USER;
                isWakeKey = false; // wake-up will be handled separately
                if (down) {
                    interceptPowerKeyDown(event, interactive);
                } else {
                    interceptPowerKeyUp(event, interactive, canceled);
                }
                break;
            }
            ...

        return result;
    }

這裡我們重點看一下電源按鍵的處理事件,可以發現當電源按鍵按下的時候我們呼叫了interceptPowerKeyDown方法,可以看出,這個方法就是處理電源事件的了,既然如此,我們繼續看一下interceptPowerKeyDown方法的執行邏輯。

private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
        ...
        // Latch power key state to detect screenshot chord.
        if (interactive && !mScreenshotChordPowerKeyTriggered
                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            mScreenshotChordPowerKeyTriggered = true;
            mScreenshotChordPowerKeyTime = event.getDownTime();
            interceptScreenshotChord();
        }

        // Stop ringing or end call if configured to do so when power is pressed.
        TelecomManager telecomManager = getTelecommService();
        boolean hungUp = false;
        if (telecomManager != null) {
            if (telecomManager.isRinging()) {
                // Pressing Power while there's a ringing incoming
                // call should silence the ringer.
                telecomManager.silenceRinger();
            } else if ((mIncallPowerBehavior
                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                    && telecomManager.isInCall() && interactive) {
                // Otherwise, if "Power button ends call" is enabled,
                // the Power button will hang up any current active call.
                hungUp = telecomManager.endCall();
            }
        }

        // 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
                || mScreenshotChordVolumeUpKeyTriggered;
        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()) {
                    Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageDelayed(msg,
                            ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
                }
            } else {
                wakeUpFromPowerKey(event.getDownTime());

                if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) {
                    Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageDelayed(msg,
                            ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
                    mBeganFromNonInteractive = true;
                } else {
                    final int maxCount = getMaxMultiPressPowerCount();

                    if (maxCount <= 1) {
                        mPowerKeyHandled = true;
                    } else {
                        mBeganFromNonInteractive = true;
                    }
                }
            }
        }
    }

這裡我們重點看一下if(interactive)分支,在這裡我們傳送一個一個非同步訊息,並且msg的what為MSG_POWER_LONG_PRESS,即長按電源事件的非同步訊息,所以我們看一下mHandler的handleMessage方法對該what訊息的處理邏輯。

case MSG_POWER_LONG_PRESS:
                    powerLongPress();
                    break;

我們可以發現在mHandler的handleMessage方法中當msg的what為MSG_POWER_LONG_PRESS時我們呼叫了powerLongPress方法,這個方法應該就是處理電源按鍵長按的邏輯,下面我們來看一下powerLongPress方法的實現。

private void powerLongPress() {
        final int behavior = getResolvedLongPressOnPowerBehavior();
        switch (behavior) {
        case LONG_PRESS_POWER_NOTHING:
            break;
        case LONG_PRESS_POWER_GLOBAL_ACTIONS:
            mPowerKeyHandled = true;
            if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
                performAuditoryFeedbackForAccessibilityIfNeed();
            }
            showGlobalActionsInternal();
            break;
        case LONG_PRESS_POWER_SHUT_OFF:
        case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
            mPowerKeyHandled = true;
            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
            mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
            break;
        }
    }

可以發現這裡有四個switch分之,其中第一個什麼都不做直接break掉,第二個case則需要彈出選擇操作介面,比如:飛航模式,開關機,靜音模式,重新啟動等,這裡可以參看一下小米手機的關機介面:
這裡寫圖片描述

然後第三第四個case分之則是直接呼叫關機方法,這裡我們先看第二個case,看看系統是如何顯示出關機操作介面的。那我們看一下showGlobalActionsInternal方法的實現邏輯。

void showGlobalActionsInternal() {
        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
        if (mGlobalActions == null) {
            mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
        }
        final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
        if (keyguardShowing) {
            // 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);
        }
    }

可以發現我們首先呼叫了sendCloseSystemWindows方法,前面我們分析HOME按鍵流程的時候(android原始碼解析(二十七)–>HOME事件流程)知道該方法用於關機系統彈窗,比如輸入法,桌布等。然後我們建立了一個GlobalActions物件,並呼叫了其showDialog方法,通過分析原始碼,我們發現該方法就是用於顯示長按電源按鍵彈出操作介面的,我們首先看一下GlobalActions的構造方法:

public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
        mContext = context;
        mWindowManagerFuncs = windowManagerFuncs;
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        mDreamManager = IDreamManager.Stub.asInterface(
                ServiceManager.getService(DreamService.DREAM_SERVICE));

        // receive broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
        context.registerReceiver(mBroadcastReceiver, filter);

        ConnectivityManager cm = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);
        mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);

        // get notified of phone state changes
        TelephonyManager telephonyManager =
                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
        mContext.getContentResolver().registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                mAirplaneModeObserver);
        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
        mHasVibrator = vibrator != null && vibrator.hasVibrator();

        mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_useFixedVolume);
    }

可以看到在GlobalActions物件的構造方法中我們主要用於初始化其成員變數,由於我們的電源長按操作介面是一個全域性頁面,所以這裡自定義了一個Window物件,下面我們看一下GlobalActions的showDialog方法。

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();
        }
    }

可以看到在showDialog方法中我們首先判斷mDialog是否為空,若為空則傳送msg的what為MESSAGE_SHOW的非同步訊息,否則呼叫handleShow方法,而這裡的mDialog是一個型別為GlobalActionsDialog的變數,由於我們的mDialog為空,所以下面我們看一下handleShow方法。

private void handleShow() {
        awakenIfNecessary();
        mDialog = createDialog();
        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("GlobalActions");
            mDialog.getWindow().setAttributes(attrs);
            mDialog.show();
            mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
        }

在方法體中我們呼叫了createDialog方法,建立了GlobalActionsDialog型別的mDialog,這裡我們看一下createDialog的實現方法。

private GlobalActionsDialog createDialog() {
        ...
        mAirplaneModeOn = new ToggleAction(
                R.drawable.ic_lock_airplane_mode,
                R.drawable.ic_lock_airplane_mode_off,
                R.string.global_actions_toggle_airplane_mode,
                R.string.global_actions_airplane_mode_on_status,
                R.string.global_actions_airplane_mode_off_status) {

            void onToggle(boolean on) {
                if (mHasTelephony && Boolean.parseBoolean(
                        SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
                    mIsWaitingForEcmExit = true;
                    // Launch ECM exit dialog
                    Intent ecmDialogIntent =
                            new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
                    ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    mContext.startActivity(ecmDialogIntent);
                } else {
                    changeAirplaneModeSystemSetting(on);
                }
            }

            @Override
            protected void changeStateFromPress(boolean buttonOn) {
                if (!mHasTelephony) return;

                // In ECM mode airplane state cannot be changed
                if (!(Boolean.parseBoolean(
                        SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
                    mState = buttonOn ? State.TurningOn : State.TurningOff;
                    mAirplaneState = mState;
                }
            }

            public boolean showDuringKeyguard() {
                return true;
            }

            public boolean showBeforeProvisioning() {
                return false;
            }
        };
        onAirplaneModeChanged();

        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());
            } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                mItems.add(mAirplaneModeOn);
            } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
                if (Settings.Global.getInt(mContext.getContentResolver(),
                        Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
                    mItems.add(getBugReportAction());
                }
            } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
                if (mShowSilentToggle) {
                    mItems.add(mSilentModeAction);
                }
            } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
                if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
                    addUsersToMenu(mItems);
                }
            } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                mItems.add(getSettingsAction());
            } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
                mItems.add(getLockdownAction());
            } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
                mItems.add(getVoiceAssistAction());
            } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
                mItems.add(getAssistAction());
            } else {
                Log.e(TAG, "Invalid global action key " + actionKey);
            }
            // Add here so we don't add more than one.
            addedKeys.add(actionKey);
        }

        mAdapter = new MyAdapter();

        AlertParams params = new AlertParams(mContext);
        params.mAdapter = mAdapter;
        params.mOnClickListener = this;
        params.mForceInverseBackground = true;

        GlobalActionsDialog dialog = new GlobalActionsDialog(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;
                    }
        });
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);

        dialog.setOnDismissListener(this);

        return dialog;
    }

方法體的內容比較長,我們看重點的內容,首先我們通過呼叫mContext.getResources().getStringArray(com.android.internal.R.array.config_globalActionsList)獲得操作列表,這裡可能包含:飛航模式、開關機、靜音模式、重啟等等,然後我們輪訓操作列表,並新增相應的Action最後我們將這個操作列表儲存到Dialog的adapter中並返回該dialog,然後我們回到我們剛剛的handleShow方法,在得到返回的dialog之後我們呼叫了dialog的show方法,這樣我們就顯示出了電源長按操作介面,比如小米的介面:
這裡寫圖片描述

好吧,繼續我們的分析,當我們長按電源按鍵彈出操作彈窗之後,這時候點選關機是怎麼樣的流程呢?我們發現在createDialog方法中關機操作adapter的item,我們添加了:

mItems.add(new PowerAction());

這樣不難發現我們對關機按鈕的操作封裝在了PowerAction中,所以我們繼續看一下PowerAction的實現。

private final class PowerAction extends SinglePressAction implements LongPressAction {
        private PowerAction() {
            super(com.android.internal.R.drawable.ic_lock_power_off,
                R.string.global_action_power_off);
        }

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

        @Override
        public boolean showDuringKeyguard() {
            return true;
        }

        @Override
        public boolean showBeforeProvisioning() {
            return true;
        }

        @Override
        public void onPress() {
            // shutdown by making sure radio and power are handled accordingly.
            mWindowManagerFuncs.shutdown(false /* confirm */);
        }
    }

可以發現在PowerAction類的成員函式onPress方法中我們呼叫了mWindowManagerFuncs.showdown方法,而這個方法也就是開始執行我們的關機操作了,那麼這裡的mWindowManagerFuncs又是什麼呢?它是在什麼時候賦值的呢?通過分析我們發現這裡的mWindowManagerFuncs成員變數是在GlobalActions的構造方法中賦值的。

public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) {
        ...
        mWindowManagerFuncs = windowManagerFuncs;
        ...
}

好吧,回到我們的PhoneWindowManager,早構造GlobalActions時,直接傳遞的是PhoneWindowManager的成員變數mWindowManagerFuncs,那麼PhoneWindowManager的mWindowManagerFuncs成員變數又是何時被賦值的呢?通過分析原始碼我們能夠看到PhoneWindowManager的mWindowManagerFuncs變數是在PhoneWindowManager的init方法中初始化的,好吧,再次查詢PhoneWindowManager的init方法是何時被呼叫的。

經過查詢終於在WindowManagerService中我們找到了PhoneWindowManager的init方法的呼叫。

private void initPolicy() {
        UiThread.getHandler().runWithScissors(new Runnable() {
            @Override
            public void run() {
                WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());

                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
            }
        }, 0);
    }

這裡的mPolicy就是一個PhoneWindowManager的實力,可以發現這裡的init方法中mWindowManagerFuncs傳遞的就是一個WindowManagerService的例項,O(∩_∩)O哈哈~,讓我們好找。

然麼在PowerAction的onPress方法中呼叫的mWindowManagerFuncs.shutdown(false /* confirm */);方法,實際上呼叫的就是WindowManagerService的shutdown方法,這樣我們繼續看一下WindowManagerService的shutdown方法的實現。

@Override
    public void shutdown(boolean confirm) {
        ShutdownThread.shutdown(mContext, confirm);
    }

好吧,這裡很簡單就是直接呼叫了ShutdownThread的shutdown方法,看樣子這裡就是執行關機操作的封裝了,繼續看一下ShutdownThread的shutdown方法。

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

可以看到在ShutdownThread的shutdown方法中程式碼很簡單,具體的操作下發到了shutdownInner方法中,那麼我們繼續看一下shutdownInner方法的實現。

static void shutdownInner(final Context context, boolean confirm) {
        // ensure that only one thread is trying to power down.
        // any additional calls are just returned
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Request to shutdown already running, returning.");
                return;
            }
        }

        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);
            sConfirmDialog.show();
        } else {
            beginShutdownSequence(context);
        }
    }

可以看到方法體中,首先判斷若使用者點選了關機按鍵是否彈出確認框,若彈出則彈出關機確認框,若不需要確認,則直接呼叫beginShutdownSequence方法,執行關機操作。而在關機確認框中我們的確認按鈕也是執行了beginShutdownSequence方法,所以我們繼續看一下關機方法beginShutdownSequence。

private static void beginShutdownSequence(Context context) {
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Shutdown sequence already running, returning.");
                return;
            }
            sIsStarted = true;
        }
        ...
        if (PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) {
            mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();
            if (mRebootUpdate) {
                pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
                pd.setMessage(context.getText(
                        com.android.internal.R.string.reboot_to_update_prepare));
                pd.setMax(100);
                pd.setProgressNumberFormat(null);
                pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                pd.setProgress(0);
                pd.setIndeterminate(false);
            } 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 {
            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();

        sInstance.mProgressDialog = pd;
        sInstance.mContext = context;
        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

        // make sure we never fall asleep again
        sInstance.mCpuWakeLock = null;
        try {
            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
            sInstance.mCpuWakeLock.setReferenceCounted(false);
            sInstance.mCpuWakeLock.acquire();
        } catch (SecurityException e) {
            Log.w(TAG, "No permission to acquire wake lock", e);
            sInstance.mCpuWakeLock = null;
        }

        // also make sure the screen stays on for better user experience
        sInstance.mScreenWakeLock = null;
        if (sInstance.mPowerManager.isScreenOn()) {
            try {
                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
                sInstance.mScreenWakeLock.setReferenceCounted(false);
                sInstance.mScreenWakeLock.acquire();
            } catch (SecurityException e) {
                Log.w(TAG, "No permission to acquire wake lock", e);
                sInstance.mScreenWakeLock = null;
            }
        }

        // start the thread that initiates shutdown
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

在方法beginShutdownSequence中我們首先初始化了一個Process的dialog,該dialog用於顯示關機介面,然後我們呼叫了sInstance.start方法,再往下的方法中就是真正的shutdown方法的實現,同時也是native方法,我們這裡就不做過得解讀了。。。

總結:

  • 電源按鍵是系統按鍵,所以對電源按鍵的處理邏輯也是在PhoneWindowManager的dispatchUnhandledKey方法中;

  • 在PhoneWindowManager的dispatchUnhandleKey方法處理Power按鍵之後會首先顯示系統操作彈窗,一般包括但不限於:飛航模式,靜音模式,重新啟動,關機等;

  • 當用戶點選關機按鈕是呼叫的是WindowManagerService.shutdown方法,而內部呼叫的是ShutdownThread.shutdown方法;

相關推薦

Android原始碼解析-->電源關機按鍵事件流程

前面我們講解了系統截圖按鍵處理流程,HOME按鍵處理流程,今天再來講解一下電源開關機按鍵事件流程,當然這也是系統按鍵處理流程方面的最後一篇部落格了。 和截圖按鍵、HOME按鍵的處理流程類似,電源按鍵由於也是系統級別的按鍵,所以對其的事件處理邏輯是和截圖按鍵

Android原始碼解析-->應用程式返回按鍵執行流程

從這篇文章中我們開始分析android系統的事件分發流程,其實網上已經有了很多關於android系統的事件分發流程的文章,奈何看了很多但是印象還不是很深,所以這裡總結一番。 android系統的事件分發流程分為很多部分: Native層 –> V

Android原始碼解析-->HOME事件流程

上一篇文章中我們介紹了android系統的截圖事件,由於截圖事件是一種系統全域性處理事件,所以事件的處理邏輯不是在App中執行,而是在PhoneWindowManager中執行。而本文我們現在主要講解android系統中HOME按鍵的事件處理,和截圖事件類似

Android原始碼解析-->PopupWindow載入繪製流程

在前面的幾篇文章中我們分析了Activity與Dialog的載入繪製流程,取消繪製流程,相信大家對Android系統的視窗繪製機制有了一個感性的認識了,這篇文章我們將繼續分析一下PopupWindow載入繪製流程。 在分析PopupWindow之前,我們將

Android原始碼解析-->截圖事件流程

今天這篇文章我們主要講一下Android系統中的截圖事件處理流程。用過android系統手機的同學應該都知道,一般的android手機按下音量減少鍵和電源按鍵就會觸發截圖事件(國內定製機做個修改的這裡就不做考慮了)。那麼這裡的截圖事件是如何觸發的呢?觸發之後

Android學習記錄--Android apache httpclients的使用。

1.歷史原因:      Android當前不在支援APACHE的一套內容,開始推自己的網路請求庫,基本等同於okhttp。但是非常令人失望的是,這個庫目前看支援是不全的,對於網路鑑權,只支援BASE的網路鑑權,不支援DIGEST鑑權,因此APACHE的庫依然還需要使用。但

eos原始碼賞析:EOS智慧合約之區塊簽名的天龍“步”

在上篇文章中我們提到了,由使用者操作會產生各種事務,事務的鏈上執行是由push_transaction來完成的,我們簡單的劃分了下,具體可參考eos原始碼賞析(二十):EOS智慧合約之push_transaction的天龍八“步” 。我們知道,在區塊生產或者打包

Android原始碼解析-->觸控事件分發流程

前面一篇文章中我們分析了App返回按鍵的分發流程,從Native層到ViewRootImpl層到DocorView層到Activity層,以及在Activity中的dispatchKeyEvent方法中分發事件,最終呼叫了Activity的finish方法,

Android開發系列:Notification的功能與使用方法

font _id when ice extends 開發 content androi mark 關於消息的提示有兩種:一種是Toast,一種就是Notification。前者維持的時間比較短暫,後者維持的時間比較長。 並且我們尋常手機的應用比方網易、貼吧等等都有非常多

【轉】JMeter學習內存溢出解決方法

不能 -xms 百度 解決 code apache 超過 軟件測試 內存 使用jmeter進行壓力測試時遇到一段時間後報內存溢出outfmenmory錯誤,導致jmeter卡死了,先嘗試在jmeter.bat中增加了JVM_ARGS="-Xmx2048m -Xms2048m

ERP合同列表頁面自動導航

date sheet ioe sage 技術 部門 lis highlight tid 合同審核完成頁面: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="CRMContractOver.asp

Android學習路線運用Fragment構建動態UI——創建一個Fragment

動態 app idt 文檔 部分 roi 現實 調用 android學習 你能夠把fragment看成是activity的模塊化部分。它擁有自己的生命周期,接受它自己的輸入事件,你能夠在activity執行時加入或者刪除它(有點像是一個“子activity”。你

Python學習筆記多線程

oop 模擬 筆記 標準庫 函數 只需要 pre 開始 print 摘抄自:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431928238187

笨辦法學Python

等你 utils 很難 cti style app 運算 技術分享 util 習題 28: 布爾表達式練習 上一節你學到的邏輯組合的正式名稱是“布爾邏輯表達式(boolean logic expression)”。在編程中,布爾邏輯可以說是無處不在。它們是計算機運算

Linux學習iptables () iptables規則語法

star amp accept log saving linux 意思 root bit 查看iptables規則: [root@ruanwenwu-0002 ~]# iptables -nvL Chain INPUT (policy ACCEPT 0 packets,

Linux學習筆記awk

awkhead -n2 test.txt|awk -F ‘:‘ ‘{print $1}‘ //文件的前兩行,以:分隔,打印第一段 head -n2 test.txt|awk -F ‘:‘ ‘{print $0}‘ //文件的前兩行,以:分隔,打印所有的內容($N就是第N段,0就是所有字段)

Linux學習總結 數據同步工具 rsync

rsyncrsync是linux系統下的數據鏡像備份工具。使用快速增量備份工具Remote Sync可以遠程同步,支持本地復制,或者與其他SSH、rsync主機同步。 rsync格式 rsync [OPTION] … SRC DEST rsync [OPTION] … SRC [user@]host:

C之典型字符串

C語言 字符串 字符數組 我們在上節博客中介紹了 C 語言中字符串相關的概念,那麽我們今天就來看看在字符串這塊的典型問題。 A、我們先來看看下面的示例代碼會輸出什麽,代碼如下#include <stdio.h> int main() { char

python2.7練習小例子

tail pen start 首字母 != 如果第一個字母一樣 append 程序 eight 28):題目:請輸入星期幾的第一個字母來判斷一下是星期幾,如果第一個字母一樣,則繼續判斷第二個字母。 程序分析:用情況語句比較好,如果第一個字母一樣,則判斷用情況

Android開發實戰:淺談android:clipChildren屬性

.cn viewpage port 部分 lap ole 有一個 默認 版本 原文:Android開發實戰(二十一):淺談android:clipChildren屬性實現功能: 1、APP主界面底部模塊欄 2、ViewPager一屏多個界面顯示 3、........