jenkins-bot has submitted this change and it was merged.

Change subject: Announcement data client.
......................................................................


Announcement data client.

Includes logic to conditionally show announcements based on country code
and start/end time.

Bug: T151085
Change-Id: Ibbfcbcbbcf000a03515af83168e06e307c43b5a4
---
M app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
A app/src/main/java/org/wikipedia/feed/announcement/Announcement.java
A app/src/main/java/org/wikipedia/feed/announcement/AnnouncementClient.java
A app/src/main/java/org/wikipedia/feed/announcement/AnnouncementList.java
A app/src/test/java/org/wikipedia/feed/announcement/AnnouncementClientTest.java
A app/src/test/res/raw/announce_2016_11_21.json
6 files changed, 460 insertions(+), 0 deletions(-)

Approvals:
  jenkins-bot: Verified
  Niedzielski: Looks good to me, approved



diff --git a/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java 
b/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
index 251f297..e8a850a 100644
--- a/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
+++ b/app/src/main/java/org/wikipedia/feed/FeedCoordinator.java
@@ -4,6 +4,7 @@
 import android.support.annotation.NonNull;
 
 import org.wikipedia.feed.aggregated.AggregatedFeedContentClient;
+import org.wikipedia.feed.announcement.AnnouncementClient;
 import org.wikipedia.feed.becauseyouread.BecauseYouReadClient;
 import org.wikipedia.feed.continuereading.ContinueReadingClient;
 import org.wikipedia.feed.mainpage.MainPageClient;
