一:Android处理Power按键长按操作

    在Framework层中,Android4.x对Power键(KeyEvent.KEYCODE_POWER)的操作,我们从PhoneWindowManager开始分析,在分析前我这里打印了该方法的堆栈调用信息。大家可以参考一下。

public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {

    ......

    android.util.Log.d("BILL",android.util.Log.getStackTraceString(new Throwable()));

    ......

}

1-13 19:35:32.458 D/BILL    (  718): Java.lang.Throwable

01-13 19:35:32.458 D/BILL    (  718): at com.android.internal.policy.impl.PhoneWindowManager.interceptKeyBeforeDispatching(PhoneWindowManager.java:2224)

01-13 19:35:32.458 D/BILL    (  718): at com.android.server.wm.InputMonitor.interceptKeyBeforeDispatching(InputMonitor.java:501)

01-13 19:35:32.458 D/BILL    (  718): at com.android.server.input.InputManagerService.interceptKeyBeforeDispatching(InputManagerService.java:1383)

01-13 19:35:32.458 D/BILL    (  718): at dalvik.system.NativeStart.run(Native Method)

调用流程如下(只贴出关键代码):


interceptKeyBeforeDispatching()-->interceptPowerKeyDown()-->mPowerLongPress.run()


1>

public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {

......

       case KeyEvent.KEYCODE_POWER: {
                result &= ~ACTION_PASS_TO_USER;
                if (down) {
                    mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn, event.getDownTime(),
                            isImmersiveMode(mLastSystemUiFlags));
                    if (isScreenOn && !mPowerKeyTriggered
                            && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                        mPowerKeyTriggered = true;
                        mPowerKeyTime = event.getDownTime();
                        interceptScreenshotChord();
                    }


                    ITelephony telephonyService = getTelephonyService();
                    boolean hungUp = false;
                    if (telephonyService != null) {
                        try {
                            if (telephonyService.isRinging()) {
                                // Pressing Power while there's a ringing incoming
                                // call should silence the ringer.
                                telephonyService.silenceRinger();
                            /// M: [ALPS00093981] @{
                            } else if ((isScreenOn
                                ||  mScreenOffReason == OFF_BECAUSE_OF_PROX_SENSOR)
                            /// @}
                                && (mIncallPowerBehavior
                                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                                    && telephonyService.isOffhook()) {
                                // Otherwise, if "Power button ends call" is enabled,
                                // the Power button will hang up any current active call.
                                hungUp = telephonyService.endCall();
                            }
                        } catch (RemoteException ex) {
                            Log.w(TAG, "ITelephony threw RemoteException", ex);
                        }
                    }
                    interceptPowerKeyDown(!isScreenOn || hungUp
                            || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);

                } else {
                    mPowerKeyTriggered = false;
                    cancelPendingScreenshotChordAction();
                    if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
                        result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP;
                    }
                    mPendingPowerKeyUpCanceled = false;
                }
                break;
            }

......

}

注!红色为判断长按(down),蓝色为判断短按(up)。

2>

    private void interceptPowerKeyDown(boolean handled) {
        mPowerKeyHandled = handled;
        if (!handled) {
            mHandler.postDelayed(mPowerLongPress, ViewConfiguration.getGlobalActionKeyTimeout());
        }
    }

3>

private final Runnable mPowerLongPress = new Runnable() {
......
            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                mPowerKeyHandled = true;
                if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
                    performAuditoryFeedbackForAccessibilityIfNeed();
                }
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                showGlobalActionsDialog();
                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(resolvedBehavior == LONG_PRESS_POWER_SHUT_OFF);
                break;
......
};

注:上述代码中下划线即弹出(关机、重启、飞行模式等选项)的对话框。

