629 lines
26 KiB
Java
629 lines
26 KiB
Java
package com.hyzq.boss;
|
|
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.fail;
|
|
import static org.junit.Assert.assertTrue;
|
|
|
|
import android.app.Dialog;
|
|
import android.app.NotificationManager;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.Looper;
|
|
import android.view.View;
|
|
|
|
import androidx.appcompat.app.AlertDialog;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.robolectric.Robolectric;
|
|
import org.robolectric.RobolectricTestRunner;
|
|
import org.robolectric.RuntimeEnvironment;
|
|
import org.robolectric.Shadows;
|
|
import org.robolectric.annotation.Config;
|
|
import org.robolectric.shadows.ShadowApplication;
|
|
import org.robolectric.shadows.ShadowDialog;
|
|
import org.robolectric.shadows.ShadowNotificationManager;
|
|
import org.robolectric.util.ReflectionHelpers;
|
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.function.BooleanSupplier;
|
|
|
|
@RunWith(RobolectricTestRunner.class)
|
|
@Config(sdk = 34)
|
|
public class ProjectDetailActivityRealtimeTest {
|
|
@Test
|
|
public void matchingProjectMessageEventTriggersReload() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent("project.messages.updated", new JSONObject().put("projectId", "project-1"))
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
waitFor(() -> activity.messageReloadCount == 1);
|
|
assertEquals(0, activity.reloadCount);
|
|
assertEquals(1, activity.messageReloadCount);
|
|
}
|
|
|
|
@Test
|
|
public void unrelatedProjectMessageEventDoesNotTriggerReload() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent("project.messages.updated", new JSONObject().put("projectId", "project-2"))
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
assertEquals(0, activity.reloadCount);
|
|
}
|
|
|
|
@Test
|
|
public void masterAgentTaskEventDoesNotRefreshForDifferentProjectId() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "master-agent")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent("master_agent.task.updated", new JSONObject().put("projectId", "project-2"))
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
assertEquals(0, activity.reloadCount);
|
|
}
|
|
|
|
@Test
|
|
public void distinctRealtimeEventsBackToBackStillReloadMatchingProject() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"conversation.updated",
|
|
new JSONObject().put("projectId", "project-1").put("at", "2026-04-05T10:00:00.000Z")
|
|
)
|
|
)
|
|
);
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"project.messages.updated",
|
|
new JSONObject().put("projectId", "project-1").put("at", "2026-04-05T10:00:00.500Z")
|
|
)
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
waitFor(() -> activity.loadCallCount == 1);
|
|
assertEquals(1, activity.loadCallCount);
|
|
assertEquals(0, activity.messageReloadCount);
|
|
}
|
|
|
|
@Test
|
|
public void matchingProjectContextRiskEventTriggersReload() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"project.context_risk.updated",
|
|
new JSONObject().put("projectId", "project-1").put("deviceId", "mac-studio")
|
|
)
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
waitFor(() -> activity.loadCallCount == 1);
|
|
assertEquals(1, activity.loadCallCount);
|
|
assertEquals(0, activity.messageReloadCount);
|
|
}
|
|
|
|
@Test
|
|
public void duplicateRealtimeEventsWithDifferentAtAreDeduped() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"project.messages.updated",
|
|
new JSONObject().put("projectId", "project-1").put("at", "2026-04-05T10:00:00.000Z")
|
|
)
|
|
)
|
|
);
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"project.messages.updated",
|
|
new JSONObject().put("projectId", "project-1").put("at", "2026-04-05T10:00:00.500Z")
|
|
)
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
waitFor(() -> activity.messageReloadCount == 1);
|
|
assertEquals(0, activity.reloadCount);
|
|
assertEquals(1, activity.messageReloadCount);
|
|
}
|
|
|
|
@Test
|
|
public void dialogGuardInterventionRequiredShowsBlockedSafeActionDialog() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
RecordingDialogGuardApiClient apiClient = new RecordingDialogGuardApiClient();
|
|
ReflectionHelpers.setField(activity, "apiClient", apiClient);
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"desktop.dialog_guard.intervention_required",
|
|
new JSONObject()
|
|
.put("interventionId", "intervention-1")
|
|
.put("dialogId", "dialog-1")
|
|
.put("requestId", "request-1")
|
|
.put("taskId", "task-1")
|
|
.put("deviceId", "mac-studio")
|
|
.put("projectId", "project-1")
|
|
.put("appName", "微信")
|
|
.put("platform", "macos")
|
|
.put("risk", "blocked")
|
|
.put("summary", "微信正在请求读取敏感通讯录权限")
|
|
.put("recommendedAction", "handled_on_device")
|
|
.put("availableActions", new JSONArray()
|
|
.put("allow_once")
|
|
.put("allow_for_device_dialog")
|
|
.put("deny")
|
|
.put("handled_on_device")
|
|
.put("cancel_task"))
|
|
)
|
|
)
|
|
);
|
|
Shadows.shadowOf(activity.getMainLooper()).idle();
|
|
|
|
Dialog latestDialog = ShadowDialog.getLatestDialog();
|
|
assertTrue(latestDialog instanceof AlertDialog);
|
|
AlertDialog dialog = (AlertDialog) latestDialog;
|
|
assertTrue(dialog.isShowing());
|
|
assertTrue(viewTreeContainsText(dialog.getWindow().getDecorView(), "微信"));
|
|
assertTrue(viewTreeContainsText(dialog.getWindow().getDecorView(), "微信正在请求读取敏感通讯录权限"));
|
|
assertTrue(viewTreeContainsText(dialog.getWindow().getDecorView(), "我已在电脑上处理"));
|
|
assertTrue(viewTreeContainsText(dialog.getWindow().getDecorView(), "取消任务"));
|
|
assertFalse(viewTreeContainsText(dialog.getWindow().getDecorView(), "允许本次"));
|
|
assertFalse(viewTreeContainsText(dialog.getWindow().getDecorView(), "当前设备此弹窗允许"));
|
|
|
|
View handledButton = findClickableViewContainingText(dialog.getWindow().getDecorView(), "我已在电脑上处理");
|
|
assertNotNull(handledButton);
|
|
handledButton.performClick();
|
|
waitFor(() -> apiClient.decisionCallCount == 1);
|
|
|
|
assertEquals("intervention-1", apiClient.lastInterventionId);
|
|
assertEquals("handled_on_device", apiClient.lastDecision);
|
|
}
|
|
|
|
@Test
|
|
public void dialogGuardResolvedEventClosesMatchingDialog() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"desktop.dialog_guard.intervention_required",
|
|
new JSONObject()
|
|
.put("interventionId", "intervention-2")
|
|
.put("projectId", "project-1")
|
|
.put("appName", "访达")
|
|
.put("risk", "safe")
|
|
.put("summary", "确认打开下载文件")
|
|
.put("availableActions", new JSONArray().put("allow_once").put("deny"))
|
|
)
|
|
)
|
|
);
|
|
Shadows.shadowOf(activity.getMainLooper()).idle();
|
|
AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
|
|
assertTrue(dialog.isShowing());
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent(
|
|
"desktop.dialog_guard.intervention_resolved",
|
|
new JSONObject()
|
|
.put("interventionId", "intervention-2")
|
|
.put("projectId", "project-1")
|
|
)
|
|
)
|
|
);
|
|
Shadows.shadowOf(activity.getMainLooper()).idle();
|
|
|
|
assertFalse(dialog.isShowing());
|
|
}
|
|
|
|
@Test
|
|
public void openingMasterAgentConversationClearsPendingMasterAgentNotification() throws Exception {
|
|
Context context = RuntimeEnvironment.getApplication();
|
|
BossApplication application = (BossApplication) context.getApplicationContext();
|
|
ShadowApplication.getInstance().grantPermissions(android.Manifest.permission.POST_NOTIFICATIONS);
|
|
ShadowNotificationManager notificationManager = Shadows.shadowOf(
|
|
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)
|
|
);
|
|
application.visibilityTracker().onAppBackgrounded();
|
|
|
|
JSONObject message = new JSONObject()
|
|
.put("id", "master-msg-1")
|
|
.put("sender", "master")
|
|
.put("senderLabel", "主 Agent · gpt-5.4-mini")
|
|
.put("body", "主 Agent 后台回复");
|
|
JSONObject payload = new JSONObject()
|
|
.put("projectId", "master-agent")
|
|
.put("projectMessagesPayload", new JSONObject().put(
|
|
"project",
|
|
new JSONObject().put("messages", new JSONArray().put(message))
|
|
));
|
|
assertTrue(application.notificationRouter().maybeNotifyForRealtimeEvent(
|
|
new BossRealtimeEvent("project.messages.updated", payload)
|
|
));
|
|
assertEquals(1, notificationManager.size());
|
|
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "master-agent")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "主 Agent");
|
|
Robolectric.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
assertEquals(0, notificationManager.size());
|
|
}
|
|
|
|
@Test
|
|
public void burstRealtimeEventsWhileReloadingCoalesceIntoSingleFollowUpReload() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
|
|
activity.blockFirstReload();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent("project.messages.updated", new JSONObject().put("projectId", "project-1"))
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
assertTrue(activity.awaitFirstLoadStarted());
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent("conversation.updated", new JSONObject().put("projectId", "project-1"))
|
|
)
|
|
);
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeEvent",
|
|
ReflectionHelpers.ClassParameter.from(
|
|
BossRealtimeEvent.class,
|
|
new BossRealtimeEvent("master_agent.task.updated", new JSONObject().put("projectId", "project-1"))
|
|
)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
assertEquals(0, activity.loadCallCount);
|
|
assertEquals(1, activity.messageLoadCallCount);
|
|
assertEquals(0, activity.renderCount);
|
|
|
|
activity.releaseFirstLoad();
|
|
waitFor(() -> activity.renderCount == 2 && activity.messageLoadCallCount == 1 && activity.loadCallCount == 1);
|
|
|
|
assertEquals(1, activity.loadCallCount);
|
|
assertEquals(1, activity.messageLoadCallCount);
|
|
assertEquals(2, activity.renderCount);
|
|
}
|
|
|
|
@Test
|
|
public void realtimeDisconnectTriggersImmediateConversationFallbackReload() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.get();
|
|
activity.getSharedPreferences("boss_native_client", android.content.Context.MODE_PRIVATE)
|
|
.edit()
|
|
.putString("session_cookie", "boss_session=test")
|
|
.apply();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"handleRealtimeConnectionChanged",
|
|
ReflectionHelpers.ClassParameter.from(boolean.class, false)
|
|
);
|
|
drainRealtimeDebounce(activity);
|
|
|
|
waitFor(() -> activity.loadCallCount == 1);
|
|
assertEquals(1, activity.loadCallCount);
|
|
}
|
|
|
|
@Test
|
|
public void reloadSnapshotAfterDestroyDoesNotCrashWhenExecutorsAreShutdown() throws Exception {
|
|
Intent intent = new Intent()
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_ID, "project-1")
|
|
.putExtra(ProjectDetailActivity.EXTRA_PROJECT_NAME, "北区试产线");
|
|
TestRealtimeProjectDetailActivity activity = Robolectric
|
|
.buildActivity(TestRealtimeProjectDetailActivity.class, intent)
|
|
.setup()
|
|
.resume()
|
|
.pause()
|
|
.destroy()
|
|
.get();
|
|
|
|
ReflectionHelpers.callInstanceMethod(
|
|
activity,
|
|
"reloadSnapshot",
|
|
ReflectionHelpers.ClassParameter.from(boolean.class, false),
|
|
ReflectionHelpers.ClassParameter.from(boolean.class, false)
|
|
);
|
|
|
|
assertEquals(0, activity.loadCallCount);
|
|
}
|
|
|
|
private static void waitFor(BooleanSupplier condition) throws Exception {
|
|
long deadlineAt = System.currentTimeMillis() + 2_000L;
|
|
while (System.currentTimeMillis() < deadlineAt) {
|
|
Shadows.shadowOf(Looper.getMainLooper()).idle();
|
|
if (condition.getAsBoolean()) {
|
|
return;
|
|
}
|
|
Thread.sleep(20L);
|
|
}
|
|
fail("condition not met before timeout");
|
|
}
|
|
|
|
private static void drainRealtimeDebounce(TestRealtimeProjectDetailActivity activity) {
|
|
Shadows.shadowOf(activity.getMainLooper()).idleFor(350, TimeUnit.MILLISECONDS);
|
|
}
|
|
|
|
private static boolean viewTreeContainsText(View root, String expectedText) {
|
|
if (root instanceof android.widget.TextView) {
|
|
CharSequence text = ((android.widget.TextView) root).getText();
|
|
if (expectedText.contentEquals(text)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (!(root instanceof android.view.ViewGroup)) {
|
|
return false;
|
|
}
|
|
android.view.ViewGroup group = (android.view.ViewGroup) root;
|
|
for (int index = 0; index < group.getChildCount(); index += 1) {
|
|
if (viewTreeContainsText(group.getChildAt(index), expectedText)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static View findClickableViewContainingText(View root, String expectedText) {
|
|
if (root == null) {
|
|
return null;
|
|
}
|
|
if (viewTreeContainsText(root, expectedText) && root.isClickable()) {
|
|
return root;
|
|
}
|
|
if (!(root instanceof android.view.ViewGroup)) {
|
|
return null;
|
|
}
|
|
android.view.ViewGroup group = (android.view.ViewGroup) root;
|
|
for (int index = 0; index < group.getChildCount(); index += 1) {
|
|
View match = findClickableViewContainingText(group.getChildAt(index), expectedText);
|
|
if (match != null) {
|
|
return match;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static class TestRealtimeProjectDetailActivity extends ProjectDetailActivity {
|
|
int reloadCount;
|
|
int messageReloadCount;
|
|
volatile int loadCallCount;
|
|
volatile int messageLoadCallCount;
|
|
volatile int renderCount;
|
|
private CountDownLatch firstLoadStarted;
|
|
private CountDownLatch releaseFirstLoad;
|
|
|
|
@Override
|
|
boolean shouldLoadOnCreate() {
|
|
return false;
|
|
}
|
|
|
|
void blockFirstReload() {
|
|
firstLoadStarted = new CountDownLatch(1);
|
|
releaseFirstLoad = new CountDownLatch(1);
|
|
}
|
|
|
|
boolean awaitFirstLoadStarted() throws InterruptedException {
|
|
return firstLoadStarted != null && firstLoadStarted.await(2, TimeUnit.SECONDS);
|
|
}
|
|
|
|
void releaseFirstLoad() {
|
|
if (releaseFirstLoad != null) {
|
|
releaseFirstLoad.countDown();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void reload() {
|
|
reloadCount += 1;
|
|
super.reload();
|
|
}
|
|
|
|
@Override
|
|
ProjectSnapshot loadProjectMessagesSnapshotForRefresh() throws Exception {
|
|
messageReloadCount += 1;
|
|
messageLoadCallCount += 1;
|
|
if (messageLoadCallCount == 1 && firstLoadStarted != null && releaseFirstLoad != null) {
|
|
firstLoadStarted.countDown();
|
|
releaseFirstLoad.await(2, TimeUnit.SECONDS);
|
|
}
|
|
return new ProjectSnapshot(
|
|
new JSONObject().put(
|
|
"project",
|
|
new JSONObject()
|
|
.put("name", "北区试产线")
|
|
.put("messages", new JSONArray())
|
|
),
|
|
null,
|
|
null
|
|
);
|
|
}
|
|
|
|
@Override
|
|
ProjectSnapshot loadProjectSnapshotForRefresh() throws Exception {
|
|
loadCallCount += 1;
|
|
if (loadCallCount == 1 && firstLoadStarted != null && releaseFirstLoad != null) {
|
|
firstLoadStarted.countDown();
|
|
releaseFirstLoad.await(2, TimeUnit.SECONDS);
|
|
}
|
|
return new ProjectSnapshot(
|
|
new JSONObject().put(
|
|
"project",
|
|
new JSONObject()
|
|
.put("name", "北区试产线")
|
|
.put("messages", new JSONArray())
|
|
),
|
|
null,
|
|
null
|
|
);
|
|
}
|
|
|
|
@Override
|
|
void renderLoadedProjectSnapshot(ProjectSnapshot snapshot) {
|
|
renderCount += 1;
|
|
setRefreshing(false);
|
|
}
|
|
}
|
|
|
|
private static final class RecordingDialogGuardApiClient extends BossApiClient {
|
|
int decisionCallCount;
|
|
String lastInterventionId;
|
|
String lastDecision;
|
|
|
|
RecordingDialogGuardApiClient() {
|
|
super(RuntimeEnvironment.getApplication().getSharedPreferences("dialog_guard_test", Context.MODE_PRIVATE), "https://boss.hyzq.net");
|
|
}
|
|
|
|
@Override
|
|
public ApiResponse decideDialogGuardIntervention(String interventionId, String decision) throws org.json.JSONException {
|
|
decisionCallCount += 1;
|
|
lastInterventionId = interventionId;
|
|
lastDecision = decision;
|
|
return new ApiResponse(200, new JSONObject().put("ok", true));
|
|
}
|
|
}
|
|
}
|