Android中通过按键旋转屏幕

本文详细解析了Android系统中Home键的工作原理,包括短按、长按时的不同行为及背后的实现机制,并介绍了如何处理Home键与其他应用程序间的交互。

Android上的home键是系统直接处理的,功能如下:

1:跳转到Home界面,如果Home进程死掉了,则重新启动Home进程

2:长按5s,弹出选择应用的窗口,可以切换应用.

由于需要,我需要修改这里的部分逻辑,修改后的逻辑不能放出,这里放出我找到的大概地点

view plaincopy to clipboardprint?
public boolean interceptKeyTi(WindowState win, int code, int metaKeys, boolean down,    
            int repeatCount, int flags) {   
        boolean keyguardOn = keyguardOn();   
        if (false) {   
            Log.d(TAG, "interceptKeyTi code=" + code + " down=" + down + " repeatCount="  
                    + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);   
        }   
        // Clear a pending HOME longpress if the user releases Home   
        // TODO: This could probably be inside the next bit of logic, but that code   
        // turned out to be a bit fragile so I'm doing it here explicitly, for now.   
        if ((code == KeyEvent.KEYCODE_HOME) && !down) {   
            mHandler.removeCallbacks(mHomeLongPress);   
        }   
        // If the HOME button is currently being held, then we do special   
        // chording with it.   
        if (mHomePressed) {   
               
            // 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 (code == KeyEvent.KEYCODE_HOME) {   
                if (!down) {   
                    mHomePressed = false;   
                       
                    if ((flags&KeyEvent.FLAG_CANCELED) == 0) {   
                        // 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.)   
                        boolean incomingRinging = false;   
                        try {   
                            ITelephony phoneServ = getPhoneInterface();   
                            if (phoneServ != null) {   
                                incomingRinging = phoneServ.isRinging();   
                            } else {   
                                Log.w(TAG, "Unable to find ITelephony interface");   
                            }   
                        } catch (RemoteException ex) {   
                            Log.w(TAG, "RemoteException from getPhoneInterface()", ex);   
                        }   
           
                        if (incomingRinging) {   
                            Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");   
                        } else {   
                            launchHomeFromHotKey();   
                        }   
                    } else {   
                        Log.i(TAG, "Ignoring HOME; event canceled.");   
                    }   
                }   
            }   
               
            return true;   
        }   
           
        // 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 (code == KeyEvent.KEYCODE_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 false;   
                }   
                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 true;   
                    }   
                }   
            }   
               
            if (down && repeatCount == 0) {   
                if (!keyguardOn) {   
                    mHandler.postDelayed(mHomeLongPress, ViewConfiguration.getGlobalActionKeyTimeout());  
                }   
                mHomePressed = true;   
            }   
            return true;   
        }  
public boolean interceptKeyTi(WindowState win, int code, int metaKeys, boolean down, 
            int repeatCount, int flags) {
        boolean keyguardOn = keyguardOn();
        if (false) {
            Log.d(TAG, "interceptKeyTi code=" + code + " down=" + down + " repeatCount="
                    + repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed);
        }
        // Clear a pending HOME longpress if the user releases Home
        // TODO: This could probably be inside the next bit of logic, but that code
        // turned out to be a bit fragile so I'm doing it here explicitly, for now.
        if ((code == KeyEvent.KEYCODE_HOME) && !down) {
            mHandler.removeCallbacks(mHomeLongPress);
        }
        // If the HOME button is currently being held, then we do special
        // chording with it.
        if (mHomePressed) {
            
            // 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 (code == KeyEvent.KEYCODE_HOME) {
                if (!down) {
                    mHomePressed = false;
                    
                    if ((flags&KeyEvent.FLAG_CANCELED) == 0) {
                        // 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.)
                        boolean incomingRinging = false;
                        try {
                            ITelephony phoneServ = getPhoneInterface();
                            if (phoneServ != null) {
                                incomingRinging = phoneServ.isRinging();
                            } else {
                                Log.w(TAG, "Unable to find ITelephony interface");
                            }
                        } catch (RemoteException ex) {
                            Log.w(TAG, "RemoteException from getPhoneInterface()", ex);
                        }
        
                        if (incomingRinging) {
                            Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
                        } else {
                            launchHomeFromHotKey();
                        }
                    } else {
                        Log.i(TAG, "Ignoring HOME; event canceled.");
                    }
                }
            }
            
            return true;
        }
        
        // 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 (code == KeyEvent.KEYCODE_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 false;
                }
                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 true;
                    }
                }
            }
            
            if (down && repeatCount == 0) {
                if (!keyguardOn) {
                    mHandler.postDelayed(mHomeLongPress, ViewConfiguration.getGlobalActionKeyTimeout());
                }
                mHomePressed = true;
            }
            return true;
        }

