1. 程式人生 > >android 7.1 雙擊power按鍵喚醒camera功能調查

android 7.1 雙擊power按鍵喚醒camera功能調查

最近專案要求實現一個一定短時間內連續點選3次power按鍵進行某個特殊動作的需求,基於的base是android 7.1。以前沒有聽說過這個新功能,最初的想法是在PhoneWindowmanager.java的interceptKeyBeforeQueueing(KeyEvent event, int policyFlags)方法中對powerkey進行判斷處理,這樣做的話所有關於連續點選以及點選時間間隔的邏輯都要自己來實現,費了半天勁寫了點邏輯,燒寫進去後測試。
發現雙擊power按鍵的時候竟然觸發了camera功能!!!這是什麼鬼???

百度之後竟然發現“關閉雙擊 Power 開啟 Camera 功能”的相關資料,查了下發現原來android 7.1已經幫你實現了雙擊power按鍵的邏輯處理,研究了一下,特將這部分的邏輯處理進行說明。

首先怎麼入手呢,從log開始分析。
02-26 14:02:32.115  1965  2066 I ActivityManager: START u0 {act=android.media.action.STILL_IMAGE_CAMERA flg=0x14000000 cmp=com.android.camera/.Camera} from uid 10038 on display 0
當啟動camera的時候,ActivityManager的log如下,uid顯示10038,那麼10038到底是哪個應用呢,我們可以通過拉取packages.list檔案進行檢視。
adb pull /data/system/packages.list .
拉出packages.list檔案後檢視發現如下,原來是systemUI裡面啟動的camera。
com.android.systemui 10038 0 /data/user_de/0/com.android.systemui platform:privapp 3002,1023,1015,3001,3006
這下有了方向了,繼續檢視SystemUI的程式碼,在SystemUI裡面檢視啟動camera相關的東西,原來是在PhoneStatusBar.java裡面。
PhoneStatusBar.java (base\packages\systemui\src\com\android\systemui\statusbar\phone):
    @Override
    public void onCameraLaunchGestureDetected(int source) {
        mLastCameraLaunchSource = source;
        if (mStartedGoingToSleep) {
            mLaunchCameraOnFinishedGoingToSleep = true;
            return;
        }
        if (!mNotificationPanel.canCameraGestureBeLaunched(
                mStatusBarKeyguardViewManager.isShowing() && mExpandedVisible)) {
            return;
        }
        if (!mDeviceInteractive) {
            PowerManager pm = mContext.getSystemService(PowerManager.class);
            pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:CAMERA_GESTURE");
            mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
        }
        vibrateForCameraGesture();
        if (!mStatusBarKeyguardViewManager.isShowing()) {
            startActivity(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT,
                    true /* dismissShade */);
        } else {
            if (!mDeviceInteractive) {
                // Avoid flickering of the scrim when we instant launch the camera and the bouncer
                // comes on.
                mScrimController.dontAnimateBouncerChangesUntilNextFrame();
                mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
            }
            if (mScreenTurningOn || mStatusBarKeyguardViewManager.isScreenTurnedOn()) {
                mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
            } else {
                // We need to defer the camera launch until the screen comes on, since otherwise
                // we will dismiss us too early since we are waiting on an activity to be drawn and
                // incorrectly get notified because of the screen on event (which resumes and pauses
                // some activities)
                mLaunchCameraOnScreenTurningOn = true;
            }
        }
    }
重點是這句
startActivity(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT,
                    true /* dismissShade */);
那麼哪裡調到的onCameraLaunchGestureDetected(int source)呢?
StatusBarManagerService.java (base\services\core\java\com\android\server\statusbar):
        @Override
        public void onCameraLaunchGestureDetected(int source) {
            if (mBar != null) {
                try {
                    mBar.onCameraLaunchGestureDetected(source);
                } catch (RemoteException e) {
                }
            }
        }
原來是在這裡呼叫過來的,那麼繼續跟蹤。
GestureLauncherService.java (base\services\core\java\com\android\server):
    private boolean handleCameraLaunchGesture(boolean useWakelock, int source) {
        boolean userSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
        if (!userSetupComplete) {
            if (DBG) Slog.d(TAG, String.format(
                    "userSetupComplete = %s, ignoring camera launch gesture.",
                    userSetupComplete));
            return false;
        }
        if (DBG) Slog.d(TAG, String.format(
                "userSetupComplete = %s, performing camera launch gesture.",
                userSetupComplete));

        if (useWakelock) {
            // Make sure we don't sleep too early
            mWakeLock.acquire(500L);
        }
        StatusBarManagerInternal service = LocalServices.getService(
                StatusBarManagerInternal.class);
        service.onCameraLaunchGestureDetected(source);
        return true;
    }