二,关机流程

    从前一篇博文我们知道,当用户长按Power键时会弹出(关机、重启,飞行模式等选项)对话框,我们点击关机,则会弹出关机确认对话框。那么从选项对话框到关机确认对话框又是一个什么流程呢。下面我们在简单分析一下:

    showGlobalActionsDialog()-->showDialog()-->handleShow()-->createDialog()-->onPress()-->shutdown()

PhoneWindowManager.Java
    void showGlobalActionsDialog() {
        ......
        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
        ......
    }

GlobalActions.java

    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
        ......
        handleShow();
        ......
    }


    private void handleShow() {
        ......
        mDialog = createDialog();
        ......
    }

    private GlobalActionsDialog createDialog(){
        ......
        mItems = new ArrayList();


        // first: power off
        mItems.add(
            new SinglePressAction(
                    com.Android.internal.R.drawable.uirom_ic_lock_power_off,
                    R.string.global_action_power_off) {


                public void onPress() {
                    // shutdown by making sure radio and power are handled accordingly.
                    mWindowManagerFuncs.shutdown(true);
                }


                public boolean onLongPress() {
                    mWindowManagerFuncs.rebootSafeMode(true);
                    return true;
                }


                public boolean showDuringKeyguard() {
                    return true;
                }


                public boolean showBeforeProvisioning() {
                    return true;
                }
            });
        ......
    }
上述代码中的mWindowManagerFuncs实际上是WindowManagerService的对象,该对象有PhoneWindowManager的init的方法传入GlobalActions的构造函数中,并在上述代码中进行调用。下面这一行代码是调用的关键代码。
mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);


下面是弹出“关机确认对话框”的堆栈:
01-16 18:08:21.497 D/bill    (  720): java.lang.Throwable

01-16 18:08:21.497 D/bill    (  720): at com.android.server.power.ShutdownThread.shutdown(ShutdownThread.java:175)

01-16 18:08:21.497 D/bill    (  720): at com.android.server.wm.WindowManagerService.shutdown(WindowManagerService.java:5783)01-16 18:08:21.497 D/bill    (  720):at com.android.internal.policy.impl.GlobalActions$2.onPress(GlobalActions.java:352)//WindowManagerService实现了接口WindowsManagerFuncs

01-16 18:08:21.497 D/bill    (  720): at com.android.internal.policy.impl.GlobalActions.onClick(GlobalActions.java:581)

01-16 18:08:21.497 D/bill    (  720): at com.android.internal.app.AlertController$AlertParams$3.onItemClick(AlertController.java:952)

01-16 18:08:21.497 D/bill    (  720): at android.widget.AdapterView.performItemClick(AdapterView.java:299)

01-16 18:08:21.497 D/bill    (  720): at android.widget.AbsListView.performItemClick(AbsListView.java:1152)

01-16 18:08:21.497 D/bill    (  720): at android.widget.AbsListView$PerformClick.run(AbsListView.java:3014)

01-16 18:08:21.497 D/bill    (  720): at android.widget.AbsListView$3.run(AbsListView.java:3865)

01-16 18:08:21.497 D/bill    (  720): at android.os.Handler.handleCallback(Handler.java:808)

01-16 18:08:21.497 D/bill    (  720): at android.os.Handler.dispatchMessage(Handler.java:103)

01-16 18:08:21.497 D/bill    (  720): at android.os.Looper.loop(Looper.java:193)

01-16 18:08:21.497 D/bill    (  720): at android.os.HandlerThread.run(HandlerThread.java:61)


从这里(shutdown())我们正式进入关机流程的关键。

shutdown() --->shutdownInner() --->beginShutdownSequence()--->run()--->rebootOrShutdown()--->lowLevelShutdown()<PowerManagerService.java>--->

源码来自:https://github.com/android/platform_frameworks_base/blob/master/services/java/com/android/server/power/ShutdownThread.java

[java]  view plain  copy  print ?
  1. public static void shutdown(final Context context, boolean confirm) {  
  2.        mReboot = false;  
  3.        mRebootSafeMode = false;  
  4.        shutdownInner(context, confirm);  
  5.    }  
