Skip to content

Commit 9d96a77

Browse files
deadYokaiteccheck
authored andcommitted
feat(wear): add NetworkConnectionManager
1 parent 048aa39 commit 9d96a77

6 files changed

Lines changed: 488 additions & 10 deletions

File tree

play-services-wearable/core/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
88

9+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
910
<uses-permission android:name="android.permission.BLUETOOTH" />
1011
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
1112
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

play-services-wearable/core/src/main/java/org/microg/gms/wearable/WearableImpl.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.microg.gms.common.RemoteListenerProxy;
4848
import org.microg.gms.common.Utils;
4949
import org.microg.gms.wearable.bluetooth.BluetoothClient;
50+
import org.microg.gms.wearable.network.NetworkConnectionManager;
5051
import org.microg.gms.wearable.channel.ChannelAssetApiEnum;
5152
import org.microg.gms.wearable.channel.ChannelCallbacks;
5253
import org.microg.gms.wearable.channel.ChannelManager;
@@ -55,7 +56,6 @@
5556
import org.microg.gms.wearable.proto.AppKey;
5657
import org.microg.gms.wearable.proto.AppKeys;
5758
import org.microg.gms.wearable.proto.Connect;
58-
import org.microg.gms.wearable.proto.FetchAsset;
5959
import org.microg.gms.wearable.proto.FilePiece;
6060
import org.microg.gms.wearable.proto.Request;
6161
import org.microg.gms.wearable.proto.RootMessage;
@@ -119,6 +119,8 @@ public class WearableImpl {
119119

120120
private AssetFetcher assetFetcher;
121121

122+
private NetworkConnectionManager networkManager;
123+
122124
public WearableImpl(Context context, NodeDatabaseHelper nodeDatabase, ConfigurationDatabaseHelper configDatabase) {
123125
this.context = context;
124126
this.nodeDatabase = nodeDatabase;
@@ -904,7 +906,15 @@ private void handleBle(ConnectionConfiguration config, boolean enabled) {
904906
}
905907

906908
private void handleNetwork(ConnectionConfiguration config, boolean enabled) {
907-
Log.w(TAG, "Network not implemented");
909+
if (networkManager == null) {
910+
networkManager = new NetworkConnectionManager(context, this);
911+
}
912+
913+
if (enabled) {
914+
networkManager.addConfig(config);
915+
} else {
916+
networkManager.removeConfig(config);
917+
}
908918
}
909919

910920
private void handleLegacy(ConnectionConfiguration config, boolean enabled) {

play-services-wearable/core/src/main/java/org/microg/gms/wearable/bluetooth/NetworkConnectionManager.java

Lines changed: 0 additions & 4 deletions
This file was deleted.

play-services-wearable/core/src/main/java/org/microg/gms/wearable/bluetooth/NetworkConnectionThread.java

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package org.microg.gms.wearable.network;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.content.IntentFilter;
7+
import android.net.ConnectivityManager;
8+
import android.net.NetworkInfo;
9+
import android.util.Log;
10+
11+
import com.google.android.gms.wearable.ConnectionConfiguration;
12+
13+
import org.microg.gms.wearable.WearableImpl;
14+
import org.microg.gms.wearable.proto.RootMessage;
15+
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
19+
public class NetworkConnectionManager implements Cloneable {
20+
private static final String TAG = "GmsWearNetMgr";
21+
22+
private static final long SHUTDOWN_JOIN_TIMEOUT = 5000;
23+
24+
private final Context context;
25+
private final WearableImpl wearable;
26+
27+
private final Map<String, NetworkConnectionThread> threads = new HashMap<>();
28+
private final Map<String, ConnectionConfiguration> configs = new HashMap<>();
29+
30+
private final BroadcastReceiver connectivityReceiver;
31+
private volatile boolean shutdown = false;
32+
33+
public NetworkConnectionManager(Context context, WearableImpl wearable) {
34+
this.context = context;
35+
this.wearable = wearable;
36+
37+
connectivityReceiver = new BroadcastReceiver() {
38+
@Override
39+
public void onReceive(Context context, Intent intent) {
40+
if (!ConnectivityManager.EXTRA_NO_CONNECTIVITY.equals(intent.getAction())) {
41+
return;
42+
}
43+
44+
if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
45+
return;
46+
}
47+
48+
onNetworkAvailable();
49+
}
50+
};
51+
52+
context.registerReceiver(
53+
connectivityReceiver,
54+
new IntentFilter(ConnectivityManager.EXTRA_NO_CONNECTIVITY)
55+
);
56+
57+
Log.d(TAG, "initialised");
58+
}
59+
60+
public synchronized void addConfig(ConnectionConfiguration config) {
61+
if (shutdown) {
62+
Log.w(TAG, "Manager is shut down, ignoring addConfig for " + config.address);
63+
return;
64+
}
65+
validateConfig(config);
66+
67+
String addr = config.address;
68+
configs.put(addr, config);
69+
70+
NetworkConnectionThread existing = threads.get(addr);
71+
if (existing != null) {
72+
if (existing.isAlive() && !existing.isInterrupted()) {
73+
Log.d(TAG, "Thread already active for " + addr + ", triggering retry");
74+
existing.triggerRetry();
75+
} else {
76+
Log.d(TAG, "Replacing dead thread for " + addr);
77+
existing.close();
78+
threads.remove(addr);
79+
startThread(config);
80+
}
81+
return;
82+
}
83+
84+
if (isNetworkAvailable()) {
85+
startThread(config);
86+
} else {
87+
Log.d(TAG, "Network unavailable, deferring connection for " + addr);
88+
}
89+
}
90+
91+
public synchronized void removeConfig(ConnectionConfiguration config) {
92+
if (shutdown) return;
93+
validateConfig(config);
94+
95+
String addr = config.address;
96+
configs.remove(addr);
97+
98+
NetworkConnectionThread t = threads.remove(addr);
99+
if (t != null) {
100+
Log.d(TAG, "Removing thread for " + addr);
101+
t.close();
102+
joinThread(t, addr);
103+
}
104+
}
105+
106+
public synchronized void sendMessage(String address, RootMessage message) {
107+
NetworkConnectionThread t = threads.get(address);
108+
if (t == null || !t.isAlive()) {
109+
throw new IllegalArgumentException("No active connection for " + address);
110+
}
111+
t.sendMessage(message);
112+
}
113+
114+
public synchronized boolean hasConnection(String address) {
115+
NetworkConnectionThread t = threads.get(address);
116+
return t != null && t.isAlive() && !t.isInterrupted();
117+
}
118+
119+
private void onNetworkAvailable() {
120+
synchronized (this) {
121+
if (shutdown) return;
122+
Log.d(TAG, "Network available — checking " + configs.size() + " config(s)");
123+
124+
for (Map.Entry<String, ConnectionConfiguration> entry : configs.entrySet()) {
125+
String addr = entry.getKey();
126+
NetworkConnectionThread t = threads.get(addr);
127+
128+
if (t == null || !t.isAlive()) {
129+
Log.d(TAG, "Starting thread for " + addr + " (network regained)");
130+
if (t != null) threads.remove(addr);
131+
startThread(entry.getValue());
132+
} else {
133+
t.triggerRetry();
134+
}
135+
}
136+
}
137+
}
138+
139+
@SuppressWarnings("deprecation")
140+
private boolean isNetworkAvailable() {
141+
ConnectivityManager cm =
142+
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
143+
if (cm == null) return false;
144+
NetworkInfo info = cm.getActiveNetworkInfo();
145+
return info != null && info.isConnected();
146+
}
147+
148+
public synchronized void close() {
149+
if (shutdown) return;
150+
shutdown = true;
151+
Log.d(TAG, "Shutting down NetworkConnectionManager (" + threads.size() + " thread(s))");
152+
153+
try {
154+
context.unregisterReceiver(connectivityReceiver);
155+
} catch (Exception e) {
156+
Log.w(TAG, "Error unregistering connectivity receiver", e);
157+
}
158+
159+
for (NetworkConnectionThread t : threads.values()) t.close();
160+
for (Map.Entry<String, NetworkConnectionThread> e : threads.entrySet()) {
161+
joinThread(e.getValue(), e.getKey());
162+
}
163+
164+
threads.clear();
165+
configs.clear();
166+
Log.d(TAG, "NetworkConnectionManager shut down");
167+
}
168+
169+
private void startThread(ConnectionConfiguration config) {
170+
NetworkConnectionThread t =
171+
new NetworkConnectionThread(context, config, wearable);
172+
threads.put(config.address, t);
173+
t.start();
174+
Log.d(TAG, "Started NetworkConnectionThread for " + config.address);
175+
}
176+
177+
private static void joinThread(NetworkConnectionThread t, String address) {
178+
try {
179+
t.join(SHUTDOWN_JOIN_TIMEOUT);
180+
if (t.isAlive()) {
181+
Log.w(TAG, "Thread for " + address + " did not stop within "
182+
+ SHUTDOWN_JOIN_TIMEOUT + "ms");
183+
}
184+
} catch (InterruptedException e) {
185+
Thread.currentThread().interrupt();
186+
}
187+
}
188+
189+
private static void validateConfig(ConnectionConfiguration config) {
190+
if (config == null || config.address == null) {
191+
throw new IllegalArgumentException("Config or address is null");
192+
}
193+
if (config.type != WearableImpl.TYPE_NETWORK) {
194+
throw new IllegalArgumentException("Expected TYPE_NETWORK, got type=" + config.type);
195+
}
196+
if (config.role != WearableImpl.ROLE_CLIENT) {
197+
throw new IllegalArgumentException("Expected ROLE_CLIENT, got role=" + config.role);
198+
}
199+
}
200+
201+
202+
}

0 commit comments

Comments
 (0)