我們跟蹤到了GestureLauncherService.java這個類中,這裡面會獲取StatusBar的系統服務,然後呼叫onCameraLaunchGestureDetected方法。
繼續往上游查詢,會找到GestureLauncherService.java中的interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched)。
一看到名字已經很熟悉了,聯想所有hardkey事件相關都是從PhoneWindowManager.java中轉發處理的,所以猜測這個方法應該是在PhoneWindowManager.java調過來的。
PhoneWindowManager.java (base\services\core\java\com\android\server\policy):
    private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
        // Hold a wake lock until the power key is released.
        if (!mPowerKeyWakeLock.isHeld()) {
            mPowerKeyWakeLock.acquire();
        }

        // Cancel multi-press detection timeout.
        if (mPowerKeyPressCounter != 0) {
            mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
        }

        // Detect user pressing the power button in panic when an application has
        // taken over the whole screen.
        boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
                SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),
                isNavBarEmpty(mLastSystemUiFlags));
        if (panic) {
            mHandler.post(mHiddenNavPanic);
        }

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

        GestureLauncherService gestureService = LocalServices.getService(
                GestureLauncherService.class);
        boolean gesturedServiceIntercepted = false;
        if (gestureService != null) {
            gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
                    mTmpBoolean);
            if (mTmpBoolean.value && mGoingToSleep) {
                mCameraGestureTriggeredDuringGoingToSleep = true;
            }
        }

        // 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 || 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()) {
                    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;
                    }
                }
            }
        }
    }
gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, mTmpBoolean);
這句很關鍵,這樣我們就跟最初調查的地方聯絡上了。
PhoneWindowManager.java (base\services\core\java\com\android\server\policy):
    /** {@inheritDoc} */
    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        ......

        // Handle special keys.
        switch (keyCode) {
            ......
            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;
            }
            ......
        }

        if (useHapticFeedback) {
            performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
        }

        if (isWakeKey) {
            wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, "android.policy:KEY");
        }

        return result;
    }

至此邏輯呼叫關係調查清楚,我們只需要在interceptPowerKeyDown方法中進行相應的定製就可以了。
GestureLauncherService.java (base\services\core\java\com\android\server):
    public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
            MutableBoolean outLaunched) {
        boolean launched = false;
        boolean intercept = false;
        long doubleTapInterval;
        synchronized (this) {
            doubleTapInterval = event.getEventTime() - mLastPowerDown;
            if (mIsEmergencyOnPowerKeyTapEnabled) {
                //mIsEmergencyOnPowerKeyTapEnabled的值可以用來enable或disable三擊的功能
                System.arraycopy(mHits, 1, mHits, 0, mHits.length-1);
                mHits[mHits.length-1] = SystemClock.uptimeMillis();
                for (int i = 0 ; i < mHits.length ; i++) {
                    Slog.i(TAG, "mHits[" + i + "] = " + mHits[i] + "ms");
                }
                if (mHits[0] >=
                        (SystemClock.uptimeMillis()-mDuration)) {
                     //此處是對三擊的時間進行判斷,利用一個array來儲存每次點選down的時間,然後進行判斷是否在mDuration時間之類連續點選了3次。
                     launched = true;
                     intercept = interactive;
                     Arrays.fill(mHits,0);
                }
            } else if (mCameraDoubleTapPowerEnabled//mCameraDoubleTapPowerEnabled的值可以用來enable或disable雙擊啟動camera的功能
                    && doubleTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
                launched = true;
                intercept = interactive;
            }
            mLastPowerDown = event.getEventTime();
        }
        if (launched) {
            if (mIsEmergencyOnPowerKeyTapEnabled &&
                    !TextUtils.isEmpty(mEmergencyNumber)) {
                Slog.i(TAG, "Power button Triple tap gesture detected, launching Emergency Call");
                Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
                        Uri.fromParts("tel", mEmergencyNumber, null));
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                getContext().startActivityAsUser(intent, UserHandle.CURRENT);
            } else {
                Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval="
                        + doubleTapInterval + "ms");
                launched = handleCameraLaunchGesture(false /* useWakelock */,
                        StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
                if (launched) {
                    MetricsLogger.action(mContext,
                            MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE,
                            (int) doubleTapInterval);
                }
            }
        }
        MetricsLogger.histogram(mContext, "power_double_tap_interval", (int) doubleTapInterval);
        outLaunched.value = launched;
        return intercept && launched;
    }