注! 

    参数2:confir;关机操作前是否需要用户进行确认
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);

        //longPressBehavior的值标示当前长按Power操作意向(关机、重启。。。)
        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()有些手机厂商常常会在这里添加一些定制功能,例如在对话框中添加“下次快速开机”,定制关机动画等等。随后会根据不同平台进行讲解。下面这张图是Android原生系统的关机画面(对应下面加粗显示的代码):

 private static void  beginShutdownSequence (Context context) {
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Shutdown sequence already running, returning.");
                return;
            }
            sIsStarted = true;
        }
        // throw up an indeterminate system dialog to indicate radio is
        // shutting down.
        ProgressDialog pd = new ProgressDialog(context);
        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.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();⑤
    }
注解!

①上述红色代码中的作用主要是为了防止手机进入休眠状态,从代码中我们看到,此时通过PowerManager的newWakeLock方法生成了PowerManager.WakeLock对象。newWakeLock()是PowerManager中最为常用的方法,该对象是一种锁机制,通过该对象可以控制设备的电源状态。在生成WakeLock实例时通过第一个参数的传入只开控制获取不同的WakeLock,主要是不同的lock对CPU,屏幕,键盘灯有不同的影响。如下:

  1. PARTIAL_WAKE_LOCK:保持CPU 运转,屏幕和键盘灯有可能是关闭的。
  2. SCREEN_DIM_WAKE_LOCK:保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯
  3. SCREEN_BRIGHT_WAKE_LOCK:保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯
  4. FULL_WAKE_LOCK:保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度

②Wake Lock 是一种锁的机制,只要有人拿着这个锁,系统九五案发进入休眠,可以被用户动态程序和内核获得,这个锁可以使有超时的或者是没有超时的,超时的锁会在时间过去以后自动解锁。如果没有锁了,或者超时了,内核就会启动休眠的那套机制来进入休眠。PowerManager.WakeLock有加锁和解锁的两种状态,加锁的方式有两种,一种是永久的锁住,这样的锁除非是显示的放开,否则是不会解锁的,所以这种锁用起来要非常小心,第二种锁是超时锁,这种锁会在锁住一段时间后自动解锁。

sInstance.mCpuWakeLock.setReferenceCounted(false);是设置锁的方式为永久的锁住。

sInstance.mCpuWakeLock.acquire(); 加锁
④上述蓝色代码的作用是为了保证用户体验,保持屏幕、键盘的亮度
⑤接着启动关机线程,进入关机流程的下一个节点。