这里贴出一些代码,是在 frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.Java 文包括件里的,主要是处理特殊按键的,里面涵盖来android中的特殊按键,还包括Search和其他特殊键

对于Home键的逻辑,大致的分析是:

1 : 如果用户按下Home键,则开始一个计时器,具体是里面的mHomeLongPress对象,如果超过5s,则弹出一个切换app的窗口

2 : 用户松开Home键,则视为单机时间,现把系统中的一些浮动的模态窗口关闭,具体的代码是 :

sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);

3: 关闭模态窗口后,启动或者切换Home应用,代码:

startDockOrHome

我自己添加的代码这里不能放出,有这样的需求可以在这里手动加入.

<think> 我们正在讨论如何通过adb shell获取Android屏幕上虚拟按键的坐标。根据之前的对话,我们已经知道: 1. 开启开发者选项中的“指针位置”功能可以在屏幕上显示触摸点的坐标,但这需要手动操作。 2. 使用`adb shell dumpsys input`可以获取输入系统的信息,但需要分析其中的内容才能得到虚拟按键的坐标。 现在,用户再次询问使用adb shell获取虚拟按键坐标的方法。根据引用内容,我们注意到: 引用[2]中提到,在MonkeyScript中可以使用DispatchTrackball等事件,并且需要坐标。同时,引用[2]中也提到了开启指针位置功能。 但是,我们仍然需要一个直接通过adb命令获取坐标的方法。然而,虚拟按键(如返回键、主页键、最近任务键)是系统导航栏的一部分,它们的坐标信息通常由系统UI管理。 因此,我们可以尝试通过dumpsys系统UI服务来获取导航栏的信息,从而推断虚拟按键的位置。 具体步骤如下: 1. 使用命令:`adb shell dumpsys window displays` 这个命令会输出屏幕的显示信息,包括屏幕的分辨率。虚拟按键区域(导航栏)通常位于屏幕底部(或侧边),我们可以得到屏幕的宽高。 2. 使用命令:`adb shell dumpsys window` 在输出中搜索“mStable”或“mUnrestricted”等字样,找到内容区域(Content区域)和整个显示区域(Display区域)。导航栏的高度(或宽度)就是整个显示区域减去内容区域。 3. 使用命令:`adb shell dumpsys activity` 或者更具体地,`adb shell dumpsys activity containers`,这里可能会显示当前窗口的边界。 4. 使用命令:`adb shell dumpsys SystemUI` 系统UI服务管理导航栏。在输出中搜索“NavigationBar”相关信息,可能会包含导航栏视图的边界(例如,一个矩形区域)。 但是,请注意,不同Android版本和不同厂商的定制系统输出可能不同。 例如,在Android原生系统中,我们可以尝试: ```bash adb shell dumpsys window | grep -E 'Display|mCurrentFocus|mStable' ``` 或者,更具体地查找导航栏: ```bash adb shell dumpsys window | grep mNavigationBar ``` 如果找到类似这样的行: mNavigationBar=com.android.systemui.navigationbar.NavigationBarView{... ... left=0 top=1184 right=1080 bottom=1200} 那么,这个矩形区域(left, top, right, bottom)就是导航栏的位置。虚拟按键就在这个区域内。 但是,虚拟按键可能有多个(返回、主页、最近任务),它们通常是等间距排列在导航栏中。我们可以通过计算得到每个按键的中心坐标。 例如,假设导航栏的矩形是 (0, 1184, 1080, 1200),那么高度是16像素(bottom-top)。虚拟按键通常是水平排列,所以我们可以将导航栏水平分成三个部分(三个按键),每个部分的中心坐标计算如下: - 返回键:x = 1080/6 = 180, y = (1184+1200)/2 = 1192 - 主页键:x = 1080/2 = 540, y = 1192 - 最近任务键:x = 1080*5/6 = 900, y = 1192 但是,这是理想情况,实际布局可能有所不同(比如有的系统只有两个按键,或者有额外的按键)。因此,我们需要具体分析导航栏的布局。 另外,在Android 10及更高版本中,手势导航可能取代了虚拟按键,此时导航栏区域可能很小甚至没有(只有手势条)。 因此,步骤总结如下: 1. 通过`adb shell dumpsys window`获取导航栏的边界矩形。 2. 解析出矩形的坐标(left, top, right, bottom)。 3. 根据导航栏内按键的布局(通常从左到右等间距排列),计算每个按键的中心坐标。 但是,需要注意的是,虚拟按键的坐标是相对于屏幕的,且原点在屏幕左上角。 如果无法直接获取导航栏的矩形,还可以尝试获取内容区域和整个显示区域,然后计算导航栏的位置。例如: 整个显示区域高度为1920,内容区域高度为1904(即状态栏高度24+导航栏高度?),那么导航栏高度为1920-1904=16?但实际上,状态栏和导航栏是分开的。 更准确的做法是: - 整个显示区域(Display):通过`adb shell dumpsys window displays`得到。 - 内容区域(Content区域):在`dumpsys window`的输出中查找mStableInsets或mUnrestrictedScreen等。 例如,输出可能包含: mStableInsets=Rect(0, 50, 0, 100) // 上、左、下、右的稳定内边距(非内容区域) mUnrestrictedScreen=Rect(0,0,1080,1920) 那么,底部的稳定内边距(100)很可能就是导航栏的高度。此时,导航栏的矩形区域为: left=0, top=1920-100, right=1080, bottom=1920 然后,同样按照等分的方法计算按键坐标。 然而,以上方法需要根据具体输出进行解析。由于不同设备差异较大,可能无法保证通用性。 因此,回答用户的问题: 目前,没有直接获取虚拟按键坐标的adb命令。但可以通过解析`adb shell dumpsys window`或`adb shell dumpsys SystemUI`的输出,间接计算出虚拟按键的坐标。 具体操作步骤(以获取底部导航栏为例): 1. 执行`adb shell dumpsys window`,查找导航栏的矩形区域(如搜索NavigationBar)或屏幕的稳定内边距(mStableInsets)。 2. 如果找到导航栏的矩形,则根据矩形计算。 3. 如果找到稳定内边距,则根据整个显示区域和稳定内边距计算出导航栏的矩形。 4. 假设导航栏内按键水平均匀分布,计算每个按键的中心坐标。 示例命令解析: 步骤1:获取整个显示区域 adb shell dumpsys window displays 输出示例: Display: mDisplayId=0 init=1080x1920 420dpi cur=1080x1920 app=1080x1794 ... 这里,app区域高度为1794,那么状态栏和导航栏一共占1920-1794=126像素。但不知道导航栏具体多少。 步骤2:获取稳定内边距 adb shell dumpsys window | grep mStableInsets 输出示例: mStableInsets=Rect(0, 50, 0, 100) 这里,底部内边距为100,所以导航栏高度为100。那么导航栏的矩形为:left=0, top=1920-100=1820, right=1080, bottom=1920。 步骤3:计算按键坐标。假设有三个按键(返回、主页、最近任务),则: 每个按键区域宽度 = 1080 / 3 = 360 返回键中心:x=180, y=1820+50=1870(这里50是导航栏高度的一半?实际上,中心y坐标应该是(1820+1920)/2=1870) 主页键中心:x=540, y=1870 最近任务键中心:x=900, y=1870 注意:这个计算是假设的,实际按键可能不是均匀分布,或者有额外的区域(如手势区域)。而且,不同设备可能不同。 因此,如果用户需要精确获取,可能需要查看系统UI的布局文件,但这需要root权限。 鉴于以上复杂性,如果只是为了自动化测试,可以考虑使用其他工具,如uiautomator,它可以获取界面元素(包括导航栏)的坐标。 使用uiautomator的方法: 1. 确保设备API级别>=16 2. 执行命令:`adb shell uiautomator dump`(将当前界面布局保存到/sdcard/window_dump.xml) 3. 然后使用`adb pull /sdcard/window_dump.xml`将文件拉取到本地 4. 在xml文件中,搜索导航栏的控件,获取其边界(bounds)属性。例如: <node ... resource-id="com.android.systemui:id/navigation_bar" bounds="[0,1870][1080,1920]">...</node> 然后,再在这个节点下查找具体的按键,如返回键: <node ... resource-id="com.android.systemui:id/back" bounds="[30,1870][150,1920]">...</node> 这样,我们就可以计算出返回键的中心坐标:x=(30+150)/2=90, y=(1870+1920)/2=1895 这种方法更为准确,但需要额外的步骤(拉取文件并解析xml)。 总结: 如果没有root权限,且设备支持uiautomator,则推荐使用uiautomator dump的方法获取虚拟按键的坐标。 如果用户坚持使用dumpsys window,则需要根据输出进行解析,但这种方法可能因设备而异。 因此,我们给出两种方法: 方法一:通过dumpsys window获取(系统级,但可能不准确) 方法二:通过uiautomator dump获取(更准确,但需要文件操作) 下面给出方法二的详细步骤: 1. 执行命令: adb shell uiautomator dump --compressed /sdcard/window_dump.xml 2. 将文件从设备拉取到本地: adb pull /sdcard/window_dump.xml 3. 解析window_dump.xml文件,查找导航栏中的按键节点(根据resource-id),然后解析bounds属性。 在Android系统中,常见的虚拟按键资源ID如下(可能因系统版本和厂商定制而不同): 返回键:com.android.systemui:id/back 或 com.android.systemui:id/nav_btn_back 主页键:com.android.systemui:id/home 或 com.android.systemui:id/nav_btn_home 最近任务键:com.android.systemui:id/recent_apps 或 com.android.systemui:id/nav_btn_recent 注意:有些设备可能使用手势导航,没有可见的虚拟按键。 最后,如果用户需要动态获取(比如在脚本中),可以使用: adb shell uiautomator dump /sdcard/window_dump.xml && adb shell cat /sdcard/window_dump.xml 然后通过grep等工具解析xml(但xml在设备上是单行文本,建议拉取到本地解析)。 相关问题: 1. 如何在没有root权限的情况下,通过adb命令获取虚拟按键的坐标? 2. 使用uiautomator dump获取布局信息时,如何快速解析出特定虚拟按键的坐标? 3. 不同设备(如华为、小米等)的虚拟按键资源ID有何不同? 4. 如果设备使用了手势导航,如何获取手势区域的坐标? </think> 要通过 `adb shell` 获取 Android 屏幕上虚拟按键(导航栏)的坐标,可采用以下两种方法: --- ### **方法一:通过系统 UI 服务解析导航栏边界** 1. **获取导航栏边界信息**: ```bash adb shell dumpsys window | grep -i 'mNavigationBar' ``` - 输出示例:`mNavigationBar=com.android.systemui.navigationbar.NavigationBarView{... left=0 top=1872 right=1080 bottom=1920}` - **关键参数**: - `top`:导航栏顶部 Y 坐标 - `bottom`:导航栏底部 Y 坐标 - `right`:屏幕宽度 X 坐标 2. **计算虚拟按键坐标**: - 虚拟按键通常在导航栏内**水平均匀分布**(如返回键、主页键、多任务键)。 - 以三键导航栏为例: - **返回键中心坐标**: $$(x_1, y) = \left( \frac{\text{right}}{6}, \frac{\text{top} + \text{bottom}}{2} \right)$$ - **主页键中心坐标**: $$(x_2, y) = \left( \frac{\text{right}}{2}, \frac{\text{top} + \text{bottom}}{2} \right)$$ - **多任务键中心坐标**: $$(x_3, y) = \left( \frac{5 \times \text{right}}{6}, \frac{\text{top} + \text{bottom}}{2} \right)$$ > 示例:若输出为 `top=1872, bottom=1920, right=1080`,则主页键坐标为 `(540, 1896)`。 --- ### **方法二:通过 UI Automator 获取精确坐标** 1. **导出当前界面布局**: ```bash adb shell uiautomator dump /sdcard/window_dump.xml adb pull /sdcard/window_dump.xml ``` 2. **解析导航栏按键坐标**: - 打开 `window_dump.xml`,搜索导航栏控件: - **资源标识符**(常见系统 UI): - 返回键:`com.android.systemui:id/back` - 主页键:`com.android.systemui:id/home` - 多任务键:`com.android.systemui:id/recent_apps` - 输出示例: ```xml <node ... resource-id="com.android.systemui:id/home" bounds="[480,1872][600,1920]"/> ``` - **坐标解析**: - `bounds="[left,top][right,bottom]"` - 中心坐标:$$ \left( \frac{\text{left} + \text{right}}{2}, \frac{\text{top} + \text{bottom}}{2} \right) $$ --- ### **注意事项** 1. **设备差异**: - 导航栏位置可能位于屏幕**底部**(常见)或**侧边**(如三星设备)。 - 部分厂商定制 ROM 会修改资源 ID(需检查实际 XML 中的标识符)[^1]。 2. **手势导航**: - 若启用手势操作(无虚拟按键),导航栏高度可能为 0,需通过 `adb shell dumpsys window | grep mGestureHint` 获取手势条位置。 3. **动态分辨率**: - 屏幕旋转或分辨率变更后需重新执行命令获取最新坐标。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值