Anthony Restaino
8 years ago
3 changed files with 94 additions and 406 deletions
@ -1,202 +1,55 @@ |
|||||||
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.text.TextUtils; |
|
||||||
import android.util.Log; |
|
||||||
|
|
||||||
import org.json.JSONArray; |
import org.json.JSONArray; |
||||||
import org.json.JSONObject; |
import org.json.JSONObject; |
||||||
|
|
||||||
import java.io.File; |
|
||||||
import java.io.FileInputStream; |
import java.io.FileInputStream; |
||||||
import java.io.FileOutputStream; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.io.UnsupportedEncodingException; |
|
||||||
import java.net.HttpURLConnection; |
|
||||||
import java.net.URL; |
|
||||||
import java.net.URLEncoder; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.List; |
import java.util.List; |
||||||
import java.util.Locale; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
import java.util.zip.GZIPInputStream; |
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection; |
|
||||||
|
|
||||||
import acr.browser.lightning.R; |
import acr.browser.lightning.R; |
||||||
import acr.browser.lightning.database.HistoryItem; |
import acr.browser.lightning.database.HistoryItem; |
||||||
import acr.browser.lightning.utils.FileUtils; |
import acr.browser.lightning.utils.FileUtils; |
||||||
import acr.browser.lightning.utils.Utils; |
|
||||||
|
|
||||||
/** |
|
||||||
* Copyright 9/10/2016 Anthony Restaino |
|
||||||
* <p/> |
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
* you may not use this file except in compliance with the License. |
|
||||||
* You may obtain a copy of the License at |
|
||||||
* <p/> |
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* <p/> |
|
||||||
* Unless required by applicable law or agreed to in writing, software |
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
* See the License for the specific language governing permissions and |
|
||||||
* limitations under the License. |
|
||||||
*/ |
|
||||||
public final class DuckSuggestionsTask { |
|
||||||
|
|
||||||
private static final String TAG = RetrieveSuggestionsTask.class.getSimpleName(); |
public final class DuckSuggestionsTask extends BaseSuggestionsTask { |
||||||
|
|
||||||
private static final String ENCODING = "UTF-8"; |
private static final String ENCODING = "UTF-8"; |
||||||
private static final long INTERVAL_DAY = TimeUnit.DAYS.toMillis(1); |
|
||||||
private static final String DEFAULT_LANGUAGE = "en"; |
|
||||||
@Nullable private static String sLanguage; |
|
||||||
@NonNull private final SuggestionsResult mResultCallback; |
|
||||||
@NonNull private final Application mApplication; |
|
||||||
@NonNull private final String mSearchSubtitle; |
@NonNull private final String mSearchSubtitle; |
||||||
@NonNull private String mQuery; |
|
||||||
|
|
||||||
DuckSuggestionsTask(@NonNull String query, |
DuckSuggestionsTask(@NonNull String query, |
||||||
@NonNull Application application, |
@NonNull Application application, |
||||||
@NonNull SuggestionsResult callback) { |
@NonNull SuggestionsResult callback) { |
||||||
mQuery = query; |
super(query, application, callback); |
||||||
mResultCallback = callback; |
mSearchSubtitle = application.getString(R.string.suggestion); |
||||||
mApplication = application; |
|
||||||
mSearchSubtitle = mApplication.getString(R.string.suggestion); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
private static synchronized String getLanguage() { |
|
||||||
if (sLanguage == null) { |
|
||||||
sLanguage = Locale.getDefault().getLanguage(); |
|
||||||
} |
|
||||||
if (TextUtils.isEmpty(sLanguage)) { |
|
||||||
sLanguage = DEFAULT_LANGUAGE; |
|
||||||
} |
|
||||||
return sLanguage; |
|
||||||
} |
} |
||||||
|
|
||||||
@NonNull |
@Override |
||||||
private static String getQueryUrl(@NonNull String query) { |
protected String getQueryUrl(@NonNull String query, @NonNull String language) { |
||||||
return "https://duckduckgo.com/ac/?q=" + query; |
return "https://duckduckgo.com/ac/?q=" + query; |
||||||
} |
} |
||||||
|
|
||||||
void run() { |
@Override |
||||||
List<HistoryItem> filter = new ArrayList<>(5); |
protected void parseResults(FileInputStream inputStream, List<HistoryItem> results) throws Exception { |
||||||
try { |
String content = FileUtils.readStringFromFile(inputStream, ENCODING); |
||||||
mQuery = URLEncoder.encode(mQuery, ENCODING); |
JSONArray jsonArray = new JSONArray(content); |
||||||
} catch (UnsupportedEncodingException e) { |
int counter = 0; |
||||||
e.printStackTrace(); |
for (int n = 0, size = jsonArray.length(); n < size; n++) { |
||||||
} |
JSONObject object = jsonArray.getJSONObject(n); |
||||||
File cache = downloadSuggestionsForQuery(mQuery, getLanguage(), mApplication); |
String suggestion = object.getString("phrase"); |
||||||
if (!cache.exists()) { |
results.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', |
||||||
post(filter); |
suggestion, R.drawable.ic_search)); |
||||||
return; |
counter++; |
||||||
} |
if (counter >= 5) { |
||||||
InputStream fileInput = null; |
break; |
||||||
try { |
|
||||||
fileInput = new FileInputStream(cache); |
|
||||||
String content = FileUtils.readStringFromFile(fileInput, ENCODING); |
|
||||||
JSONArray jsonArray = new JSONArray(content); |
|
||||||
int counter = 0; |
|
||||||
for (int n = 0, size = jsonArray.length(); n < size; n++) { |
|
||||||
JSONObject object = jsonArray.getJSONObject(n); |
|
||||||
String suggestion = object.getString("phrase"); |
|
||||||
filter.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', |
|
||||||
suggestion, R.drawable.ic_search)); |
|
||||||
counter++; |
|
||||||
if (counter >= 5) { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (Exception e) { |
|
||||||
post(filter); |
|
||||||
e.printStackTrace(); |
|
||||||
return; |
|
||||||
} finally { |
|
||||||
Utils.close(fileInput); |
|
||||||
} |
|
||||||
post(filter); |
|
||||||
} |
|
||||||
|
|
||||||
private void post(@NonNull List<HistoryItem> result) { |
|
||||||
mResultCallback.resultReceived(result); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method downloads the search suggestions for the specific query. |
|
||||||
* NOTE: This is a blocking operation, do not run on the UI thread. |
|
||||||
* |
|
||||||
* @param query the query to get suggestions for |
|
||||||
* @return the cache file containing the suggestions |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
private static File downloadSuggestionsForQuery(@NonNull String query, String language, @NonNull Application app) { |
|
||||||
String queryUrl = getQueryUrl(query); |
|
||||||
File cacheFile = new File(app.getCacheDir(), queryUrl.hashCode() + Suggestions.CACHE_FILE_TYPE); |
|
||||||
if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) { |
|
||||||
return cacheFile; |
|
||||||
} |
|
||||||
if (!isNetworkConnected(app)) { |
|
||||||
return cacheFile; |
|
||||||
} |
|
||||||
InputStream in = null; |
|
||||||
FileOutputStream fos = null; |
|
||||||
try { |
|
||||||
URL url = new URL(queryUrl); |
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
|
||||||
connection.setDoInput(true); |
|
||||||
connection.setRequestProperty("Accept-Encoding", "gzip"); |
|
||||||
connection.connect(); |
|
||||||
if (connection.getResponseCode() >= HttpURLConnection.HTTP_MULT_CHOICE || |
|
||||||
connection.getResponseCode() < HttpURLConnection.HTTP_OK) { |
|
||||||
Log.e(TAG, "Search API Responded with code: " + connection.getResponseCode()); |
|
||||||
connection.disconnect(); |
|
||||||
return cacheFile; |
|
||||||
} |
} |
||||||
|
|
||||||
in = connection.getInputStream(); |
|
||||||
|
|
||||||
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(); |
|
||||||
} |
|
||||||
connection.disconnect(); |
|
||||||
cacheFile.setLastModified(System.currentTimeMillis()); |
|
||||||
} catch (Exception e) { |
|
||||||
Log.w(TAG, "Problem getting search suggestions", e); |
|
||||||
} finally { |
|
||||||
Utils.close(in); |
|
||||||
Utils.close(fos); |
|
||||||
} |
} |
||||||
return cacheFile; |
|
||||||
} |
} |
||||||
|
|
||||||
private static boolean isNetworkConnected(@NonNull Context context) { |
@Override |
||||||
NetworkInfo networkInfo = getActiveNetworkInfo(context); |
protected String getEncoding() { |
||||||
return networkInfo != null && networkInfo.isConnected(); |
return ENCODING; |
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) { |
|
||||||
ConnectivityManager connectivity = (ConnectivityManager) context |
|
||||||
.getApplicationContext() |
|
||||||
.getSystemService(Context.CONNECTIVITY_SERVICE); |
|
||||||
if (connectivity == null) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
return connectivity.getActiveNetworkInfo(); |
|
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
@ -1,204 +1,72 @@ |
|||||||
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.util.Log; |
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser; |
import org.xmlpull.v1.XmlPullParser; |
||||||
import org.xmlpull.v1.XmlPullParserException; |
import org.xmlpull.v1.XmlPullParserException; |
||||||
import org.xmlpull.v1.XmlPullParserFactory; |
import org.xmlpull.v1.XmlPullParserFactory; |
||||||
|
|
||||||
import java.io.BufferedInputStream; |
import java.io.BufferedInputStream; |
||||||
import java.io.File; |
|
||||||
import java.io.FileInputStream; |
import java.io.FileInputStream; |
||||||
import java.io.FileOutputStream; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.io.UnsupportedEncodingException; |
|
||||||
import java.net.HttpURLConnection; |
|
||||||
import java.net.URL; |
|
||||||
import java.net.URLEncoder; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.List; |
import java.util.List; |
||||||
import java.util.Locale; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
import java.util.zip.GZIPInputStream; |
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection; |
|
||||||
|
|
||||||
import acr.browser.lightning.R; |
import acr.browser.lightning.R; |
||||||
import acr.browser.lightning.database.HistoryItem; |
import acr.browser.lightning.database.HistoryItem; |
||||||
import acr.browser.lightning.utils.Utils; |
|
||||||
|
|
||||||
public class GoogleSuggestionsTask { |
|
||||||
|
|
||||||
private static final String TAG = RetrieveSuggestionsTask.class.getSimpleName(); |
public class GoogleSuggestionsTask extends BaseSuggestionsTask { |
||||||
|
|
||||||
private static final String ENCODING = "ISO-8859-1"; |
private static final String ENCODING = "ISO-8859-1"; |
||||||
private static final long INTERVAL_DAY = TimeUnit.DAYS.toMillis(1); |
|
||||||
private static final String DEFAULT_LANGUAGE = "en"; |
|
||||||
@Nullable private static XmlPullParser sXpp; |
@Nullable private static XmlPullParser sXpp; |
||||||
@Nullable private static String sLanguage; |
|
||||||
@NonNull private final SuggestionsResult mResultCallback; |
|
||||||
@NonNull private final Application mApplication; |
|
||||||
@NonNull private final String mSearchSubtitle; |
@NonNull private final String mSearchSubtitle; |
||||||
@NonNull private String mQuery; |
|
||||||
|
|
||||||
GoogleSuggestionsTask(@NonNull String query, |
GoogleSuggestionsTask(@NonNull String query, |
||||||
@NonNull Application application, |
@NonNull Application application, |
||||||
@NonNull SuggestionsResult callback) { |
@NonNull SuggestionsResult callback) { |
||||||
mQuery = query; |
super(query, application, callback); |
||||||
mResultCallback = callback; |
mSearchSubtitle = application.getString(R.string.suggestion); |
||||||
mApplication = application; |
|
||||||
mSearchSubtitle = mApplication.getString(R.string.suggestion); |
|
||||||
} |
|
||||||
|
|
||||||
@NonNull |
|
||||||
private static synchronized String getLanguage() { |
|
||||||
if (sLanguage == null) { |
|
||||||
sLanguage = Locale.getDefault().getLanguage(); |
|
||||||
} |
|
||||||
if (TextUtils.isEmpty(sLanguage)) { |
|
||||||
sLanguage = DEFAULT_LANGUAGE; |
|
||||||
} |
|
||||||
return sLanguage; |
|
||||||
} |
} |
||||||
|
|
||||||
@NonNull |
@NonNull |
||||||
private static String getQueryUrl(@NonNull String query, @NonNull String language) { |
protected String getQueryUrl(@NonNull String query, @NonNull String language) { |
||||||
return "https://suggestqueries.google.com/complete/search?output=toolbar&hl=" |
return "https://suggestqueries.google.com/complete/search?output=toolbar&hl=" |
||||||
+ language + "&q=" + query; |
+ language + "&q=" + query; |
||||||
} |
} |
||||||
|
|
||||||
@NonNull |
@Override |
||||||
private static synchronized XmlPullParser getParser() throws XmlPullParserException { |
protected void parseResults(FileInputStream inputStream, List<HistoryItem> results) throws Exception { |
||||||
if (sXpp == null) { |
BufferedInputStream fileInput = new BufferedInputStream(inputStream); |
||||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); |
XmlPullParser parser = getParser(); |
||||||
factory.setNamespaceAware(true); |
parser.setInput(fileInput, ENCODING); |
||||||
sXpp = factory.newPullParser(); |
int eventType = parser.getEventType(); |
||||||
} |
int counter = 0; |
||||||
return sXpp; |
while (eventType != XmlPullParser.END_DOCUMENT) { |
||||||
} |
if (eventType == XmlPullParser.START_TAG && "suggestion".equals(parser.getName())) { |
||||||
|
String suggestion = parser.getAttributeValue(null, "data"); |
||||||
void run() { |
results.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', |
||||||
List<HistoryItem> filter = new ArrayList<>(5); |
suggestion, R.drawable.ic_search)); |
||||||
try { |
counter++; |
||||||
mQuery = URLEncoder.encode(mQuery, ENCODING); |
if (counter >= 5) { |
||||||
} catch (UnsupportedEncodingException e) { |
break; |
||||||
e.printStackTrace(); |
|
||||||
} |
|
||||||
File cache = downloadSuggestionsForQuery(mQuery, getLanguage(), mApplication); |
|
||||||
if (!cache.exists()) { |
|
||||||
post(filter); |
|
||||||
return; |
|
||||||
} |
|
||||||
InputStream fileInput = null; |
|
||||||
try { |
|
||||||
fileInput = new BufferedInputStream(new FileInputStream(cache)); |
|
||||||
XmlPullParser parser = getParser(); |
|
||||||
parser.setInput(fileInput, ENCODING); |
|
||||||
int eventType = parser.getEventType(); |
|
||||||
int counter = 0; |
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) { |
|
||||||
if (eventType == XmlPullParser.START_TAG && "suggestion".equals(parser.getName())) { |
|
||||||
String suggestion = parser.getAttributeValue(null, "data"); |
|
||||||
filter.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', |
|
||||||
suggestion, R.drawable.ic_search)); |
|
||||||
counter++; |
|
||||||
if (counter >= 5) { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
} |
||||||
eventType = parser.next(); |
|
||||||
} |
} |
||||||
} catch (Exception e) { |
eventType = parser.next(); |
||||||
e.printStackTrace(); |
|
||||||
post(filter); |
|
||||||
return; |
|
||||||
} finally { |
|
||||||
Utils.close(fileInput); |
|
||||||
} |
} |
||||||
post(filter); |
|
||||||
} |
} |
||||||
|
|
||||||
private void post(@NonNull List<HistoryItem> result) { |
@Override |
||||||
mResultCallback.resultReceived(result); |
protected String getEncoding() { |
||||||
|
return ENCODING; |
||||||
} |
} |
||||||
|
|
||||||
/** |
|
||||||
* This method downloads the search suggestions for the specific query. |
|
||||||
* NOTE: This is a blocking operation, do not run on the UI thread. |
|
||||||
* |
|
||||||
* @param query the query to get suggestions for |
|
||||||
* @return the cache file containing the suggestions |
|
||||||
*/ |
|
||||||
@NonNull |
@NonNull |
||||||
private static File downloadSuggestionsForQuery(@NonNull String query, String language, @NonNull Application app) { |
private static synchronized XmlPullParser getParser() throws XmlPullParserException { |
||||||
String queryUrl = getQueryUrl(query, language); |
if (sXpp == null) { |
||||||
File cacheFile = new File(app.getCacheDir(), queryUrl.hashCode() + Suggestions.CACHE_FILE_TYPE); |
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); |
||||||
if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) { |
factory.setNamespaceAware(true); |
||||||
return cacheFile; |
sXpp = factory.newPullParser(); |
||||||
} |
|
||||||
if (!isNetworkConnected(app)) { |
|
||||||
return cacheFile; |
|
||||||
} |
|
||||||
InputStream in = null; |
|
||||||
FileOutputStream fos = null; |
|
||||||
try { |
|
||||||
// Old API that doesn't support HTTPS
|
|
||||||
// http://google.com/complete/search?q= + query + &output=toolbar&hl= + language
|
|
||||||
URL url = new URL(queryUrl); |
|
||||||
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); |
|
||||||
connection.setDoInput(true); |
|
||||||
connection.setRequestProperty("Accept-Encoding", "gzip"); |
|
||||||
connection.connect(); |
|
||||||
if (connection.getResponseCode() >= HttpURLConnection.HTTP_MULT_CHOICE || |
|
||||||
connection.getResponseCode() < HttpURLConnection.HTTP_OK) { |
|
||||||
Log.e(TAG, "Search API Responded with code: " + connection.getResponseCode()); |
|
||||||
connection.disconnect(); |
|
||||||
return cacheFile; |
|
||||||
} |
|
||||||
in = connection.getInputStream(); |
|
||||||
|
|
||||||
if (in != null) { |
|
||||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
||||||
in = new GZIPInputStream(in); |
|
||||||
fos = new FileOutputStream(cacheFile); |
|
||||||
int buffer; |
|
||||||
while ((buffer = in.read()) != -1) { |
|
||||||
fos.write(buffer); |
|
||||||
} |
|
||||||
fos.flush(); |
|
||||||
} |
|
||||||
connection.disconnect(); |
|
||||||
cacheFile.setLastModified(System.currentTimeMillis()); |
|
||||||
} catch (Exception e) { |
|
||||||
Log.w(TAG, "Problem getting search suggestions", e); |
|
||||||
} finally { |
|
||||||
Utils.close(in); |
|
||||||
Utils.close(fos); |
|
||||||
} |
|
||||||
return cacheFile; |
|
||||||
} |
|
||||||
|
|
||||||
private static boolean isNetworkConnected(@NonNull Context context) { |
|
||||||
NetworkInfo networkInfo = getActiveNetworkInfo(context); |
|
||||||
return networkInfo != null && networkInfo.isConnected(); |
|
||||||
} |
|
||||||
|
|
||||||
@Nullable |
|
||||||
private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) { |
|
||||||
ConnectivityManager connectivity = (ConnectivityManager) context |
|
||||||
.getApplicationContext() |
|
||||||
.getSystemService(Context.CONNECTIVITY_SERVICE); |
|
||||||
if (connectivity == null) { |
|
||||||
return null; |
|
||||||
} |
} |
||||||
return connectivity.getActiveNetworkInfo(); |
return sXpp; |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
Loading…
Reference in new issue