diff --git a/LABORATORY.md b/LABORATORY.md index 4a3fc98c..14c0a259 100644 --- a/LABORATORY.md +++ b/LABORATORY.md @@ -38,7 +38,7 @@ Two identical boxes with the same firmware (checked 2026-10-13): | | Aceline-1 | Aceline-2 | |---|---|---| -| Address | `172.17.32.144:5555` (Ethernet); Wi‑Fi is also up as `172.17.32.146` | `172.17.32.145:5555` (Ethernet) | +| Address | `172.17.32.144:5555` (Ethernet; Wi‑Fi switched off) | `172.17.32.145:5555` (Ethernet) | | Display | **none** (headless): only `screencap`, the debug page and the logs show what is on screen | **monitor** Philips 226VL over HDMI (HDMI audio active); for checks that need a real picture or a person looking | | Serial | `KFB442005119` | `KFB442001288` | | Extra apps | Yandex Browser TV, Faceter, Record Radio | VK TV, Wink, VLC, Nova; autostart `com.oranth.atvapplist` | @@ -254,6 +254,7 @@ The script uninstalls first. To test an upgrade that keeps the settings, use `$A | UDP socket leak | `$ADB shell cat /proc/$(pidof com.openipc.decoder)/net/udp`, count lines before and after | | CPU per thread | `$ADB shell top -H -p ` (a spinning `rtsp-audio` thread was found this way) | | Screen off / on | `input keyevent KEYCODE_SLEEP` / `KEYCODE_WAKEUP` | +| Menu with a remote (#91) | `input keyevent KEYCODE_DPAD_CENTER` opens the menu with the focus on the active item (slot, Q or U); `DPAD_RIGHT` ×n, `DPAD_DOWN` (Exit), `DPAD_UP`, then `DPAD_CENTER` must switch to that slot; `KEYCODE_BACK` closes it. The focused item: `uiautomator dump /sdcard/m.xml` and look for `focused="true"`. Check in single view and multi-view | | 4:3 screen | `wm size 960x720`, back with `wm size reset` | | Audio playback (sound cannot be heard remotely) | `dumpsys media.audio_flinger`: server position of the app's track; frames/s ≈ sample rate, no underruns | | Screenshot | `$ADB exec-out screencap -p > shot.png`; long-press screenshot in the app saves to `Pictures/OpenIPC/` | diff --git a/android/app/src/main/java/com/openipc/decoder/MenuManager.java b/android/app/src/main/java/com/openipc/decoder/MenuManager.java index 6560a2a0..b3bd7d8a 100644 --- a/android/app/src/main/java/com/openipc/decoder/MenuManager.java +++ b/android/app/src/main/java/com/openipc/decoder/MenuManager.java @@ -53,6 +53,8 @@ class MenuManager { /** The UDP RTP view is shown: neither a slot nor multi-view is active. */ private boolean udpShown; private boolean mMenuIsCached; + /** The item a remote starts from: the active slot, Q or U (#91). */ + private TextView mFocusItem; private TextView mSettingsBtn; private TextView mWebUiBtn; TextView[] camButtons; @@ -121,7 +123,8 @@ void showMenu(View anchor, boolean quadEnabled, int mActive, String version, int margin = dp(12); mMenuPopup.showAtLocation(anchor, Gravity.TOP | Gravity.START, margin, margin); mMenuPopup.setOnDismissListener(() -> mMenuPopup = null); - focusForKeys(mMenuLayout); + mFocusItem = focusItem(quadEnabled, mActive); + focusForKeys(mMenuLayout, mFocusItem); return; } @@ -134,6 +137,15 @@ public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_UP) dismissPopup(); return true; } + // While the layout itself has the focus (opened by touch, #65), a D-pad key + // finds no item: they all lie inside it, not beside it. The first key moves + // the focus to the active item instead (#91) + if (findFocus() == this && isNavigationKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN) { + TextView item = mFocusItem; + // Consumed only if the focus really moved; otherwise the key goes on + if (item != null && item.requestFocus()) return true; + } return super.dispatchKeyEvent(event); } }; @@ -146,7 +158,6 @@ public boolean dispatchKeyEvent(KeyEvent event) { popup.showAtLocation(anchor, Gravity.TOP | Gravity.START, margin, margin); mMenuPopup = popup; popup.setOnDismissListener(() -> mMenuPopup = null); - focusForKeys(layout); LinearLayout camRow = new LinearLayout(activity); camRow.setOrientation(LinearLayout.HORIZONTAL); @@ -256,6 +267,8 @@ public boolean dispatchKeyEvent(KeyEvent event) { mMenuLayout = layout; mMenuIsCached = true; + mFocusItem = focusItem(quadEnabled, mActive); + focusForKeys(layout, mFocusItem); } private void updateCachedMenu(boolean quadEnabled, int mActive, String[] mHosts) { @@ -290,11 +303,37 @@ private void updateCachedMenu(boolean quadEnabled, int mActive, String[] mHosts) /** * A menu opened by touch has no focused view, so keys never reach the layout and Escape - * did not close it. In touch mode the layout itself takes focus; with a remote, focus - * stays on the menu items as before. + * did not close it. In touch mode the layout itself takes focus (#65); opened with a + * remote, the active item does (#91). */ - private static void focusForKeys(View layout) { - if (layout.isInTouchMode()) layout.requestFocus(); + private static void focusForKeys(View layout, View item) { + if (layout.isInTouchMode() || item == null) layout.requestFocus(); + else item.requestFocus(); + } + + /** The highlighted item of the slot row: the active slot, Q in multi-view, U. */ + private TextView focusItem(boolean quadEnabled, int mActive) { + if (udpShown && mCachedUdpBtn != null) return mCachedUdpBtn; + if (quadEnabled && mCachedQuadBtn != null) return mCachedQuadBtn; + if (camButtons != null && mActive >= 0 && mActive < camButtons.length) { + return camButtons[mActive]; + } + return null; + } + + static boolean isNavigationKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_TAB: + return true; + default: + return false; + } } private TextView createItem(String title) { @@ -303,6 +342,8 @@ private TextView createItem(String title) { text.setPadding(dp(8), dp(6), dp(8), dp(6)); text.setTextColor(Color.WHITE); text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); + // Reachable with a remote (#91); not only by the clickable default of newer Androids + text.setFocusable(true); focusChange(text); return text; }