Skip to content

Commit 753a4d1

Browse files
authored
Merge pull request #206 from OpenIPC/fix/bars-addresses-202
fix: bars hidden again over the video, LAN-only debug addresses, no leaked menu (#202, #203, #205)
2 parents bd2addf + 7741cf0 commit 753a4d1

5 files changed

Lines changed: 240 additions & 23 deletions

File tree

‎android/app/build.gradle‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ android {
2323
applicationId "com.openipc.decoder"
2424
minSdk 21
2525
targetSdk 36
26-
versionCode 36
27-
versionName "2.11"
26+
versionCode 37
27+
versionName "2.12"
2828

2929
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
3030
}

‎android/app/src/main/java/com/openipc/decoder/Decoder.java‎

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,18 @@ public void onCodecChanged(boolean h265, AudioConfig audio) {
325325
FrameLayout.LayoutParams.MATCH_PARENT));
326326

327327
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
328-
WindowInsetsController ctrl = getWindow().getInsetsController();
329-
if (ctrl != null) {
330-
ctrl.hide(WindowInsets.Type.navigationBars());
331-
ctrl.setSystemBarsBehavior(
332-
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
333-
}
334-
} else {
335-
//noinspection deprecation
336-
getWindow().getDecorView().setSystemUiVisibility(
337-
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
328+
// Bars that stay shown while the video has the focus (not the transient ones after
329+
// a swipe, which the app does not see) go away again after a moment (#202)
330+
View decor = getWindow().getDecorView();
331+
decor.setOnApplyWindowInsetsListener((v, insets) -> {
332+
v.removeCallbacks(hideBarsRunnable);
333+
if (hasWindowFocus() && insets.isVisible(WindowInsets.Type.systemBars())) {
334+
v.postDelayed(hideBarsRunnable, BARS_REHIDE_MS);
335+
}
336+
return v.onApplyWindowInsets(insets);
337+
});
338338
}
339+
hideSystemBars();
339340

340341
updateStatus("disconnected");
341342
DebugServer.get().setSource(debugSource);
@@ -506,7 +507,11 @@ private boolean leaveUdpView() {
506507
}
507508

508509
@Override
509-
public void onExit() { finishAndRemoveTask(); }
510+
public void onExit() {
511+
// Not left showing over a finishing activity (#205)
512+
menuManager.dismissAll();
513+
finishAndRemoveTask();
514+
}
510515

511516
/**
512517
* Reset (#171): stops every stream, deletes the stored settings, the key of the encrypted
@@ -920,7 +925,7 @@ private void buildQuadGrid(int size) {
920925
/** Shown over the U cell until its first frame: where to send the stream. */
921926
private TextView udpHintCell() {
922927
TextView hint = new TextView(this);
923-
hint.setText(udpHintText(NetInfo.addresses(), UDP_RTP_PORT));
928+
hint.setText(udpHintText(NetInfo.udpAddresses(), UDP_RTP_PORT));
924929
hint.setTextColor(Color.GRAY);
925930
hint.setTextSize(13);
926931
hint.setGravity(Gravity.CENTER);
@@ -938,7 +943,7 @@ private void refreshUdpHint() {
938943
if (v == null) return;
939944
View overlay = ((View) v.getParent()).findViewWithTag("quad_overlay_" + U_CELL);
940945
if (!(overlay instanceof TextView) || overlay.getVisibility() != View.VISIBLE) return;
941-
String text = udpHintText(NetInfo.addresses(), UDP_RTP_PORT);
946+
String text = udpHintText(NetInfo.udpAddresses(), UDP_RTP_PORT);
942947
TextView hint = (TextView) overlay;
943948
if (!text.contentEquals(hint.getText())) hint.setText(text);
944949
}
@@ -1073,7 +1078,7 @@ private void showQuadOverlay(int i) {
10731078
overlay.setBackgroundColor(Color.BLACK);
10741079
cell.addView(overlay);
10751080
} else if (i == U_CELL) {
1076-
((TextView) overlay).setText(udpHintText(NetInfo.addresses(), UDP_RTP_PORT));
1081+
((TextView) overlay).setText(udpHintText(NetInfo.udpAddresses(), UDP_RTP_PORT));
10771082
}
10781083
overlay.setVisibility(View.VISIBLE);
10791084
overlay.bringToFront();
@@ -1533,7 +1538,7 @@ private Map<String, Object> debugState() {
15331538
if (udpViewShown) {
15341539
Map<String, Object> udp = new LinkedHashMap<>();
15351540
udp.put("port", UDP_RTP_PORT);
1536-
udp.put("addresses", NetInfo.addresses());
1541+
udp.put("addresses", NetInfo.udpAddresses());
15371542
m.put("udp", udp);
15381543
} else if (!quadEnabled) {
15391544
Map<String, Object> single = new LinkedHashMap<>();
@@ -2367,6 +2372,9 @@ private void stopExecutor() {
23672372

23682373
@Override
23692374
protected void onDestroy() {
2375+
// The menu and the dialogs go with the activity, or Android logs them as leaked (#205)
2376+
if (menuManager != null) menuManager.dismissAll();
2377+
getWindow().getDecorView().removeCallbacks(hideBarsRunnable);
23702378
super.onDestroy();
23712379
DebugServer.get().clearSource(debugSource);
23722380
streamProbes.shutdown();
@@ -2387,10 +2395,46 @@ protected void onNewIntent(Intent intent) {
23872395
}
23882396
}
23892397

2398+
/** How long bars that stay shown over the video are left before they are hidden (#202). */
2399+
private static final long BARS_REHIDE_MS = 3000;
2400+
private final Runnable hideBarsRunnable = this::hideSystemBars;
2401+
2402+
/**
2403+
* Full screen over the whole display in every viewing mode (single, grid, U), with no
2404+
* status or navigation bar (#202). A swipe from an edge shows them for a moment only.
2405+
* Android 15 draws apps edge to edge, so bars left shown cover the picture; some systems
2406+
* (an OPPO, #187) bring them back after another window had the focus, so this runs again
2407+
* whenever the activity resumes or gets the focus back: after the menu, a dialog or the
2408+
* WebUI, which keeps the bars and its insets padding (#169, #180).
2409+
*/
2410+
private void hideSystemBars() {
2411+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
2412+
WindowInsetsController ctrl = getWindow().getInsetsController();
2413+
if (ctrl != null) {
2414+
ctrl.setSystemBarsBehavior(
2415+
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
2416+
ctrl.hide(WindowInsets.Type.systemBars());
2417+
}
2418+
} else {
2419+
//noinspection deprecation
2420+
getWindow().getDecorView().setSystemUiVisibility(
2421+
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
2422+
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
2423+
| View.SYSTEM_UI_FLAG_FULLSCREEN);
2424+
}
2425+
}
2426+
2427+
@Override
2428+
public void onWindowFocusChanged(boolean hasFocus) {
2429+
super.onWindowFocusChanged(hasFocus);
2430+
if (hasFocus) hideSystemBars();
2431+
}
2432+
23902433
@Override
23912434
protected void onResume() {
23922435
super.onResume();
23932436
mResumed = true;
2437+
hideSystemBars();
23942438
startStreams();
23952439
updateDebugUrls();
23962440
}

‎android/app/src/main/java/com/openipc/decoder/MenuManager.java‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class MenuManager {
6666
private TextView mWebUiBtn;
6767
TextView[] camButtons;
6868
private Dialog mBrowserDialog;
69+
/** The URL editor or the Reset confirmation while it is shown (#205). */
70+
private Dialog mDialog;
6971
private int mActiveQualityColor = Color.WHITE;
7072

7173
interface Listener {
@@ -130,6 +132,31 @@ void dismissPopup() {
130132

131133
boolean isPopupShowing() { return mMenuPopup != null; }
132134

135+
/**
136+
* The menu, the open dialog and the WebUI closed before the activity finishes: a window
137+
* still showing then is logged as leaked (#205).
138+
*/
139+
void dismissAll() {
140+
PopupWindow popup = mMenuPopup;
141+
mMenuPopup = null;
142+
Dialog dialog = mDialog;
143+
mDialog = null;
144+
try {
145+
if (popup != null && popup.isShowing()) popup.dismiss();
146+
if (dialog != null && dialog.isShowing()) dialog.dismiss();
147+
} catch (Exception e) {
148+
Log.e(TAG, "Error dismissing the menu", e);
149+
}
150+
onPauseDismissBrowser();
151+
}
152+
153+
private void track(Dialog dialog) {
154+
mDialog = dialog;
155+
dialog.setOnDismissListener(d -> {
156+
if (mDialog == d) mDialog = null;
157+
});
158+
}
159+
133160
void showMenu(View anchor, boolean quadEnabled, int mActive, String version,
134161
String gitHash, String[] mHosts) {
135162
boolean single = !quadEnabled && !udpShown;
@@ -556,6 +583,7 @@ void showUrlEditor(int mActive, String[] mHosts, Transport.Mode[] mTransports) {
556583

557584
AlertDialog dialog = builder.create();
558585
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
586+
track(dialog);
559587
dialog.show();
560588
input.requestFocus();
561589
}
@@ -580,6 +608,7 @@ private void confirmReset() {
580608
View cancel = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
581609
if (cancel != null) cancel.requestFocus();
582610
});
611+
track(dialog);
583612
dialog.show();
584613
}
585614

‎android/app/src/main/java/com/openipc/decoder/NetInfo.java‎

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,49 @@
1515
import java.net.NetworkInterface;
1616
import java.util.ArrayList;
1717
import java.util.Collections;
18+
import java.util.LinkedHashMap;
1819
import java.util.List;
20+
import java.util.Locale;
21+
import java.util.Map;
1922

2023
final class NetInfo {
2124
private static final String TAG = "OpenIPCDecoder";
2225

2326
private NetInfo() {}
2427

28+
/** Interface name prefixes of mobile data (#203): never a LAN the debug page answers. */
29+
private static final String[] CELLULAR = {
30+
"rmnet", "rev_rmnet", "ccmni", "v4-", "clat", "seth", "pdp", "wwan"};
31+
2532
/**
26-
* IPv4 addresses of every interface that is up, the ones worth showing to a user who has
27-
* to type one in: no loopback (127.0.0.0/8) and no link-local (169.254.0.0/16) (#87, #96).
33+
* The debug page's addresses (#87, #96): private IPv4 of every interface that is up and
34+
* not mobile data, Wi-Fi first, then Ethernet, then the rest (#203).
2835
*/
2936
static List<String> addresses() {
37+
Map<String, List<InetAddress>> byInterface = new LinkedHashMap<>();
38+
try {
39+
for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
40+
if (!nif.isUp()) continue;
41+
byInterface.put(nif.getName(), Collections.list(nif.getInetAddresses()));
42+
}
43+
} catch (Exception e) {
44+
Log.w(TAG, "No local address: " + e.getMessage());
45+
}
46+
return lanAddresses(byInterface);
47+
}
48+
49+
/**
50+
* Every IPv4 address a pushed RTP stream can arrive on (the UDP receiver listens on all of
51+
* them), mobile data and public ones too, for the U view's hint (#68, Qodo on #206):
52+
* no loopback, link-local or wildcard address.
53+
*/
54+
static List<String> udpAddresses() {
3055
List<String> out = new ArrayList<>();
3156
try {
3257
for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
3358
if (!nif.isUp()) continue;
3459
for (InetAddress a : Collections.list(nif.getInetAddresses())) {
35-
if (isShown(a) && !out.contains(a.getHostAddress())) out.add(a.getHostAddress());
60+
if (isUsable(a) && !out.contains(a.getHostAddress())) out.add(a.getHostAddress());
3661
}
3762
}
3863
} catch (Exception e) {
@@ -41,11 +66,54 @@ static List<String> addresses() {
4166
return out;
4267
}
4368

44-
static boolean isShown(InetAddress a) {
69+
static boolean isUsable(InetAddress a) {
4570
return a instanceof Inet4Address && !a.isLoopbackAddress() && !a.isLinkLocalAddress()
4671
&& !a.isAnyLocalAddress();
4772
}
4873

74+
/**
75+
* The addresses a LAN client can reach the debug server on, from the interfaces in their
76+
* system order (#203): no mobile-data interface and only private IPv4 (isShown), as the
77+
* server answers LAN clients only. Wi-Fi (also a hotspot) first, then Ethernet.
78+
*/
79+
static List<String> lanAddresses(Map<String, List<InetAddress>> byInterface) {
80+
List<String> out = new ArrayList<>();
81+
for (int rank = 0; rank <= 2; rank++) {
82+
for (Map.Entry<String, List<InetAddress>> e : byInterface.entrySet()) {
83+
String name = e.getKey() == null ? "" : e.getKey().toLowerCase(Locale.ROOT);
84+
if (isCellular(name) || rank(name) != rank) continue;
85+
for (InetAddress a : e.getValue()) {
86+
if (isShown(a) && !out.contains(a.getHostAddress())) out.add(a.getHostAddress());
87+
}
88+
}
89+
}
90+
return out;
91+
}
92+
93+
static boolean isCellular(String name) {
94+
for (String prefix : CELLULAR) {
95+
if (name.startsWith(prefix)) return true;
96+
}
97+
return false;
98+
}
99+
100+
/** 0 Wi-Fi, 1 Ethernet, 2 anything else (USB tethering, a bridge ...). */
101+
private static int rank(String name) {
102+
if (name.startsWith("wlan") || name.startsWith("swlan") || name.startsWith("ap")
103+
|| name.startsWith("wifi")) return 0;
104+
if (name.startsWith("eth") || name.startsWith("en")) return 1;
105+
return 2;
106+
}
107+
108+
/**
109+
* An address to show: private IPv4 (10/8, 172.16/12, 192.168/16) — no loopback, no
110+
* link-local and nothing public or carrier-grade NAT, which the debug server never
111+
* answers (#203).
112+
*/
113+
static boolean isShown(InetAddress a) {
114+
return a instanceof Inet4Address && a.isSiteLocalAddress();
115+
}
116+
49117
/**
50118
* A client on the local network: loopback, private IPv4 (10/8, 172.16/12, 192.168/16),
51119
* link-local (169.254/16, fe80::/10) or IPv6 unique-local (fc00::/7). An IPv4 client on a

0 commit comments

Comments
 (0)