Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class FakeJSONData {

// A snapshot of real report data, used for testing.
public static final String REPORT_DATA = "{\"count\":4627301,\"donateOn\":true}";
public static final String REPORT_DATA_FULL = "{\"count\":14074073,\"donateOn\":false,\"serverTime\":1789094984,\"hourlyCalls\":[{\"time\":1789005600,\"count\":106},{\"time\":1789009200,\"count\":109},{\"time\":1789012800,\"count\":125},{\"time\":1789016400,\"count\":63},{\"time\":1789020000,\"count\":25},{\"time\":1789023600,\"count\":5},{\"time\":1789027200,\"count\":61},{\"time\":1789030800,\"count\":50},{\"time\":1789034400,\"count\":97},{\"time\":1789038000,\"count\":291},{\"time\":1789041600,\"count\":290},{\"time\":1789045200,\"count\":211},{\"time\":1789048800,\"count\":211},{\"time\":1789052400,\"count\":315},{\"time\":1789056000,\"count\":163},{\"time\":1789059600,\"count\":175},{\"time\":1789063200,\"count\":241},{\"time\":1789066800,\"count\":150},{\"time\":1789070400,\"count\":251},{\"time\":1789074000,\"count\":270},{\"time\":1789077600,\"count\":293},{\"time\":1789081200,\"count\":211},{\"time\":1789084800,\"count\":221},{\"time\":1789088400,\"count\":136},{\"time\":1789092000,\"count\":62}]}";

/**
* Returns the full issue data as a JSONArray.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.scrollTo;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.a5calls.android.a5calls.FakeJSONData.REPORT_DATA;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;

import android.app.Activity;
import android.app.Instrumentation;
Expand All @@ -27,7 +32,10 @@
import org.junit.Before;
import org.junit.Test;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;

/**
* Instrumentation test for AboutActivity.
Expand Down Expand Up @@ -57,7 +65,7 @@ public void tearDown() {
@Test
public void testCheckRegistrationButton_launchesIntent() {
// Mock the report response to avoid errors in AboutActivity
mHttpStack.setResponseToReturn(new HttpResponse(200, new ArrayList<>(), "{\"count\": 100}".getBytes()));
mHttpStack.setResponseToReturn(new HttpResponse(200, new ArrayList<>(), REPORT_DATA.getBytes()));

aboutScenario = ActivityScenario.launch(AboutActivity.class);

Expand All @@ -73,4 +81,69 @@ public void testCheckRegistrationButton_launchesIntent() {

intended(allOf(hasAction(Intent.ACTION_VIEW), hasData(Uri.parse(expectedUrl))));
}

@Test
public void testCallsToday_calculatesTotalSinceLocalMidnight() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long localMidnightSec = calendar.getTimeInMillis() / 1000;

// Create hourly call count entries:
// 1. 1 hour before local midnight -> should be excluded (500 calls)
// 2. At local midnight -> should be included (150 calls)
// 3. 1 hour after local midnight -> should be included (100 calls)
// Total today count expected = 150 + 100 = 250
long beforeMidnight = localMidnightSec - 3600;
long afterMidnight = localMidnightSec + 3600;

String reportJson = String.format(Locale.US,
"{\"count\":10000,\"donateOn\":false,\"hourlyCalls\":[" +
"{\"time\":%d,\"count\":500}," +
"{\"time\":%d,\"count\":150}," +
"{\"time\":%d,\"count\":100}" +
"]}",
beforeMidnight, localMidnightSec, afterMidnight);

mHttpStack.setResponseToReturn(new HttpResponse(200, new ArrayList<>(), reportJson.getBytes()));

aboutScenario = ActivityScenario.launch(AboutActivity.class);

Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
String expectedCallsTodayText = String.format(
context.getString(R.string.calls_today),
NumberFormat.getNumberInstance(Locale.getDefault()).format(250));

onView(withId(R.id.calls_today))
.perform(scrollTo())
.check(matches(isDisplayed()))
.check(matches(withText(expectedCallsTodayText)));
}

@Test
public void testCallsToday_hiddenWhenCountBelowThreshold() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
long localMidnightSec = calendar.getTimeInMillis() / 1000;

// Only 50 calls after local midnight (below MIN_CALLS_TO_SHOW threshold of 200)
long afterMidnight = localMidnightSec + 3600;

String reportJson = String.format(Locale.US,
"{\"count\":10000,\"donateOn\":false,\"hourlyCalls\":[" +
"{\"time\":%d,\"count\":50}" +
"]}",
afterMidnight);

mHttpStack.setResponseToReturn(new HttpResponse(200, new ArrayList<>(), reportJson.getBytes()));

aboutScenario = ActivityScenario.launch(AboutActivity.class);

onView(withId(R.id.calls_today)).check(matches(not(isDisplayed())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.android.volley.toolbox.HttpResponse;

import org.a5calls.android.a5calls.model.Contact;
import org.a5calls.android.a5calls.model.HourlyCallCount;
import org.a5calls.android.a5calls.model.Issue;
import org.a5calls.android.a5calls.model.Outcome;
import org.json.JSONException;
Expand All @@ -25,6 +26,7 @@

import static org.a5calls.android.a5calls.FakeJSONData.ISSUE_DATA;
import static org.a5calls.android.a5calls.FakeJSONData.REPORT_DATA;
import static org.a5calls.android.a5calls.FakeJSONData.REPORT_DATA_FULL;
import static org.a5calls.android.a5calls.FakeJSONData.REPS_DATA_SUFFIX;
import static org.a5calls.android.a5calls.FakeJSONData.REPS_DATA_NOT_SPLIT_PREFIX;
import static org.a5calls.android.a5calls.FakeJSONData.REPS_DATA_SPLIT_PREFIX;
Expand All @@ -39,6 +41,8 @@ static class TestCallListener implements FiveCallsApi.CallRequestListener {
protected int mCallReported = 0;
protected int mCallCount = 0;
protected boolean mDonateOn = false;
protected long mServerTime = 0;
protected List<HourlyCallCount> mHourlyCounts = null;

@Override
public void onRequestError() {
Expand All @@ -51,9 +55,12 @@ public void onJsonError() {
}

@Override
public void onReportReceived(int count, boolean donateOn) {
public void onReportReceived(int count, boolean donateOn, long serverTime,
List<HourlyCallCount> hourlyCounts) {
mCallCount = count;
mDonateOn = donateOn;
mServerTime = serverTime;
mHourlyCounts = hourlyCounts;
}

@Override
Expand Down Expand Up @@ -153,7 +160,7 @@ public void tearDown() {
}

@Test
public void testGetCallCount() {
public void testGetCallCount_noHourlyCounts() {
byte[] bytes = REPORT_DATA.getBytes();
ArrayList<Header> headers = new ArrayList<>();
headers.add(new Header("Content-Type", "text/json"));
Expand All @@ -174,6 +181,31 @@ public void testGetCallCount() {
mApi.unregisterCallRequestListener(testCallListener);
}

@Test
public void testGetCallCount_hourlyCounts() {
byte[] bytes = REPORT_DATA_FULL.getBytes();
ArrayList<Header> headers = new ArrayList<>();
headers.add(new Header("Content-Type", "text/json"));
HttpResponse response = new HttpResponse(200, headers, bytes);
mHttpStack.setResponseToReturn(response);

TestCallListener testCallListener = new TestCallListener();
mApi.registerCallRequestListener(testCallListener);
mApi.getReport();
waitForHttpRequestComplete();

assertEquals(0, testCallListener.mCallError);
assertEquals(0, testCallListener.mCallJsonError);
assertEquals(0, testCallListener.mCallReported);
assertEquals(14074073, testCallListener.mCallCount);
assertFalse(testCallListener.mDonateOn);

assertEquals(1789094984, testCallListener.mServerTime);
assertEquals(25, testCallListener.mHourlyCounts.size());

mApi.unregisterCallRequestListener(testCallListener);
}

@Test
public void testGetCallCount_serverError() {
mHttpStack.setExceptionToThrow(new IOException("HTTP Stack exception"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@
import org.a5calls.android.a5calls.FiveCallsApplication;
import org.a5calls.android.a5calls.databinding.ActivityAboutBinding;
import org.a5calls.android.a5calls.model.AccountManager;
import org.a5calls.android.a5calls.model.HourlyCallCount;
import org.a5calls.android.a5calls.net.FiveCallsApi;
import org.a5calls.android.a5calls.R;
import org.a5calls.android.a5calls.util.CustomTabsUtil;

import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

Expand All @@ -48,6 +52,8 @@ public class AboutActivity extends AppCompatActivity {
private static final String TAG = "AboutActivity";
public static final String KEY_DISTRICT_ID = "key_district_id";

private static final int MIN_CALLS_TO_SHOW = 200;

private final AccountManager accountManager = AccountManager.Instance;
private FiveCallsApi.CallRequestListener mStatusListener;

Expand Down Expand Up @@ -187,10 +193,34 @@ public void onJsonError() {
}

@Override
public void onReportReceived(int count, boolean donateOn) {
public void onReportReceived(int count, boolean donateOn, long serverTime,
List<HourlyCallCount> hourlyCounts) {
binding.callsToDate.setText(String.format(
getResources().getString(R.string.calls_to_date),
NumberFormat.getNumberInstance(Locale.US).format(count)));
NumberFormat.getNumberInstance(Locale.getDefault()).format(count)));
if (hourlyCounts != null && !hourlyCounts.isEmpty()) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);

Date localMidnight = calendar.getTime();

int todayCount = 0;
for (HourlyCallCount hourlyCount : hourlyCounts) {
if (!hourlyCount.getTime().before(localMidnight)) {
todayCount += hourlyCount.count;
}
}

if (todayCount >= MIN_CALLS_TO_SHOW) {
binding.callsToday.setVisibility(VISIBLE);
binding.callsToday.setText(String.format(
getResources().getString(R.string.calls_today),
NumberFormat.getNumberInstance(Locale.getDefault()).format(todayCount)));
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.a5calls.android.a5calls.model.Contact;
import org.a5calls.android.a5calls.model.CustomizedContactScript;
import org.a5calls.android.a5calls.model.DatabaseHelper;
import org.a5calls.android.a5calls.model.HourlyCallCount;
import org.a5calls.android.a5calls.model.Issue;
import org.a5calls.android.a5calls.model.Outcome;
import org.a5calls.android.a5calls.net.FiveCallsApi;
Expand Down Expand Up @@ -164,7 +165,8 @@ public void onJsonError() {
}

@Override
public void onReportReceived(int count, boolean donateOn) {
public void onReportReceived(int count, boolean donateOn, long serverTime,
List<HourlyCallCount> callCounts) {
// unused
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
import org.a5calls.android.a5calls.R;
import org.a5calls.android.a5calls.databinding.ActivityTutorialBinding;
import org.a5calls.android.a5calls.model.AccountManager;
import org.a5calls.android.a5calls.model.HourlyCallCount;
import org.a5calls.android.a5calls.net.FiveCallsApi;

import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;

/**
Expand Down Expand Up @@ -243,7 +245,8 @@ public void onJsonError() {
}

@Override
public void onReportReceived(int count, boolean donateOn) {
public void onReportReceived(int count, boolean donateOn, long serverTime,
List<HourlyCallCount> hourlyCounts) {
if (!isAdded()) {
// No longer attached to the activity!
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.a5calls.android.a5calls.model;

import android.os.Parcel;
import android.os.Parcelable;

import java.util.Date;

public class HourlyCallCount implements Parcelable {
public long time;
public int count;

protected HourlyCallCount(Parcel in) {
time = in.readLong();
count = in.readInt();
}

public Date getTime() {
return new Date(time * 1000);
}

public static final Creator<HourlyCallCount> CREATOR = new Creator<HourlyCallCount>() {
@Override
public HourlyCallCount createFromParcel(Parcel in) {
return new HourlyCallCount(in);
}

@Override
public HourlyCallCount[] newArray(int size) {
return new HourlyCallCount[size];
}
};

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(time);
dest.writeInt(count);
}
}
Loading