|
|
@ -1,31 +1,28 @@ |
|
|
|
package acr.browser.lightning.search; |
|
|
|
package acr.browser.lightning.search; |
|
|
|
|
|
|
|
|
|
|
|
import android.app.Application; |
|
|
|
import android.app.Application; |
|
|
|
import android.content.Context; |
|
|
|
|
|
|
|
import android.net.ConnectivityManager; |
|
|
|
|
|
|
|
import android.net.NetworkInfo; |
|
|
|
|
|
|
|
import android.support.annotation.NonNull; |
|
|
|
import android.support.annotation.NonNull; |
|
|
|
import android.support.annotation.Nullable; |
|
|
|
import android.support.annotation.Nullable; |
|
|
|
import android.text.TextUtils; |
|
|
|
import android.text.TextUtils; |
|
|
|
import android.util.Log; |
|
|
|
import android.util.Log; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
import java.io.File; |
|
|
|
import java.io.FileInputStream; |
|
|
|
import java.io.IOException; |
|
|
|
import java.io.FileOutputStream; |
|
|
|
|
|
|
|
import java.io.InputStream; |
|
|
|
import java.io.InputStream; |
|
|
|
import java.io.UnsupportedEncodingException; |
|
|
|
import java.io.UnsupportedEncodingException; |
|
|
|
import java.net.HttpURLConnection; |
|
|
|
|
|
|
|
import java.net.URL; |
|
|
|
import java.net.URL; |
|
|
|
import java.net.URLEncoder; |
|
|
|
import java.net.URLEncoder; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Locale; |
|
|
|
import java.util.Locale; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.zip.GZIPInputStream; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import acr.browser.lightning.database.HistoryItem; |
|
|
|
import acr.browser.lightning.database.HistoryItem; |
|
|
|
|
|
|
|
import acr.browser.lightning.utils.FileUtils; |
|
|
|
import acr.browser.lightning.utils.Utils; |
|
|
|
import acr.browser.lightning.utils.Utils; |
|
|
|
|
|
|
|
import okhttp3.Cache; |
|
|
|
import okhttp3.CacheControl; |
|
|
|
import okhttp3.CacheControl; |
|
|
|
|
|
|
|
import okhttp3.Interceptor; |
|
|
|
import okhttp3.OkHttpClient; |
|
|
|
import okhttp3.OkHttpClient; |
|
|
|
import okhttp3.Request; |
|
|
|
import okhttp3.Request; |
|
|
|
import okhttp3.Response; |
|
|
|
import okhttp3.Response; |
|
|
@ -35,26 +32,27 @@ abstract class BaseSuggestionsModel { |
|
|
|
private static final String TAG = BaseSuggestionsModel.class.getSimpleName(); |
|
|
|
private static final String TAG = BaseSuggestionsModel.class.getSimpleName(); |
|
|
|
|
|
|
|
|
|
|
|
static final int MAX_RESULTS = 5; |
|
|
|
static final int MAX_RESULTS = 5; |
|
|
|
private static final long INTERVAL_DAY = TimeUnit.DAYS.toMillis(1); |
|
|
|
private static final long INTERVAL_DAY = TimeUnit.DAYS.toSeconds(1); |
|
|
|
@NonNull private static final String DEFAULT_LANGUAGE = "en"; |
|
|
|
@NonNull private static final String DEFAULT_LANGUAGE = "en"; |
|
|
|
@Nullable private static String sLanguage; |
|
|
|
@Nullable private static String sLanguage; |
|
|
|
@NonNull private final Application mApplication; |
|
|
|
@NonNull private final OkHttpClient mHttpClient; |
|
|
|
@NonNull private final OkHttpClient mHttpClient = new OkHttpClient(); |
|
|
|
|
|
|
|
@NonNull private final CacheControl mCacheControl; |
|
|
|
@NonNull private final CacheControl mCacheControl; |
|
|
|
@NonNull private final ConnectivityManager mConnectivityManager; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
|
protected abstract String createQueryUrl(@NonNull String query, @NonNull String language); |
|
|
|
protected abstract String createQueryUrl(@NonNull String query, @NonNull String language); |
|
|
|
|
|
|
|
|
|
|
|
protected abstract void parseResults(@NonNull FileInputStream inputStream, @NonNull List<HistoryItem> results) throws Exception; |
|
|
|
protected abstract void parseResults(@NonNull InputStream inputStream, @NonNull List<HistoryItem> results) throws Exception; |
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
|
protected abstract String getEncoding(); |
|
|
|
protected abstract String getEncoding(); |
|
|
|
|
|
|
|
|
|
|
|
BaseSuggestionsModel(@NonNull Application application) { |
|
|
|
BaseSuggestionsModel(@NonNull Application application) { |
|
|
|
mApplication = application; |
|
|
|
File suggestionsCache = new File(application.getCacheDir(), "suggestion_responses"); |
|
|
|
|
|
|
|
mHttpClient = new OkHttpClient.Builder() |
|
|
|
|
|
|
|
.cache(new Cache(suggestionsCache, FileUtils.megabytesToBytes(1))) |
|
|
|
|
|
|
|
.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR) |
|
|
|
|
|
|
|
.build(); |
|
|
|
mCacheControl = new CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build(); |
|
|
|
mCacheControl = new CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build(); |
|
|
|
mConnectivityManager = getConnectivityManager(mApplication); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
@ -76,20 +74,18 @@ abstract class BaseSuggestionsModel { |
|
|
|
} catch (UnsupportedEncodingException e) { |
|
|
|
} catch (UnsupportedEncodingException e) { |
|
|
|
Log.e(TAG, "Unable to encode the URL", e); |
|
|
|
Log.e(TAG, "Unable to encode the URL", e); |
|
|
|
} |
|
|
|
} |
|
|
|
File cache = downloadSuggestionsForQuery(query, getLanguage(), mApplication); |
|
|
|
InputStream inputStream = downloadSuggestionsForQuery(query, getLanguage()); |
|
|
|
if (!cache.exists()) { |
|
|
|
if (inputStream == null) { |
|
|
|
// There are no suggestions for this query, return an empty list.
|
|
|
|
// There are no suggestions for this query, return an empty list.
|
|
|
|
return filter; |
|
|
|
return filter; |
|
|
|
} |
|
|
|
} |
|
|
|
FileInputStream fileInput = null; |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
fileInput = new FileInputStream(cache); |
|
|
|
parseResults(inputStream, filter); |
|
|
|
parseResults(fileInput, filter); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
} catch (Exception e) { |
|
|
|
Log.e(TAG, "Unable to parse results", e); |
|
|
|
Log.e(TAG, "Unable to parse results", e); |
|
|
|
return filter; |
|
|
|
return filter; |
|
|
|
} finally { |
|
|
|
} finally { |
|
|
|
Utils.close(fileInput); |
|
|
|
Utils.close(inputStream); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return filter; |
|
|
|
return filter; |
|
|
@ -102,68 +98,37 @@ abstract class BaseSuggestionsModel { |
|
|
|
* @param query the query to get suggestions for |
|
|
|
* @param query the query to get suggestions for |
|
|
|
* @return the cache file containing the suggestions |
|
|
|
* @return the cache file containing the suggestions |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@NonNull |
|
|
|
@Nullable |
|
|
|
private File downloadSuggestionsForQuery(@NonNull String query, String language, @NonNull Application app) { |
|
|
|
private InputStream downloadSuggestionsForQuery(@NonNull String query, String language) { |
|
|
|
String queryUrl = createQueryUrl(query, language); |
|
|
|
String queryUrl = createQueryUrl(query, language); |
|
|
|
File cacheFile = new File(app.getCacheDir(), queryUrl.hashCode() + SuggestionsAdapter.CACHE_FILE_TYPE); |
|
|
|
|
|
|
|
if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) { |
|
|
|
|
|
|
|
return cacheFile; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!isNetworkConnected()) { |
|
|
|
|
|
|
|
return cacheFile; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
InputStream in = null; |
|
|
|
|
|
|
|
FileOutputStream fos = null; |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
URL url = new URL(queryUrl); |
|
|
|
URL url = new URL(queryUrl); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// OkHttp automatically gzips requests
|
|
|
|
Request suggestionsRequest = new Request.Builder().url(url) |
|
|
|
Request suggestionsRequest = new Request.Builder().url(url) |
|
|
|
.addHeader("Accept-Encoding", "gzip") |
|
|
|
|
|
|
|
.addHeader("Accept-Charset", getEncoding()) |
|
|
|
.addHeader("Accept-Charset", getEncoding()) |
|
|
|
.cacheControl(mCacheControl) |
|
|
|
.cacheControl(mCacheControl) |
|
|
|
.build(); |
|
|
|
.build(); |
|
|
|
|
|
|
|
|
|
|
|
Response suggestionsResponse = mHttpClient.newCall(suggestionsRequest).execute(); |
|
|
|
Response suggestionsResponse = mHttpClient.newCall(suggestionsRequest).execute(); |
|
|
|
|
|
|
|
|
|
|
|
if (suggestionsResponse.code() >= HttpURLConnection.HTTP_MULT_CHOICE || |
|
|
|
return suggestionsResponse.body().byteStream(); |
|
|
|
suggestionsResponse.code() < HttpURLConnection.HTTP_OK) { |
|
|
|
|
|
|
|
Log.e(TAG, "Search API Responded with code: " + suggestionsResponse.code()); |
|
|
|
|
|
|
|
suggestionsResponse.body().close(); |
|
|
|
|
|
|
|
return cacheFile; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
in = suggestionsResponse.body().byteStream(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (in != null) { |
|
|
|
|
|
|
|
in = new GZIPInputStream(in); |
|
|
|
|
|
|
|
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
|
|
|
|
|
|
fos = new FileOutputStream(cacheFile); |
|
|
|
|
|
|
|
int buffer; |
|
|
|
|
|
|
|
while ((buffer = in.read()) != -1) { |
|
|
|
|
|
|
|
fos.write(buffer); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
fos.flush(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
suggestionsResponse.body().close(); |
|
|
|
|
|
|
|
cacheFile.setLastModified(System.currentTimeMillis()); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
} catch (Exception e) { |
|
|
|
Log.w(TAG, "Problem getting search suggestions", e); |
|
|
|
Log.e(TAG, "Problem getting search suggestions", e); |
|
|
|
} finally { |
|
|
|
|
|
|
|
Utils.close(in); |
|
|
|
|
|
|
|
Utils.close(fos); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return cacheFile; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private boolean isNetworkConnected() { |
|
|
|
return null; |
|
|
|
NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); |
|
|
|
|
|
|
|
return networkInfo != null && networkInfo.isConnected(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { |
|
|
|
private static ConnectivityManager getConnectivityManager(@NonNull Context context) { |
|
|
|
@Override |
|
|
|
return (ConnectivityManager) context |
|
|
|
public Response intercept(Chain chain) throws IOException { |
|
|
|
.getApplicationContext() |
|
|
|
Response originalResponse = chain.proceed(chain.request()); |
|
|
|
.getSystemService(Context.CONNECTIVITY_SERVICE); |
|
|
|
return originalResponse.newBuilder() |
|
|
|
|
|
|
|
.header("cache-control", "max-age=" + INTERVAL_DAY + ", max-stale=" + INTERVAL_DAY) |
|
|
|
|
|
|
|
.build(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|