Commit 38d898c5 authored by spdonghao's avatar spdonghao Committed by Commit Bot

Cache task for MostVisitedSitesHost.

Bug: 1071503
Change-Id: Idf2067f16cbdb4970965ddea980b5679621613cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2149514Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarCathy Li <chili@chromium.org>
Reviewed-by: default avatarXi Han <hanxi@chromium.org>
Commit-Queue: Hao Dong <spdonghao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#762043}
parent ebf9e7be
...@@ -59,18 +59,23 @@ public class MostVisitedSitesFaviconHelper { ...@@ -59,18 +59,23 @@ public class MostVisitedSitesFaviconHelper {
* Save the favicon to the disk. * Save the favicon to the disk.
* @param topSitesInfo SiteSuggestions data updated. * @param topSitesInfo SiteSuggestions data updated.
* @param urlsToUpdate The set of urls which need to fetch and save the favicon. * @param urlsToUpdate The set of urls which need to fetch and save the favicon.
* @param callback The callback function after skipping the existing favicon or saving favicon.
*/ */
public void saveFaviconsToFile(List<SiteSuggestion> topSitesInfo, Set<String> urlsToUpdate) { public void saveFaviconsToFile(
List<SiteSuggestion> topSitesInfo, Set<String> urlsToUpdate, Runnable callback) {
for (SiteSuggestion siteData : topSitesInfo) { for (SiteSuggestion siteData : topSitesInfo) {
String url = siteData.url; String url = siteData.url;
if (!urlsToUpdate.contains(url)) { if (!urlsToUpdate.contains(url)) {
if (callback != null) {
callback.run();
}
continue; continue;
} }
LargeIconBridge.LargeIconCallback iconCallback = LargeIconBridge.LargeIconCallback iconCallback =
(icon, fallbackColor, isFallbackColorDefault, iconType) -> { (icon, fallbackColor, isFallbackColorDefault, iconType) -> {
saveFaviconToFile(String.valueOf(siteData.faviconId), saveFaviconToFile(String.valueOf(siteData.faviconId),
MostVisitedSitesMetadataUtils.getOrCreateTopSitesDirectory(), url, MostVisitedSitesMetadataUtils.getOrCreateTopSitesDirectory(), url,
fallbackColor, icon); fallbackColor, icon, callback);
}; };
fetchIcon(siteData, iconCallback); fetchIcon(siteData, iconCallback);
} }
...@@ -120,10 +125,13 @@ public class MostVisitedSitesFaviconHelper { ...@@ -120,10 +125,13 @@ public class MostVisitedSitesFaviconHelper {
* @param url The url which the favicon corresponds to. * @param url The url which the favicon corresponds to.
* @param fallbackColor The color for generating a new icon when favicon is null from native. * @param fallbackColor The color for generating a new icon when favicon is null from native.
* @param icon The favicon fetched from native. * @param icon The favicon fetched from native.
* @param callback The callback function after saving each favicon.
*/ */
private void saveFaviconToFile( private void saveFaviconToFile(String fileName, File directory, String url, int fallbackColor,
String fileName, File directory, String url, int fallbackColor, Bitmap icon) { Bitmap icon, Runnable callback) {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { new AsyncTask<Void>() {
@Override
protected Void doInBackground() {
Bitmap newIcon = icon; Bitmap newIcon = icon;
// If icon is null, we need to generate a favicon. // If icon is null, we need to generate a favicon.
if (newIcon == null) { if (newIcon == null) {
...@@ -146,6 +154,15 @@ public class MostVisitedSitesFaviconHelper { ...@@ -146,6 +154,15 @@ public class MostVisitedSitesFaviconHelper {
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Fail to write file: " + metadataFile.getAbsolutePath()); Log.e(TAG, "Fail to write file: " + metadataFile.getAbsolutePath());
} }
}); return null;
}
@Override
protected void onPostExecute(Void aVoid) {
if (callback != null) {
callback.run();
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
} }
...@@ -9,6 +9,7 @@ import android.content.Context; ...@@ -9,6 +9,7 @@ import android.content.Context;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.AsyncTask; import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.SiteSuggestion; import org.chromium.chrome.browser.suggestions.SiteSuggestion;
...@@ -50,6 +51,16 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer { ...@@ -50,6 +51,16 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer {
private static boolean sSkipRestoreFromDiskForTests; private static boolean sSkipRestoreFromDiskForTests;
private Runnable mCurrentTask;
private Runnable mPendingTask;
// Whether restoreFromDisk() is finished.
private boolean mIsSynced;
// Records how many remaining files the current task needs to save.
private int mCurrentFilesNeedToSaveCount;
// Records how many remaining files the pending task needs to save. This value is used to
// replace mCurrentFilesNeedToSaveCount when updating pending to current task.
private int mPendingFilesNeedToSaveCount;
public MostVisitedSitesHost(Context context, Profile profile) { public MostVisitedSitesHost(Context context, Profile profile) {
LargeIconBridge largeIconBridge = LargeIconBridge largeIconBridge =
SuggestionsDependencyFactory.getInstance().createLargeIconBridge(profile); SuggestionsDependencyFactory.getInstance().createLargeIconBridge(profile);
...@@ -61,6 +72,56 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer { ...@@ -61,6 +72,56 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer {
} }
} }
/**
* Once new siteSuggestions info is available, call this function to update map and set and save
* data to the disk. If syncing with disk hasn't finished or there is a current task running,
* make this new task the pending task. Otherwise, make this new task the current task and run
* it.
* @param siteSuggestions The new SiteSuggestions.
*/
public void saveMostVisitedSitesInfo(List<SiteSuggestion> siteSuggestions) {
// Ensure that saving happens after map and set are updated. Use finishOneFileSaving() as
// callback to record when this current task has been finished.
Runnable newTask = () -> updateMapAndSetForNewSites(siteSuggestions, () -> {
MostVisitedSitesMetadataUtils.saveSuggestionListsToFile(
siteSuggestions, this::finishOneFileSaving);
mFaviconSaver.saveFaviconsToFile(
siteSuggestions, mUrlsToUpdateFavicon, this::finishOneFileSaving);
});
if (!mIsSynced || mCurrentTask != null) {
// Skip last mPendingTask which is not necessary to run.
mPendingTask = newTask;
mPendingFilesNeedToSaveCount = siteSuggestions.size() + 1;
} else {
// Assign newTask to mCurrentTask and run this task.
mCurrentTask = newTask;
mCurrentFilesNeedToSaveCount = siteSuggestions.size() + 1;
// Skip any pending task.
mPendingTask = null;
mPendingFilesNeedToSaveCount = 0;
Log.d(TAG, "Start a new task.");
mCurrentTask.run();
}
}
@Override
public void onSiteSuggestionsAvailable(List<SiteSuggestion> siteSuggestions) {
saveMostVisitedSitesInfo(siteSuggestions);
}
@Override
public void onIconMadeAvailable(String siteUrl) {}
/**
* Start the observer.
* @param maxResults The max number of sites to observe.
*/
public void startObserving(int maxResults) {
mMostVisitedSites.setObserver(this, maxResults);
}
/** /**
* Restore disk info to the mUrlToIDMap. * Restore disk info to the mUrlToIDMap.
*/ */
...@@ -85,32 +146,12 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer { ...@@ -85,32 +146,12 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer {
mUrlToIdMap.put(site.url, site.faviconId); mUrlToIdMap.put(site.url, site.faviconId);
} }
buildIdToUrlMap(); buildIdToUrlMap();
mIsSynced = true;
updatePendingToCurrent();
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
// TODO(spdonghao): Add a queue to cache the new SiteSuggestions task when there is a pending
// task that is updating the favicon files and metadata on disk. Reschedule the cached task when
// the pending task is completed.
@Override
public void onSiteSuggestionsAvailable(List<SiteSuggestion> siteSuggestions) {
updateMapAndSetForNewSites(siteSuggestions, () -> {
MostVisitedSitesMetadataUtils.saveSuggestionListsToFile(siteSuggestions, null);
mFaviconSaver.saveFaviconsToFile(siteSuggestions, mUrlsToUpdateFavicon);
});
}
@Override
public void onIconMadeAvailable(String siteUrl) {}
/**
* Start the observer.
* @param maxResults The max number of sites to observe.
*/
public void startObserving(int maxResults) {
mMostVisitedSites.setObserver(this, maxResults);
}
/** /**
* Update mUrlToIDMap and mUrlsToUpdateFavicon based on the new SiteSuggestions passed in. * Update mUrlToIDMap and mUrlsToUpdateFavicon based on the new SiteSuggestions passed in.
* @param newSuggestions The new SiteSuggestions. * @param newSuggestions The new SiteSuggestions.
...@@ -280,6 +321,27 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer { ...@@ -280,6 +321,27 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@VisibleForTesting
protected void updatePendingToCurrent() {
mCurrentTask = mPendingTask;
mCurrentFilesNeedToSaveCount = mPendingFilesNeedToSaveCount;
mPendingTask = null;
if (mCurrentTask != null) {
Log.d(TAG, "Start a new task.");
mCurrentTask.run();
}
}
private void finishOneFileSaving() {
ThreadUtils.assertOnUiThread();
mCurrentFilesNeedToSaveCount--;
// If there is no file needed to save for current task, update pending task to current.
if (mCurrentFilesNeedToSaveCount == 0) {
updatePendingToCurrent();
}
}
@VisibleForTesting @VisibleForTesting
protected Map<String, Integer> getUrlToIDMapForTesting() { protected Map<String, Integer> getUrlToIDMapForTesting() {
return mUrlToIdMap; return mUrlToIdMap;
...@@ -299,4 +361,29 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer { ...@@ -299,4 +361,29 @@ public class MostVisitedSitesHost implements MostVisitedSites.Observer {
protected static void setSkipRestoreFromDiskForTesting() { protected static void setSkipRestoreFromDiskForTesting() {
sSkipRestoreFromDiskForTests = true; sSkipRestoreFromDiskForTests = true;
} }
@VisibleForTesting
public void setIsSyncedForTesting(boolean isSynced) {
mIsSynced = isSynced;
}
@VisibleForTesting
public int getCurrentFilesNeedToSaveCountForTesting() {
return mCurrentFilesNeedToSaveCount;
}
@VisibleForTesting
public int getPendingFilesNeedToSaveCountForTesting() {
return mPendingFilesNeedToSaveCount;
}
@VisibleForTesting
public void setCurrentTaskForTesting(Runnable currentTask) {
mCurrentTask = currentTask;
}
@VisibleForTesting
public void setPendingTaskForTesting(Runnable pendingTask) {
mPendingTask = pendingTask;
}
} }
...@@ -80,7 +80,7 @@ public class MostVisitedSitesFaviconHelperTest { ...@@ -80,7 +80,7 @@ public class MostVisitedSitesFaviconHelperTest {
TestThreadUtils.runOnUiThreadBlocking( TestThreadUtils.runOnUiThreadBlocking(
() ()
-> mMostVisitedSitesFaviconHelper.saveFaviconsToFile( -> mMostVisitedSitesFaviconHelper.saveFaviconsToFile(
mExpectedSiteSuggestions, urlsToUpdate)); mExpectedSiteSuggestions, urlsToUpdate, null));
// Wait util the file number equals to expected one. // Wait util the file number equals to expected one.
CriteriaHelper.pollInstrumentationThread(Criteria.equals( CriteriaHelper.pollInstrumentationThread(Criteria.equals(
......
...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.suggestions.mostvisited; ...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.suggestions.mostvisited;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
...@@ -149,6 +150,71 @@ public class MostVisitedSitesHostTest { ...@@ -149,6 +150,71 @@ public class MostVisitedSitesHostTest {
(int) mMostVisitedSitesHost.getUrlToIDMapForTesting().get("https://www.2.com")); (int) mMostVisitedSitesHost.getUrlToIDMapForTesting().get("https://www.2.com"));
} }
/**
* Test when synchronization of metadata stored on disk hasn't finished yet, all coming tasks
* will be set as the pending task. Besides, the latest task will override the old one.
*/
@Test
@SmallTest
public void testSyncNotFinished() {
List<SiteSuggestion> newTopSites1 = createFakeSiteSuggestions1();
List<SiteSuggestion> newTopSites2 = createFakeSiteSuggestions2();
mMostVisitedSitesHost.setIsSyncedForTesting(false);
// If restoring from disk is not finished, all coming tasks should be set as the pending
// task.
mMostVisitedSitesHost.saveMostVisitedSitesInfo(newTopSites1);
mMostVisitedSitesHost.saveMostVisitedSitesInfo(newTopSites2);
// newTopSites1 should be skipped and newTopSites2 should be the pending task.
assertEquals(newTopSites2.size(),
mMostVisitedSitesHost.getPendingFilesNeedToSaveCountForTesting() - 1);
}
/**
* Test when current task is not finished, all coming tasks will be set as the pending task.
* Besides, the latest task will override the old one.
*/
@Test
@SmallTest
public void testCurrentNotNull() {
List<SiteSuggestion> newTopSites1 = createFakeSiteSuggestions1();
List<SiteSuggestion> newTopSites2 = createFakeSiteSuggestions2();
mMostVisitedSitesHost.setIsSyncedForTesting(true);
mMostVisitedSitesHost.setCurrentTaskForTesting(() -> {});
// If current task is not null, all saving tasks should be set as pending task.
mMostVisitedSitesHost.saveMostVisitedSitesInfo(newTopSites1);
mMostVisitedSitesHost.saveMostVisitedSitesInfo(newTopSites2);
// newTopSites1 should be skipped and newTopSites2 should be the pending task.
assertEquals(newTopSites2.size(),
mMostVisitedSitesHost.getPendingFilesNeedToSaveCountForTesting() - 1);
}
/**
* Test when current task is finished, the pending task should be set as current task and run.
*/
@Test
@SmallTest
public void testTasksContinuity() {
AtomicBoolean isPendingRun = new AtomicBoolean(false);
// Set and run current task.
mMostVisitedSitesHost.setIsSyncedForTesting(true);
mMostVisitedSitesHost.setCurrentTaskForTesting(null);
mMostVisitedSitesHost.saveMostVisitedSitesInfo(createFakeSiteSuggestions1());
// When current task is not finished, set pending task.
assertTrue(mMostVisitedSitesHost.getCurrentFilesNeedToSaveCountForTesting() > 0);
mMostVisitedSitesHost.setPendingTaskForTesting(() -> isPendingRun.set(true));
// isPendingRun should eventually become true.
CriteriaHelper.pollInstrumentationThread(isPendingRun::get);
}
private void checkMapAndSet(Set<String> expectedUrlsToFetchIcon, Set<String> expectedUrlsInMap, private void checkMapAndSet(Set<String> expectedUrlsToFetchIcon, Set<String> expectedUrlsInMap,
Set<Integer> expectedIdsInMap, List<SiteSuggestion> newSiteSuggestions) { Set<Integer> expectedIdsInMap, List<SiteSuggestion> newSiteSuggestions) {
// Check whether mExpectedSiteSuggestions' faviconIDs have been updated. // Check whether mExpectedSiteSuggestions' faviconIDs have been updated.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment