Niedzielski has uploaded a new change for review. https://gerrit.wikimedia.org/r/316852
Change subject: WIP: Replace PageCache with OkHttp caching ...................................................................... WIP: Replace PageCache with OkHttp caching Seems snappier and promising. Todo: • Allow cached pages when offline • Verify saved page behavior • More testing and thinking Bug: T148675 Change-Id: Ie4cd16463360a1b44f5f52ac1d1a3afb84fb66ff --- M app/src/main/java/org/wikipedia/WikipediaApp.java M app/src/main/java/org/wikipedia/page/PageActivity.java D app/src/main/java/org/wikipedia/page/PageCache.java M app/src/main/java/org/wikipedia/page/PageDataClient.java M app/src/main/java/org/wikipedia/page/PageFragment.java M app/src/main/java/org/wikipedia/page/PageLoadStrategy.java M app/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java M app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java 8 files changed, 36 insertions(+), 386 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia refs/changes/52/316852/1 diff --git a/app/src/main/java/org/wikipedia/WikipediaApp.java b/app/src/main/java/org/wikipedia/WikipediaApp.java index 7e9f11b..5f83032 100644 --- a/app/src/main/java/org/wikipedia/WikipediaApp.java +++ b/app/src/main/java/org/wikipedia/WikipediaApp.java @@ -41,7 +41,6 @@ import org.wikipedia.login.User; import org.wikipedia.onboarding.OnboardingStateMachine; import org.wikipedia.onboarding.PrefsOnboardingStateMachine; -import org.wikipedia.page.PageCache; import org.wikipedia.pageimages.PageImage; import org.wikipedia.readinglist.database.ReadingListRow; import org.wikipedia.readinglist.page.ReadingListPageRow; @@ -131,17 +130,6 @@ return zeroHandler; } - /** - * Our page cache, which discards the eldest entries based on access time. - * This will allow the user to go "back" smoothly (the previous page is guaranteed - * to be in cache), but also to go "forward" smoothly (if the user clicks on a link - * that was already visited within a short time). - */ - private PageCache pageCache; - public PageCache getPageCache() { - return pageCache; - } - public WikipediaApp() { INSTANCE = this; } @@ -189,7 +177,6 @@ Fresco.initialize(this, config); zeroHandler = new WikipediaZeroHandler(this); - pageCache = new PageCache(this); // TODO: remove this code after all logged in users also have a system account or August 2016. AccountUtil.createAccountForLoggedInUser(); diff --git a/app/src/main/java/org/wikipedia/page/PageActivity.java b/app/src/main/java/org/wikipedia/page/PageActivity.java index 92d2649..1f174a6 100644 --- a/app/src/main/java/org/wikipedia/page/PageActivity.java +++ b/app/src/main/java/org/wikipedia/page/PageActivity.java @@ -381,7 +381,7 @@ } pageFragment.closeFindInPage(); if (position == TabPosition.CURRENT_TAB) { - pageFragment.loadPage(title, entry, PageLoadStrategy.Cache.FALLBACK, true); + pageFragment.loadPage(title, entry, true); } else if (position == TabPosition.NEW_TAB_BACKGROUND) { pageFragment.openInNewBackgroundTabFromMenu(title, entry); } else { diff --git a/app/src/main/java/org/wikipedia/page/PageCache.java b/app/src/main/java/org/wikipedia/page/PageCache.java deleted file mode 100644 index 93071f4..0000000 --- a/app/src/main/java/org/wikipedia/page/PageCache.java +++ /dev/null @@ -1,180 +0,0 @@ -package org.wikipedia.page; - -import android.content.Context; -import android.os.Environment; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import com.jakewharton.disklrucache.DiskLruCache; - -import org.json.JSONObject; -import org.wikipedia.concurrency.SaneAsyncTask; -import org.wikipedia.util.log.L; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import static org.wikipedia.util.FileUtil.readFile; -import static org.wikipedia.util.FileUtil.writeToStream; - -/** - * Implements a cache of Page objects. - */ -public class PageCache { - private static final int DISK_CACHE_VERSION = 1; - private static final int DISK_CACHE_SIZE = 1024 * 1024 * 64; // 64MB - private static final String DISK_CACHE_SUBDIR = "wp_pagecache"; - - @Nullable private DiskLruCache mDiskLruCache; - @NonNull private final Object mDiskCacheLock = new Object(); - - public PageCache(Context context) { - // Initialize disk cache on background thread - File cacheDir = getDiskCacheDir(context, DISK_CACHE_SUBDIR); - new InitDiskCacheTask(cacheDir).execute(); - } - - private class InitDiskCacheTask extends SaneAsyncTask<Void> { - private final File cacheDir; - - InitDiskCacheTask(File cacheDir) { - this.cacheDir = cacheDir; - } - - @Override - public Void performTask() throws Throwable { - synchronized (mDiskCacheLock) { - mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_VERSION, 1, DISK_CACHE_SIZE); - mDiskCacheLock.notifyAll(); // Wake any waiting threads - } - return null; - } - - @Override - public void onCatch(Throwable caught) { - L.e("Caught " + caught.getMessage(), caught); - } - } - - public void put(PageTitle title, Page page) { - new AddPageToCacheTask(title, page).execute(); - } - - private class AddPageToCacheTask extends SaneAsyncTask<Void> { - private final PageTitle title; - private final Page page; - - AddPageToCacheTask(PageTitle title, Page page) { - this.title = title; - this.page = page; - } - - @Override - public Void performTask() throws Throwable { - synchronized (mDiskCacheLock) { - if (mDiskLruCache == null) { - return null; - } - DiskLruCache.Editor editor = null; - try { - L.d("Writing to cache: " + title.getDisplayText()); - String key = title.getIdentifier(); - editor = mDiskLruCache.edit(key); - if (editor == null) { - return null; - } - OutputStream outputStream = new BufferedOutputStream(editor.newOutputStream(0)); - writeToStream(outputStream, page.toJSON().toString()); - mDiskLruCache.flush(); - editor.commit(); - } catch (IOException e) { - if (editor != null) { - editor.abort(); - } - } - } - return null; - } - - @Override public void onCatch(Throwable caught) { - L.e("Failed to add page to cache.", caught); - } - } - - public interface CacheGetListener { - void onGetComplete(Page page, int sequence); - void onGetError(Throwable e, int sequence); - } - - public void get(PageTitle title, final int sequence, final CacheGetListener listener) { - new GetPageFromCacheTask(title) { - @Override - public void onFinish(Page page) { - listener.onGetComplete(page, sequence); - } - - @Override - public void onCatch(Throwable caught) { - listener.onGetError(caught, sequence); - } - }.execute(); - } - - private class GetPageFromCacheTask extends SaneAsyncTask<Page> { - private final PageTitle title; - - GetPageFromCacheTask(PageTitle title) { - this.title = title; - } - - @Override - public Page performTask() throws Throwable { - synchronized (mDiskCacheLock) { - if (mDiskLruCache == null) { - return null; - } - String key = title.getIdentifier(); - DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key); - if (snapshot == null) { - return null; - } - try { - L.d("Reading from cache: " + title.getDisplayText()); - InputStream inputStream = new BufferedInputStream(snapshot.getInputStream(0)); - String jsonStr = readFile(inputStream); - return new Page(new JSONObject(jsonStr)); - } finally { - snapshot.close(); - } - } - } - } - - // Creates a unique subdirectory of the designated app cache directory. Tries to use external - // storage, but if not mounted, falls back on internal storage. - private static File getDiskCacheDir(Context context, String uniqueName) { - // Check if media is mounted or storage is built-in, if so, try and use external cache dir - // otherwise use internal cache dir. - final String cachePath; - if (Environment.MEDIA_MOUNTED.equals(extStorageState()) - && context.getExternalCacheDir() != null) { - cachePath = context.getExternalCacheDir().getPath(); - } else { - cachePath = context.getCacheDir().getPath(); - } - return new File(cachePath + File.separator + uniqueName); - } - - @Nullable private static String extStorageState() { - // https://code.google.com/p/android/issues/detail?id=175810 - try { - return Environment.getExternalStorageState(); - } catch (NullPointerException e) { - return null; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/page/PageDataClient.java b/app/src/main/java/org/wikipedia/page/PageDataClient.java index 4e613ae..9f03005 100644 --- a/app/src/main/java/org/wikipedia/page/PageDataClient.java +++ b/app/src/main/java/org/wikipedia/page/PageDataClient.java @@ -92,11 +92,6 @@ private int sectionTargetFromIntent; private String sectionTargetFromTitle; - /** - * Whether to write the page contents to cache as soon as it's loaded. - */ - private boolean cacheOnComplete = true; - private ErrorCallback networkErrorCallback; // copied fields @@ -145,7 +140,7 @@ } @Override - public void load(boolean pushBackStack, @NonNull Cache cachePreference, int stagedScrollY) { + public void load(boolean pushBackStack, int stagedScrollY) { if (pushBackStack) { // update the topmost entry in the backstack, before we start overwriting things. updateCurrentBackStackItem(); @@ -172,7 +167,6 @@ JSONObject wrapper = new JSONObject(); // whatever we pass to this event will be passed back to us by the WebView! wrapper.put("sequence", sequenceNumber.get()); - wrapper.put("cachePreference", cachePreference.name()); wrapper.put("stagedScrollY", stagedScrollY); bridge.sendMessage("beginNewPage", wrapper); } catch (JSONException e) { @@ -193,8 +187,7 @@ PageBackStackItem item = backStack.get(backStack.size() - 1); // display the page based on the backstack item, stage the scrollY position based on // the backstack item. - fragment.loadPage(item.getTitle(), item.getHistoryEntry(), Cache.PREFERRED, false, - item.getScrollY()); + fragment.loadPage(item.getTitle(), item.getHistoryEntry(), false, item.getScrollY()); L.d("Loaded page " + item.getTitle().getDisplayText() + " from backstack"); } @@ -284,7 +277,6 @@ protected void commonSectionFetchOnCatch(Throwable caught, int startSequenceNum) { ErrorCallback callback = networkErrorCallback; networkErrorCallback = null; - cacheOnComplete = false; state = STATE_COMPLETE_FETCH; if (fragment.callback() != null) { fragment.callback().onPageInvalidateOptionsMenu(); @@ -332,7 +324,7 @@ public void onMessage(JSONObject payload) { try { stagedScrollY = payload.getInt("stagedScrollY"); - loadOnWebViewReady(Cache.valueOf(payload.getString("cachePreference"))); + loadOnWebViewReady(); } catch (JSONException e) { L.logRemoteErrorIfProd(e); } @@ -417,15 +409,10 @@ // FIXME: Move this out into a PageComplete event of sorts if (state == STATE_COMPLETE_FETCH) { fragment.setupToC(model, isFirstPage()); - - //add the page to cache! - if (cacheOnComplete) { - app.getPageCache().put(model.getTitleOriginal(), model.getPage()); - } } } - private void loadOnWebViewReady(Cache cachePreference) { + private void loadOnWebViewReady() { // stage any section-specific link target from the title, since the title may be // replaced (normalized) sectionTargetFromTitle = model.getTitle().getFragment(); @@ -433,96 +420,21 @@ L10nUtil.setupDirectionality(model.getTitle().getWikiSite().languageCode(), Locale.getDefault().getLanguage(), bridge); - switch (cachePreference) { - case PREFERRED: - loadFromCache(new ErrorCallback() { + loadFromNetwork(new ErrorCallback() { + @Override + public void call(final Throwable networkError) { + loadSavedPage(new ErrorCallback() { @Override - public void call(Throwable cacheError) { - loadFromNetwork(new ErrorCallback() { - @Override - public void call(final Throwable networkError) { - loadSavedPage(new ErrorCallback() { - @Override - public void call(Throwable savedError) { - fragment.onPageLoadError(networkError); - } - }); - } - }); + public void call(Throwable savedError) { + fragment.onPageLoadError(networkError); } }); - break; - case FALLBACK: - loadFromNetwork(new ErrorCallback() { - @Override - public void call(final Throwable networkError) { - loadFromCache(new ErrorCallback() { - @Override - public void call(Throwable cacheError) { - loadSavedPage(new ErrorCallback() { - @Override - public void call(Throwable savedError) { - fragment.onPageLoadError(networkError); - } - }); - } - }); - } - }); - break; - default: - throw new IllegalStateException("Unknown cache preference=" + cachePreference); - } - } - - private void loadFromCache(final ErrorCallback errorCallback) { - app.getPageCache() - .get(model.getTitleOriginal(), sequenceNumber.get(), new PageCache.CacheGetListener() { - @Override - public void onGetComplete(Page page, int sequence) { - if (!sequenceNumber.inSync(sequence)) { - return; - } - if (page != null) { - L.d("Using page from cache: " + model.getTitleOriginal().getDisplayText()); - model.setPage(page); - model.setTitle(page.getTitle()); - // Update our history entry, in case the Title was changed (i.e. normalized) - final HistoryEntry curEntry = model.getCurEntry(); - model.setCurEntry( - new HistoryEntry(model.getTitle(), curEntry.getSource())); - // load the current title's thumbnail from sqlite - updateThumbnail(PageImage.DATABASE_TABLE.getImageUrlForTitle(app, model.getTitle())); - // Save history entry... - new SaveHistoryTask(model.getCurEntry(), app).execute(); - // don't re-cache the page after loading. - cacheOnComplete = false; - state = STATE_COMPLETE_FETCH; - setState(state); - performActionForState(state); - if (fragment.isAdded()) { - fragment.onPageLoadComplete(); - } - } else { - errorCallback.call(null); - } - } - - @Override - public void onGetError(Throwable e, int sequence) { - L.e("Failed to get page from cache.", e); - if (!sequenceNumber.inSync(sequence)) { - return; - } - errorCallback.call(e); - } - }); + } + }); } private void loadFromNetwork(final ErrorCallback errorCallback) { networkErrorCallback = errorCallback; - // and make sure to write it to cache when it's loaded. - cacheOnComplete = true; setState(STATE_NO_FETCH); performActionForState(state); } diff --git a/app/src/main/java/org/wikipedia/page/PageFragment.java b/app/src/main/java/org/wikipedia/page/PageFragment.java index ec9f51e..723ddf7 100755 --- a/app/src/main/java/org/wikipedia/page/PageFragment.java +++ b/app/src/main/java/org/wikipedia/page/PageFragment.java @@ -36,7 +36,6 @@ import org.wikipedia.LongPressHandler; import org.wikipedia.NightModeHandler; import org.wikipedia.R; -import org.wikipedia.dataclient.WikiSite; import org.wikipedia.WikipediaApp; import org.wikipedia.activity.FragmentUtil; import org.wikipedia.analytics.FindInPageFunnel; @@ -45,6 +44,7 @@ import org.wikipedia.analytics.TabFunnel; import org.wikipedia.bridge.CommunicationBridge; import org.wikipedia.concurrency.CallbackTask; +import org.wikipedia.dataclient.WikiSite; import org.wikipedia.editing.EditHandler; import org.wikipedia.history.HistoryEntry; import org.wikipedia.language.LangLinksActivity; @@ -569,7 +569,8 @@ public void openInNewForegroundTabFromMenu(PageTitle title, HistoryEntry entry) { openInNewTabFromMenu(title, entry, getForegroundTabPosition()); - loadPage(title, entry, PageLoadStrategy.Cache.FALLBACK, false); + // todo: prefer cache + loadPage(title, entry, true, false); } public void openInNewTabFromMenu(PageTitle title, @@ -579,19 +580,18 @@ tabFunnel.logOpenInNew(tabList.size()); } - public void loadPage(PageTitle title, HistoryEntry entry, PageLoadStrategy.Cache cachePreference, - boolean pushBackStack) { - loadPage(title, entry, cachePreference, pushBackStack, 0); + public void loadPage(PageTitle title, HistoryEntry entry, boolean pushBackStack) { + loadPage(title, entry, pushBackStack, 0); } - public void loadPage(PageTitle title, HistoryEntry entry, PageLoadStrategy.Cache cachePreference, - boolean pushBackStack, int stagedScrollY) { - loadPage(title, entry, cachePreference, pushBackStack, stagedScrollY, false); + public void loadPage(PageTitle title, HistoryEntry entry, boolean pushBackStack, + int stagedScrollY) { + loadPage(title, entry, pushBackStack, stagedScrollY, false); } - public void loadPage(PageTitle title, HistoryEntry entry, PageLoadStrategy.Cache cachePreference, - boolean pushBackStack, boolean pageRefreshed) { - loadPage(title, entry, cachePreference, pushBackStack, 0, pageRefreshed); + public void loadPage(PageTitle title, HistoryEntry entry, boolean pushBackStack, + boolean pageRefreshed) { + loadPage(title, entry, pushBackStack, 0, pageRefreshed); } /** @@ -601,11 +601,10 @@ * request, etc. * @param title Title of the new page to load. * @param entry HistoryEntry associated with the new page. - * @param cachePreference Whether to try loading the page from cache or from network. * @param pushBackStack Whether to push the new page onto the backstack. */ - public void loadPage(PageTitle title, HistoryEntry entry, PageLoadStrategy.Cache cachePreference, - boolean pushBackStack, int stagedScrollY, boolean pageRefreshed) { + public void loadPage(PageTitle title, HistoryEntry entry, boolean pushBackStack, + int stagedScrollY, boolean pageRefreshed) { // disable sliding of the ToC while sections are loading tocHandler.setEnabled(false); @@ -621,7 +620,7 @@ this.pageRefreshed = pageRefreshed; closePageScrollFunnel(); - pageDataClient.load(pushBackStack, cachePreference, stagedScrollY); + pageDataClient.load(pushBackStack, stagedScrollY); updateBookmark(); } @@ -676,7 +675,7 @@ pageDataClient.backFromEditing(data); FeedbackUtil.showMessage(getActivity(), R.string.edit_saved_successfully); // and reload the page... - loadPage(model.getTitleOriginal(), model.getCurEntry(), PageLoadStrategy.Cache.FALLBACK, false); + loadPage(model.getTitleOriginal(), model.getCurEntry(), true, false); } } @@ -919,7 +918,7 @@ errorState = false; model.setCurEntry(new HistoryEntry(model.getTitle(), HistoryEntry.SOURCE_HISTORY)); - loadPage(model.getTitle(), model.getCurEntry(), PageLoadStrategy.Cache.FALLBACK, false, true); + loadPage(model.getTitle(), model.getCurEntry(), false, true); } private ToCHandler tocHandler; diff --git a/app/src/main/java/org/wikipedia/page/PageLoadStrategy.java b/app/src/main/java/org/wikipedia/page/PageLoadStrategy.java index 7720e25..fde3354 100644 --- a/app/src/main/java/org/wikipedia/page/PageLoadStrategy.java +++ b/app/src/main/java/org/wikipedia/page/PageLoadStrategy.java @@ -1,13 +1,13 @@ package org.wikipedia.page; +import android.content.Intent; +import android.support.annotation.NonNull; + import org.wikipedia.bridge.CommunicationBridge; import org.wikipedia.editing.EditHandler; import org.wikipedia.page.leadimages.LeadImagesHandler; import org.wikipedia.views.ObservableWebView; import org.wikipedia.views.SwipeRefreshLayoutWithScroll; - -import android.content.Intent; -import android.support.annotation.NonNull; import java.util.List; @@ -16,22 +16,6 @@ * for viewing. */ public interface PageLoadStrategy { - - /** - * Indicates what type of cache strategy should the current request take. - */ - enum Cache { - /** - * Page should be retrieved from cache if possible, only use network connection if necessary - */ - PREFERRED, - - /** - * Page should try to be loaded from network connection, only try cache as a fallback - */ - FALLBACK - } - @SuppressWarnings("checkstyle:parameternumber") void setUp(@NonNull PageViewModel model, @NonNull PageFragment fragment, @@ -42,7 +26,7 @@ @NonNull LeadImagesHandler leadImagesHandler, @NonNull List<PageBackStackItem> backStack); - void load(boolean pushBackStack, @NonNull Cache cachePreference, int stagedScrollY); + void load(boolean pushBackStack, int stagedScrollY); boolean isLoading(); diff --git a/app/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java b/app/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java index b70d400..85dc2ea 100644 --- a/app/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java +++ b/app/src/main/java/org/wikipedia/page/gallery/GalleryActivity.java @@ -27,12 +27,12 @@ import android.widget.TextView; import org.wikipedia.R; -import org.wikipedia.dataclient.WikiSite; import org.wikipedia.ViewAnimations; import org.wikipedia.WikipediaApp; import org.wikipedia.activity.ActivityUtil; import org.wikipedia.activity.ThemedActionBarActivity; import org.wikipedia.analytics.GalleryFunnel; +import org.wikipedia.dataclient.WikiSite; import org.wikipedia.feed.image.FeaturedImage; import org.wikipedia.history.HistoryEntry; import org.wikipedia.json.GsonMarshaller; @@ -40,7 +40,6 @@ import org.wikipedia.page.LinkMovementMethodExt; import org.wikipedia.page.Page; import org.wikipedia.page.PageActivity; -import org.wikipedia.page.PageCache; import org.wikipedia.page.PageTitle; import org.wikipedia.theme.Theme; import org.wikipedia.util.FeedbackUtil; @@ -231,29 +230,7 @@ loadGalleryItemFor(featuredImage); } } else { - // find our Page in the page cache... - app.getPageCache().get(pageTitle, 0, new PageCache.CacheGetListener() { - @Override - public void onGetComplete(Page page, int sequence) { - GalleryActivity.this.page = page; - if (page != null && page.getGalleryCollection() != null - && page.getGalleryCollection().getItemList().size() > 0) { - applyGalleryCollection(page.getGalleryCollection()); - cacheOnLoad = false; - } else { - // fetch the gallery from the network... - fetchGalleryCollection(); - cacheOnLoad = true; - } - } - - @Override - public void onGetError(Throwable e, int sequence) { - L.e("Failed to get page from cache.", e); - fetchGalleryCollection(); - cacheOnLoad = true; - } - }); + fetchGalleryCollection(); } } @@ -421,7 +398,6 @@ // save it to our current page, for later use if (cacheOnLoad && page != null) { page.setGalleryCollection(result); - app.getPageCache().put(pageTitle, page); } applyGalleryCollection(result); } diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java index d3d2306..bb067e9 100755 --- a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.java @@ -10,7 +10,6 @@ import org.wikipedia.page.Page; import org.wikipedia.page.PageFragment; import org.wikipedia.page.PageContainerLongPressHandler; -import org.wikipedia.page.PageCache; import org.wikipedia.page.PageTitle; import org.wikipedia.page.gallery.GalleryActivity; import org.wikipedia.page.gallery.GalleryCollection; @@ -234,33 +233,6 @@ linkPreviewOnLoadCallback); } - private void loadContentFromCache() { - L.v("Loading link preview from cache"); - getApplication().getPageCache() - .get(pageTitle, 0, new PageCache.CacheGetListener() { - @Override - public void onGetComplete(Page page, int sequence) { - if (!isAdded()) { - return; - } - if (page != null) { - displayPreviewFromCachedPage(page); - } else { - loadContentFromSavedPage(); - } - } - - @Override - public void onGetError(Throwable e, int sequence) { - if (!isAdded()) { - return; - } - L.e("Failed to get page from cache.", e); - loadContentFromSavedPage(); - } - }); - } - private void loadContentFromSavedPage() { L.v("Loading link preview from Saved Pages"); new LoadSavedPageTask(pageTitle) { @@ -302,7 +274,7 @@ layoutPreview(); } else { pageSummary.logError("Page summary request failed"); - loadContentFromCache(); + loadContentFromSavedPage(); } } -- To view, visit https://gerrit.wikimedia.org/r/316852 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ie4cd16463360a1b44f5f52ac1d1a3afb84fb66ff Gerrit-PatchSet: 1 Gerrit-Project: apps/android/wikipedia Gerrit-Branch: master Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits