android HOME點擊事件的獲取

 首先聲明我是做系統開發的(高通平臺),所以下面介紹的方法並不適合應用開發者。

    最經有個需求要屏蔽HOME按鍵返回桌面並且實現自己的功能,發現以前的方式報錯用不了,上網搜索了一下,發現都是抄來抄去基本是無用的。網上的方法不外乎這幾種:

  第一, 大家最常用的重寫onAttachedToWindow()方法,然後在HOME點擊事件KeyEvent.KEYCODE_HOME中做自己想做的事情,但是這個方法google處於安全考慮在android2.3.3之後就不支持了。

  第二, 抓取系統log日誌,判斷有沒有打印“android.intent.category.HOME”信息來獲得是否按下了HOME按鍵, 這樣做就算代碼知道是按下HOME鍵,那怎麼阻止返回桌面呢? 這隻能是截獲HOME按鍵,並不能屏蔽它返回桌面的功能。

 第三, 修改framework源碼,在PhoneWindowManager中處理HOME按鍵的地方發送一個消息,然後在上層應用中捕獲這個消息,這和上面是一樣的,只能截獲HOME按鍵,並不能阻止返回桌面的動作。

 第四, 在setContentView之前getWindow().setFlags(FLAG_HOMEKEY_DISPATCHED, FLAG_HOMEKEY_DISPATCHED); 這個FLAG_HOMEKEY_DISPATCHED其實是個標誌位,是個常量,值爲0x80000000, 這個方法確實可以,但僅限於MTK(聯發科)平臺的系統,因爲MTK自己實現了一套機制來規避HOME按鍵,而其它的平臺,如高通、博通、展迅的代碼中是沒有加這個屬性的,所以也不行。

 還有極少數思路很極端的方式,看上去都覺得很繁瑣,根本沒耐心細看。

 現在介紹我的思路,首先還是複寫onAttachedToWindow()方法,具體代碼是在activity中加入這一段:

@Override
public void onAttachedToWindow() {
        this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
        super.onAttachedToWindow();

    }

   結果當然是一進入這個activity就報錯,logcat查看錯誤信息,報的錯誤是"Window type can not be changed after the window is added.",發現是WindowManagerService.java中報的錯,源碼位置frameworks/base/services/java/com/android/server/wm/WindowManagerService.java,具體代碼段是relayoutWindow方法中判斷窗口類型的時候報錯:

  if (attrs != null) {
                if (win.mAttrs.type != attrs.type) {
                    throw new IllegalArgumentException(
                            "Window type can not be changed after the window is added.");
                }
                flagChanges = win.mAttrs.flags ^= attrs.flags;
                attrChanges = win.mAttrs.copyFrom(attrs);
                if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
                        | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
                    win.mLayoutNeeded = true;
                }
     }

  剛纔說了,google可能是出於安全原因不能讓你把窗口類型設爲WindowManager.LayoutParams.TYPE_KEYGUARD了, 設置了就報錯,要屏蔽這個錯誤只需要把if (win.mAttrs.type != attrs.type) {
                    throw new IllegalArgumentException(
                            "Window type can not be changed after the window is added.");
                }

註釋掉就行了,再次運行就不會報錯了,但是按HOME鍵還是返回桌面。繼續看分發HOME按鍵事件的代碼,源碼位置frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中的if (keyCode == KeyEvent.KEYCODE_HOME) {...}中,這裏就是處理上層用戶按下HOME鍵的代碼,在裏面會看到// If a system window has focus, then it doesn't make sense
            // right now to interact with applications.
            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
            if (attrs != null) {
                final int type = attrs.type;
                if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
                        || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
                    // the "app" is keyguard, so give it the key
                    return 0;
                }
                final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
                for (int i=0; i<typeCount; i++) {
                    if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
                        // don't do anything, but also don't pass it to the app
                        return -1;
                    }
                }
            }