@@ -20,6 +21,7 @@
     protected void buildScript(int age) {
         if (age == 0) {
             addPendingClient(new SearchClient());
+            addPendingClient(new AnnouncementClient());
         }
         addPendingClient(new AggregatedFeedContentClient());
         addPendingClient(new ContinueReadingClient());
diff --git 
a/app/src/main/java/org/wikipedia/feed/announcement/Announcement.java 
b/app/src/main/java/org/wikipedia/feed/announcement/Announcement.java
new file mode 100644
index 0000000..b19df12
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/announcement/Announcement.java
@@ -0,0 +1,89 @@
+package org.wikipedia.feed.announcement;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.wikipedia.json.annotations.Required;
+import org.wikipedia.model.BaseModel;
+import org.wikipedia.util.DateUtil;
+
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+class Announcement extends BaseModel {
+
+    @SuppressWarnings("unused,NullableProblems") @Required @NonNull private 
String id;
+    @SuppressWarnings("unused,NullableProblems") @Required @NonNull private 
String type;
+    @SuppressWarnings("unused,NullableProblems") @SerializedName("start_time") 
@Required @NonNull private String startTime;
+    @SuppressWarnings("unused,NullableProblems") @SerializedName("end_time") 
@Required @NonNull private String endTime;
+    @SuppressWarnings("unused") @NonNull private List<String> platforms = 
Collections.emptyList();
+    @SuppressWarnings("unused") @NonNull private List<String> countries = 
Collections.emptyList();
+
+    @SuppressWarnings("unused,NullableProblems") @Required @NonNull private 
String text;
+    @SuppressWarnings("unused") @Nullable private Action action;
+
+    @NonNull String id() {
+        return id;
+    }
+
+    @NonNull String type() {
+        return type;
+    }
+
+    @Nullable Date startTime() {
+        try {
+            return DateUtil.getIso8601DateFormat().parse(startTime);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    @Nullable Date endTime() {
+        try {
+            return DateUtil.getIso8601DateFormat().parse(endTime);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    @NonNull List<String> platforms() {
+        return platforms;
+    }
+
+    @NonNull List<String> countries() {
+        return countries;
+    }
+
+    @NonNull String text() {
+        return text;
+    }
+
+    boolean hasAction() {
+        return action != null;
+    }
+
+    @NonNull String actionTitle() {
+        return action.title();
+    }
+
+    @NonNull String actionUrl() {
+        return action.url();
+    }
+
+    static class Action {
+        @SuppressWarnings("unused,NullableProblems") @Required @NonNull 
private String title;
+        @SuppressWarnings("unused,NullableProblems") @Required @NonNull 
private String url;
+
+        @NonNull String title() {
+            return title;
+        }
+
+        @NonNull String url() {
+            return url;
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/app/src/main/java/org/wikipedia/feed/announcement/AnnouncementClient.java 
b/app/src/main/java/org/wikipedia/feed/announcement/AnnouncementClient.java
new file mode 100644
index 0000000..aa23fd5
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/announcement/AnnouncementClient.java
@@ -0,0 +1,141 @@
+package org.wikipedia.feed.announcement;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.dataclient.WikiSite;
+import org.wikipedia.dataclient.retrofit.RetrofitFactory;
+import org.wikipedia.feed.dataclient.FeedClient;
+import org.wikipedia.feed.model.Card;
+import org.wikipedia.settings.Prefs;
+import org.wikipedia.util.log.L;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+
+public class AnnouncementClient implements FeedClient {
+    private static final String PLATFORM_CODE = "AndroidApp";
+
+    @Nullable private Call<AnnouncementList> call;
+
+    @Override
+    public void request(@NonNull Context context, @NonNull WikiSite wiki, int 
age, @NonNull Callback cb) {
+        cancel();
+        String endpoint = String.format(Locale.ROOT, 
Prefs.getRestbaseUriFormat(), wiki.scheme(),
+                wiki.authority());
+        Retrofit retrofit = RetrofitFactory.newInstance(endpoint, wiki);
+        Service service = retrofit.create(Service.class);
+        call = request(service);
+        call.enqueue(new CallbackAdapter(cb));
+    }
+
+    @Override
+    public void cancel() {
+        if (call == null) {
+            return;
+        }
+        call.cancel();
+        call = null;
+    }
+
+    @VisibleForTesting
+    interface Service {
+
+        /**
+         * Gets a list of announcements that are currently in effect.
+         */
+        @NonNull
+        @Headers("accept: application/json; charset=utf-8; 
profile=\"https://www.mediawiki.org/wiki/Specs/announcements/0.1.0\"";)
+        @GET("feed/announcements")
+        Call<AnnouncementList> get();
+    }
+
+    @VisibleForTesting
+    @NonNull
+    Call<AnnouncementList> request(@NonNull Service service) {
+        return service.get();
+    }
+
+    @VisibleForTesting
+    static class CallbackAdapter implements 
retrofit2.Callback<AnnouncementList> {
+        @NonNull private final Callback cb;
+
+        CallbackAdapter(@NonNull Callback cb) {
+            this.cb = cb;
+        }
+
+        @Override public void onResponse(Call<AnnouncementList> call,
+                                         Response<AnnouncementList> response) {
+            if (response.isSuccessful()) {
+                List<Card> cards = new ArrayList<>();
+                AnnouncementList content = response.body();
+                if (content != null) {
+                    cards.addAll(buildCards(content.items()));
+                }
+                cb.success(cards);
+            } else {
+                L.v(response.message());
+                cb.error(new IOException(response.message()));
+            }
+        }
+
+        @Override public void onFailure(Call<AnnouncementList> call, Throwable 
caught) {
+            L.v(caught);
+            cb.error(caught);
+        }
+    }
+
+    private static List<Card> buildCards(@NonNull List<Announcement> 
announcements) {
+        List<Card> cards = new ArrayList<>();
+        String country = getGeoIPCountry();
+        Date now = new Date();
+        for (Announcement announcement : announcements) {
+            if (!shouldShow(announcement, country, now)) {
+                continue;
+            }
+
+            // TODO: add this card!
+
+            L.d("yes!!");
+
+        }
+        return cards;
+    }
+
+    @VisibleForTesting
+    static boolean shouldShow(@Nullable Announcement announcement,
+                              @Nullable String country,
+                              @NonNull Date date) {
+        if (announcement == null
+                || !announcement.platforms().contains(PLATFORM_CODE)
+                || TextUtils.isEmpty(country)
+                || !announcement.countries().contains(country)
+                || (announcement.startTime() != null && 
announcement.startTime().after(date))
+                || (announcement.endTime() != null && 
announcement.endTime().before(date))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Nullable private static String getGeoIPCountry() {
+        try {
+            return 
GeoIPCookieUnmarshaller.unmarshal(WikipediaApp.getInstance()).country();
+        } catch (IllegalArgumentException e) {
+            // For our purposes, don't care about malformations in the GeoIP 
cookie for now.
+            return null;
+        }
+    }
+}
diff --git 
a/app/src/main/java/org/wikipedia/feed/announcement/AnnouncementList.java 
b/app/src/main/java/org/wikipedia/feed/announcement/AnnouncementList.java
new file mode 100644
index 0000000..393f7cd
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/feed/announcement/AnnouncementList.java
@@ -0,0 +1,21 @@
+package org.wikipedia.feed.announcement;
+
+import android.support.annotation.NonNull;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.wikipedia.model.BaseModel;
+
+import java.util.Collections;
+import java.util.List;
+
+class AnnouncementList extends BaseModel {
+
+    @SuppressWarnings("unused") @SerializedName("announce") @NonNull private 
List<Announcement> items = Collections.emptyList();
+
+    @NonNull
+    List<Announcement> items() {
+        return items;
+    }
+
+}
\ No newline at end of file
diff --git 
a/app/src/test/java/org/wikipedia/feed/announcement/AnnouncementClientTest.java 
b/app/src/test/java/org/wikipedia/feed/announcement/AnnouncementClientTest.java
new file mode 100644
index 0000000..00732e5
--- /dev/null
+++ 
b/app/src/test/java/org/wikipedia/feed/announcement/AnnouncementClientTest.java
@@ -0,0 +1,113 @@
+package org.wikipedia.feed.announcement;
+
+import android.support.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.wikipedia.feed.dataclient.FeedClient.Callback;
+import org.wikipedia.feed.model.Card;
+import org.wikipedia.json.GsonUnmarshaller;
+import org.wikipedia.test.MockWebServerTest;
+import org.wikipedia.test.TestFileUtil;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import retrofit2.Call;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+public class AnnouncementClientTest extends MockWebServerTest {
+    private static final int ANNOUNCEMENT_IOS = 0;
+    private static final int ANNOUNCEMENT_ANDROID = 1;
+    private static final int ANNOUNCEMENT_INVALID_DATES = 2;
+    private static final int ANNOUNCEMENT_NO_DATES = 3;
+    private static final int ANNOUNCEMENT_NO_COUNTRIES = 4;
+    @NonNull private AnnouncementClient client = new AnnouncementClient();
+    private AnnouncementList announcementList;
+    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", 
Locale.ROOT);
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+        String json = TestFileUtil.readRawFile("announce_2016_11_21.json");
+        announcementList = GsonUnmarshaller.unmarshal(AnnouncementList.class, 
json);
+    }
+
+    @Test public void testRequestSuccess() throws Throwable {
+        enqueueFromFile("announce_2016_11_21.json");
+        Callback cb = mock(Callback.class);
+        request(cb);
+        server().takeRequest();
+        verify(cb).success(anyListOf(Card.class));
+        //noinspection unchecked
+        verify(cb, never()).error(any(Throwable.class));
+    }
+
+    @Test public void testRequestMalformed() throws Throwable {
+        server().enqueue("Jimmy crack corn, and I don't care.");
+        Callback cb = mock(Callback.class);
+        request(cb);
+        server().takeRequest();
+        verify(cb, never()).success(anyListOf(Card.class));
+        verify(cb).error(any(Throwable.class));
+    }
+
+    @Test public void testRequestNotFound() throws Throwable {
+        enqueue404();
+        Callback cb = mock(Callback.class);
+        request(cb);
+        server().takeRequest();
+        verify(cb, never()).success(anyListOf(Card.class));
+        verify(cb).error(any(Throwable.class));
+    }
+
+    @Test public void testShouldShowByCountry() throws Throwable {
+        Announcement announcement = 
announcementList.items().get(ANNOUNCEMENT_ANDROID);
+        Date dateDuring = dateFormat.parse("2016-11-20");
+        assertThat(AnnouncementClient.shouldShow(announcement, "US", 
dateDuring), is(true));
+        assertThat(AnnouncementClient.shouldShow(announcement, "FI", 
dateDuring), is(false));
+        assertThat(AnnouncementClient.shouldShow(announcement, null, 
dateDuring), is(false));
+    }
+
+    @Test public void testShouldShowByDate() throws Throwable {
+        Announcement announcement = 
announcementList.items().get(ANNOUNCEMENT_ANDROID);
+        Date dateBefore = dateFormat.parse("2016-08-01");
+        Date dateAfter = dateFormat.parse("2017-01-05");
+        assertThat(AnnouncementClient.shouldShow(announcement, "US", 
dateBefore), is(false));
+        assertThat(AnnouncementClient.shouldShow(announcement, "US", 
dateAfter), is(false));
+    }
+
+    @Test public void testShouldShowByPlatform() throws Throwable {
+        Announcement announcementIOS = 
announcementList.items().get(ANNOUNCEMENT_IOS);
+        Date dateDuring = dateFormat.parse("2016-11-20");
+        assertThat(AnnouncementClient.shouldShow(announcementIOS, "US", 
dateDuring), is(false));
+    }
+
+    @Test public void testShouldShowForInvalidDates() throws Throwable {
+        assertThat(announcementList.items().get(ANNOUNCEMENT_INVALID_DATES), 
is(nullValue()));
+        assertThat(announcementList.items().get(ANNOUNCEMENT_NO_DATES), 
is(nullValue()));
+    }
+
+    @Test public void testShouldShowForInvalidCountries() throws Throwable {
+        Announcement announcement = 
announcementList.items().get(ANNOUNCEMENT_NO_COUNTRIES);
+        Date dateDuring = dateFormat.parse("2016-11-20");
+        assertThat(AnnouncementClient.shouldShow(announcement, "US", 
dateDuring), is(false));
+        assertThat(AnnouncementClient.shouldShow(announcement, "FI", 
dateDuring), is(false));
+        assertThat(AnnouncementClient.shouldShow(announcement, "", 
dateDuring), is(false));
+    }
+
+    private void request(@NonNull Callback cb) {
+        Call<AnnouncementList> call = 
client.request(service(AnnouncementClient.Service.class));
+        call.enqueue(new AnnouncementClient.CallbackAdapter(cb));
+    }
+}
diff --git a/app/src/test/res/raw/announce_2016_11_21.json 
b/app/src/test/res/raw/announce_2016_11_21.json
new file mode 100644
index 0000000..85eef97
--- /dev/null
+++ b/app/src/test/res/raw/announce_2016_11_21.json
@@ -0,0 +1,94 @@
+{
+  "announce": [
+    {
+      "id": "EN1116SURVEYIOS",
+      "type": "survey",
+      "start_time": "2016-11-15T17:11:12Z",
+      "end_time": "2016-11-30T17:11:12Z",
+      "platforms": [
+        "iOSApp"
+      ],
+      "text": "Answer three questions and help us improve Wikipedia.",
+      "action": {
+        "title": "Take the survey",
+        "url": "https://survey.url?survey_id=12345&source=iOS";
+      },
+      "caption_HTML": "<p>Survey data handled by a third party. <a 
href=\"https://wikimediafoundation.org/wiki/Survey_Privacy_Statement\";>Privacy</a>.</p>",
+      "countries": [
+        "US",
+        "CA",
+        "GB"
+      ]
+    },
+    {
+      "id": "EN11116SURVEYANDROID",
+      "type": "survey",
+      "start_time": "2016-11-15T17:11:12Z",
+      "end_time": "2016-11-30T17:11:12Z",
+      "platforms": [
+        "AndroidApp"
+      ],
+      "text": "Answer three questions and help us improve Wikipedia.",
+      "action": {
+        "title": "Take the survey",
+        "url": "https://survey.url?survey_id=12345&source=android";
+      },
+      "caption_HTML": "<p>Survey data handled by a third party. <a 
href=\"https://wikimediafoundation.org/wiki/Survey_Privacy_Statement\";>Privacy</a>.</p>",
+      "countries": [
+        "US",
+        "CA",
+        "GB"
+      ]
+    },
+    {
+      "id": "AnnouncementWithInvalidDates",
+      "type": "fundraising",
+      "start_time": null,
+      "end_time": null,
+      "platforms": [
+        "AndroidApp"
+      ],
+      "text": "Help Wikipedia by making a donation.",
+      "action": {
+        "title": "Donate",
+        "url": "https://donate.url?campaign_id=12345&source=android";
+      },
+      "countries": [
+        "US",
+        "CA",
+        "GB"
+      ]
+    },
+    {
+      "id": "AnnouncementWithNoDates",
+      "type": "fundraising",
+      "platforms": [
+        "AndroidApp"
+      ],
+      "text": "Help Wikipedia by making a donation.",
+      "action": {
+        "title": "Donate",
+        "url": "https://donate.url?campaign_id=12345&source=android";
+      },
+      "countries": [
+        "US",
+        "CA",
+        "GB"
+      ]
+    },
+    {
+      "id": "AnnouncementWithNoCountries",
+      "type": "fundraising",
+      "start_time": "2016-11-15T17:11:12Z",
+      "end_time": "2016-11-30T17:11:12Z",
+      "platforms": [
+        "AndroidApp"
+      ],
+      "text": "Help Wikipedia by making a donation.",
+      "action": {
+        "title": "Donate",
+        "url": "https://donate.url?campaign_id=12345&source=android";
+      }
+    }
+  ]
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/322712
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ibbfcbcbbcf000a03515af83168e06e307c43b5a4
Gerrit-PatchSet: 6
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <dbr...@wikimedia.org>
Gerrit-Reviewer: BearND <bsitzm...@wikimedia.org>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Dbrant <dbr...@wikimedia.org>
Gerrit-Reviewer: Mholloway <mhollo...@wikimedia.org>
Gerrit-Reviewer: Niedzielski <sniedziel...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to