[feat] add Android device presence and align status icons with iOS

This commit is contained in:
dijunkun
2026-10-02 02:27:32 +08:00
parent ff706857bd
commit 3b5cbf0735
9 changed files with 270 additions and 10 deletions
@@ -15,6 +15,38 @@ import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
public class ControllerTest {
@Test public void presenceCallbacksRejectDisconnectedAndPreviousSignalingGeneration()throws Exception{
var instrumentation=InstrumentationRegistry.getInstrumentation();
var delivered=new java.util.concurrent.atomic.AtomicInteger();
NativeSession.Listener listener=new NativeSession.Listener(){
public void status(String value){}public void connected(){}public void ended(String reason){}
public void data(JSONObject value){}public void videoSize(int width,int height){}public void clipboard(String value){}public void statistics(RemoteNetworkStatistics.Snapshot value){}
public void presence(JSONObject value){delivered.incrementAndGet();}
};
NativeSession session=new NativeSession(instrumentation.getTargetContext(),"127.0.0.1",1,"","",listener);
Field workerField=NativeSession.class.getDeclaredField("RTC"),requestedField=NativeSession.class.getDeclaredField("presenceRequested");workerField.setAccessible(true);requestedField.setAccessible(true);
ExecutorService worker=(ExecutorService)workerField.get(null);
var event=NativeSession.class.getDeclaredMethod("onNativeEvent",int.class,byte[].class);event.setAccessible(true);
java.util.function.BiConsumer<Integer,String> send=(type,json)->{
try{event.invoke(session,type,json.getBytes(java.nio.charset.StandardCharsets.UTF_8));}catch(Exception error){throw new RuntimeException(error);}
};
try{
send.accept(1,"{\"controller\":false,\"status\":1,\"generation\":1}");
worker.submit(()->{requestedField.setBoolean(session,true);return null;}).get(5,TimeUnit.SECONDS);
send.accept(9,"{\"type\":\"presence\",\"devices\":[],\"generation\":1}");
worker.submit(()->{}).get(5,TimeUnit.SECONDS);instrumentation.waitForIdleSync();assertEquals(1,delivered.get());
send.accept(1,"{\"controller\":false,\"status\":4,\"generation\":2}");
send.accept(9,"{\"type\":\"presence\",\"devices\":[],\"generation\":1}");
send.accept(1,"{\"controller\":false,\"status\":1,\"generation\":3}");
worker.submit(()->{requestedField.setBoolean(session,true);return null;}).get(5,TimeUnit.SECONDS);
send.accept(9,"{\"type\":\"presence\",\"devices\":[],\"generation\":1}");
worker.submit(()->{}).get(5,TimeUnit.SECONDS);instrumentation.waitForIdleSync();assertEquals(1,delivered.get());
send.accept(1,"{\"controller\":false,\"status\":1,\"generation\":3}");
send.accept(9,"{\"type\":\"presence_update\",\"id\":\"123456789\",\"online\":true,\"generation\":3}");
worker.submit(()->{}).get(5,TimeUnit.SECONDS);instrumentation.waitForIdleSync();assertEquals("Repeated login readiness keeps the current subscription",2,delivered.get());
}finally{session.close();worker.submit(()->{}).get(5,TimeUnit.SECONDS);}
}
@Test public void repeatedNativeConnectedEventsNotifyAndInitializeOnlyOnce()throws Exception{
var instrumentation=InstrumentationRegistry.getInstrumentation();
var notifications=new java.util.concurrent.atomic.AtomicInteger();
@@ -0,0 +1,51 @@
package cn.crossdesk.mobile;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Test;
import java.util.ArrayList;
import static org.junit.Assert.*;
public class RecentConnectionPresenceTest {
private static JSONArray records(String... ids)throws Exception{
JSONArray result=new JSONArray();for(String id:ids)result.put(new JSONObject().put("id",id));return result;
}
private static JSONObject device(String id,boolean online)throws Exception{return new JSONObject().put("id",id).put("online",online);}
private static JSONObject snapshot(JSONObject... devices)throws Exception{
JSONArray values=new JSONArray();for(JSONObject device:devices)values.put(device);return new JSONObject().put("type","presence").put("devices",values);
}
private static JSONObject update(String id,boolean online)throws Exception{return device(id,online).put("type","presence_update");}
@Test public void snapshotAndUpdatesOnlyAffectWatchedDevicesAndSortStably()throws Exception{
RecentConnectionPresence presence=new RecentConnectionPresence();JSONArray history=records("111111111","222222222","333333333");
presence.watch(history,0);presence.setConnected(true,0);
presence.receive(update("111111111",true),1);assertFalse(presence.isOnline("111111111",1));
presence.receive(snapshot(device("222222222",true),device("333333333",true),device("999999999",true)),2);
assertFalse(presence.isOnline("999999999",2));assertTrue(presence.isOnline("222222222",2));
JSONArray ordered=presence.ordered(history,2);assertEquals("222222222",ordered.getJSONObject(0).getString("id"));assertEquals("333333333",ordered.getJSONObject(1).getString("id"));
presence.receive(update("222222222",false),3);assertFalse(presence.isOnline("222222222",3));
assertEquals("333333333",presence.ordered(history,3).getJSONObject(0).getString("id"));
presence.receive(snapshot(device("111111111",true)),4);assertFalse("A full snapshot replaces missing devices",presence.isOnline("333333333",4));
}
@Test public void refreshExpiryAndReconnectRequireFreshSnapshot()throws Exception{
RecentConnectionPresence presence=new RecentConnectionPresence();ArrayList<JSONArray> requests=new ArrayList<>();presence.sender=requests::add;
presence.watch(records("111111111"),0);assertTrue(requests.isEmpty());presence.setConnected(true,0);assertEquals(1,requests.size());
presence.receive(snapshot(device("111111111",true)),0);presence.maintain(29_999);assertEquals(1,requests.size());
presence.maintain(30_000);assertEquals(2,requests.size());assertTrue(presence.isOnline("111111111",59_999));
presence.maintain(60_000);assertEquals(3,requests.size());assertFalse(presence.isOnline("111111111",60_000));
presence.receive(update("111111111",true),60_001);assertTrue(presence.isOnline("111111111",60_001));
presence.setConnected(false,60_002);presence.receive(snapshot(device("111111111",true)),60_003);presence.maintain(120_000);assertEquals(3,requests.size());assertFalse(presence.isOnline("111111111",120_000));
presence.setConnected(true,120_001);assertEquals(4,requests.size());presence.receive(update("111111111",true),120_002);assertFalse(presence.isOnline("111111111",120_002));
presence.receive(snapshot(device("111111111",true)),120_003);assertTrue(presence.isOnline("111111111",120_003));
}
@Test public void changedHistoryResubscribesAndMalformedStatesDoNotBecomeOnline()throws Exception{
RecentConnectionPresence presence=new RecentConnectionPresence();ArrayList<JSONArray> requests=new ArrayList<>();presence.sender=requests::add;
presence.watch(records("111111111","111111111"),0);presence.setConnected(true,0);assertEquals(1,requests.get(0).length());
presence.receive(snapshot(device("111111111",true)),1);presence.watch(records("222222222"),2);assertEquals(2,requests.size());assertFalse(presence.isOnline("111111111",2));
presence.receive(snapshot(new JSONObject().put("id","222222222").put("online","true")),3);assertFalse(presence.isOnline("222222222",3));
presence.receive(snapshot(device("222222222",true)),4);presence.receive(new JSONObject().put("type","presence").put("devices","invalid"),5);assertTrue(presence.isOnline("222222222",5));
presence.watch(records(),6);assertEquals(0,requests.get(2).length());assertFalse(presence.isOnline("222222222",6));
}
}