這一段代碼,意思就是如果設置了窗口類型爲 WindowManager.LayoutParams.TYPE_KEYGUARD(值爲2004) 或者WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,那就return 0,交給用戶自己處理,不返回桌面,否則返回-1,返回桌面。通過打log發現代碼確實是return 0,但還是返回桌面,後來才發現還有方法在返回桌面:/**
     * A home key -> launch home action was detected.  Take the appropriate action
     * given the situation with the keyguard.
     */
    void launchHomeFromHotKey() {
        if (mKeyguardMediator != null && mKeyguardMediator.isShowingAndNotHidden()) {
            // don't launch home if keyguard showing
        } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
            // when in keyguard restricted mode, must first verify unlock
            // before launching home
            mKeyguardMediator.verifyUnlock(new OnKeyguardExitResult() {
                public void onKeyguardExitResult(boolean success) {
                    if (success) {
                        try {
                            ActivityManagerNative.getDefault().stopAppSwitches();
                        } catch (RemoteException e) {
                        }
                        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                        startDockOrHome();
                    }
                }
            });
        } else {
            // no keyguard stuff to worry about, just launch home!
            try {
                ActivityManagerNative.getDefault().stopAppSwitches();
            } catch (RemoteException e) {
            }
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
            startDockOrHome();
        }
    }

不知道高通怎麼改的,就算return 0也會執行這個方法,也會返回桌面,因爲這個方法在判斷窗口類型前面調用,現在要做的就簡單了,只需要加個判斷,就可以屏蔽返回HOME了:

boolean isGoHome = true;
                WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
                if (attrs != null) {
                    final int type = attrs.type;
                    if (type == WindowManager.LayoutParams.TYPE_KEYGUARD) {
                        isGoHome = false;
                    }
                }
                // Go home!
                if (isGoHome) {
                    launchHomeFromHotKey();
                }

整個HOME按鍵的代碼段:

// First we always handle the home key here, so applications
        // can never break it, although if keyguard is on, we do let
        // it handle it, because that gives us the correct 5 second
        // timeout.
        if (keyCode == KeyEvent.KEYCODE_HOME) {

            // If we have released the home key, and didn't do anything else
            // while it was pressed, then it is time to go home!
            if (!down) {
                cancelPreloadRecentApps();
                mHomePressed = false;
                if (mHomeConsumed) {
                    mHomeConsumed = false;
                    return -1;
                }

                if (canceled) {
                    Log.i(TAG, "Ignoring HOME; event canceled.");
                    return -1;
                }

                // If an incoming call is ringing, HOME is totally disabled.
                // (The user is already on the InCallScreen at this point,
                // and his ONLY options are to answer or reject the call.)
                try {
                    ITelephony telephonyService = getTelephonyService();
                    if (telephonyService != null && telephonyService.isRinging()) {
                        Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
                        return -1;
                    }
                } catch (RemoteException ex) {
                    Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
                }

                // Delay handling home if a double-tap is possible.
                if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
                    mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
                    mHomeDoubleTapPending = true;
                    mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                            ViewConfiguration.getDoubleTapTimeout());
                    return -1;
                }

                boolean isGoHome = true;
                WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
                if (attrs != null) {
                    final int type = attrs.type;
                    if (type == WindowManager.LayoutParams.TYPE_KEYGUARD) {
                        isGoHome = false;
                    }
                }
                // Go home!
                if (isGoHome) {
                    launchHomeFromHotKey();
                }
                return -1;
            }

            // If a system window has focus, then it doesn't make sense
            // right now to interact with applications.
            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
            if (attrs != null) {
                final int type = attrs.type;
                if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
                        || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
                    // the "app" is keyguard, so give it the key
                    return 0;
                }
                final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
                for (int i=0; i<typeCount; i++) {
                    if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
                        // don't do anything, but also don't pass it to the app
                        return -1;
                    }
                }
            }

            // Remember that home is pressed and handle special actions.
            if (repeatCount == 0) {
                mHomePressed = true;
                if (mHomeDoubleTapPending) {
                    mHomeDoubleTapPending = false;
                    mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
                    handleDoubleTapOnHome();
                } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
                        || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
                    preloadRecentApps();
                }
            } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                if (!keyguardOn) {
                    handleLongPressOnHome();
                }
            }
            return -1;
        }


至此就可以屏蔽HOME按鍵了,在需在上層應用中複寫onAttachedToWindow()方法即可,然後

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_HOME:
// 想實現的功能

                break;
        }
        return true;
    }


至此分析完畢,如果要屏蔽HOME鍵返回桌面不修改源碼是不行的,至少上面介紹的那些方法是不行的(MTK除外),所以有這個需求的童鞋找方法的時候就不要浪費時間去看那些抄來抄去的帖子了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章