/**
     * Makes sure we handle the shutdown gracefully.
     * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
     */
    public void run() {
        BroadcastReceiver br = new BroadcastReceiver() {
            @Override public void onReceive(Context context, Intent intent) {
                actionDone();//这里用于接受关机广播,actionDone()方法主要是防止应用程序取消关机操作。
            }
        };

        /*
         * Write a system property in case the system_server reboots before we
         * get to the actual hardware restart. If that happens, we'll retry at
         * the beginning of the SystemServer startup.
         */
        {
            String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
        }


        /*
         * If we are rebooting into safe mode, write a system property
         * indicating so.
         */
        if (mRebootSafeMode) {
            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
        }


        Log.i(TAG, "Sending shutdown broadcast...");
        
        // First send the high-level shut down broadcast.
        mActionDone = false;
        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mContext.sendOrderedBroadcastAsUser(intent,
                UserHandle.ALL, null, br, mHandler, 0, null, null);//发送关机广播
        
        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
        synchronized (mActionDoneSync) {
            while (!mActionDone) {
                long delay = endTime - SystemClock.elapsedRealtime();
                if (delay <= 0) {
                    Log.w(TAG, "Shutdown broadcast timed out");
                    break;
                }
                try {
                    mActionDoneSync.wait(delay);
                } catch (InterruptedException e) {
                }
            }
        }
        
        Log.i(TAG, "Shutting down activity manager...");
        
        final IActivityManager am =
            ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
        if (am != null) {
            try {
                am.shutdown(MAX_BROADCAST_TIME);//关闭ActivityManagerService
            } catch (RemoteException e) {
            }
        }


        Log.i(TAG, "Shutting down package manager...");


        final PackageManagerService pm = (PackageManagerService)
            ServiceManager.getService("package");
        if (pm != null) {
            pm.shutdown();//关闭PackageManagerService服务
        }


        // 关闭Radios
        shutdownRadios(MAX_RADIO_WAIT_TIME);


        // Shutdown MountService to ensure media is in a safe state
        IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
            public void onShutDownComplete(int statusCode) throws RemoteException {
                Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
                actionDone();
            }
        };


        Log.i(TAG, "Shutting down MountService");


        // Set initial variables and time out time.
        mActionDone = false;
        final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
        synchronized (mActionDoneSync) {
            try {
                final IMountService mount = IMountService.Stub.asInterface(
                        ServiceManager.checkService("mount"));
                if (mount != null) {
                    mount.shutdown(observer);//关闭MountService
                } else {
                    Log.w(TAG, "MountService unavailable for shutdown");
                }
            } catch (Exception e) {
                Log.e(TAG, "Exception during MountService shutdown", e);
            }
            while (!mActionDone) {
                long delay = endShutTime - SystemClock.elapsedRealtime();
                if (delay <= 0) {
                    Log.w(TAG, "Shutdown wait timed out");
                    break;
                }
                try {
                    mActionDoneSync.wait(delay);
                } catch (InterruptedException e) {
                }
            }
        }


        rebootOrShutdown(mReboot, mRebootReason);
    }

最后调用rebootOrShutdown()
    public static void rebootOrShutdown(boolean reboot, String reason) {
        if (reboot) {
            Log.i(TAG, "Rebooting, reason: " + reason);
            PowerManagerService.lowLevelReboot(reason);
            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
        } else if (SHUTDOWN_VIBRATE_MS > 0) {
            // vibrate before shutting down
            Vibrator vibrator = new SystemVibrator();
            try {
                vibrator.vibrate(SHUTDOWN_VIBRATE_MS);//关机震动
            } catch (Exception e) {
                // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
                Log.w(TAG, "Failed to vibrate during shutdown.", e);
            }


            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
            try {
                Thread.sleep(SHUTDOWN_VIBRATE_MS);
            } catch (InterruptedException unused) {
            }
        }


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


     /**
     * Low-level function turn the device off immediately, without trying
     * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.
     */
    public static void lowLevelShutdown() {
        SystemProperties.set("sys.powerctl", "shutdown");//这里通过修改Android属性进行关机
    }
注:上述代码中,红色加粗部分为关机关键代码,我也可以通过adb 命令进行修改Android系统的属性进行关机,具体命令如下

adb shell setprop sys.powerctl shutdown



更多相关文章

  1. Android开发周刊 第四期
  2. Android有用代码片段(三)
  3. Android设置透明、半透明等效果
  4. Android调节屏幕亮度分析源码
  5. Android(安卓)Studio 中使用SVN注意事项
  6. PhoneGap Developer App 安卓(android)调试工具免费下载
  7. Ant 打包 Android(安卓)Project
  8. Android中的对话框
  9. Android中的使用

随机推荐

  1. Android代码风格(Android属性前缀m的意思)
  2. Android(安卓)AIDL实现两个APP间的跨进程
  3. Android中bindService()启动Service的过
  4. 通信组件之Intent的基本使用
  5. Android(安卓)Studio提示端口号5037被占
  6. 通过GridView仿微信动态添加本地图片
  7. android RecyclerView实现列表定位
  8. Android文件存储和读取方式
  9. Android(安卓)简单实现订单模块类APP的物
  10. 那两年炼就的Android内功修养 - 老罗的An