1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.gecko;
6 
7 import java.io.BufferedInputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.net.HttpURLConnection;
11 import java.net.URL;
12 import java.net.URLEncoder;
13 import java.util.ArrayList;
14 
15 import org.json.JSONArray;
16 import org.mozilla.gecko.annotation.RobocopTarget;
17 import org.mozilla.gecko.util.HardwareUtils;
18 
19 import android.content.Context;
20 import android.text.TextUtils;
21 import android.util.Log;
22 import org.mozilla.gecko.util.NetworkUtils;
23 
24 /**
25  * Use network-based search suggestions.
26  */
27 public class SuggestClient {
28     private static final String LOGTAG = "GeckoSuggestClient";
29 
30     // This should go through GeckoInterface to get the UA, but the search activity
addDisplay(int displayType, int width, int height, float density)31     // doesn't use a GeckoView yet. Until it does, get the UA directly.
32     private static final String USER_AGENT = HardwareUtils.isTablet() ?
33         AppConstants.USER_AGENT_FENNEC_TABLET : AppConstants.USER_AGENT_FENNEC_MOBILE;
34 
35     private final Context mContext;
36     private final int mTimeout;
37 
38     // should contain the string "__searchTerms__", which is replaced with the query
39     private final String mSuggestTemplate;
40 
41     // the maximum number of suggestions to return
removeDisplay(int screenId)42     private final int mMaxResults;
43 
44     // used by robocop for testing
45     private final boolean mCheckNetwork;
46 
47     // used to make suggestions appear instantly after opt-in
48     private String mPrevQuery;
49     private ArrayList<String> mPrevResults;
50 
51     @RobocopTarget
52     public SuggestClient(Context context, String suggestTemplate, int timeout, int maxResults, boolean checkNetwork) {
53         mContext = context;
54         mMaxResults = maxResults;
55         mSuggestTemplate = suggestTemplate;
56         mTimeout = timeout;
57         mCheckNetwork = checkNetwork;
58     }
59 
60     public String getSuggestTemplate() {
61         return mSuggestTemplate;
62     }
63 
64     /**
65      * Queries for a given search term and returns an ArrayList of suggestions.
66      */
67     public ArrayList<String> query(String query) {
68         if (query.equals(mPrevQuery))
69             return mPrevResults;
70 
71         ArrayList<String> suggestions = new ArrayList<String>();
72         if (TextUtils.isEmpty(mSuggestTemplate) || TextUtils.isEmpty(query)) {
73             return suggestions;
74         }
75 
76         if (!NetworkUtils.isConnected(mContext) && mCheckNetwork) {
77             Log.i(LOGTAG, "Not connected to network");
78             return suggestions;
79         }
80 
81         try {
82             String encoded = URLEncoder.encode(query, "UTF-8");
83             String suggestUri = mSuggestTemplate.replace("__searchTerms__", encoded);
84 
85             URL url = new URL(suggestUri);
86             String json = null;
87             HttpURLConnection urlConnection = null;
88             InputStream in = null;
89             try {
90                 urlConnection = (HttpURLConnection) url.openConnection();
91                 urlConnection.setConnectTimeout(mTimeout);
92                 urlConnection.setRequestProperty("User-Agent", USER_AGENT);
93                 in = new BufferedInputStream(urlConnection.getInputStream());
94                 json = convertStreamToString(in);
95             } finally {
96                 if (urlConnection != null)
97                     urlConnection.disconnect();
98                 if (in != null) {
99                     try {
100                         in.close();
101                     } catch (IOException e) {
102                         Log.e(LOGTAG, "error", e);
103                     }
104                 }
105             }
106 
107             if (json != null) {
108                 /*
109                  * Sample result:
110                  * ["foo",["food network","foothill college","foot locker",...]]
111                  */
112                 JSONArray results = new JSONArray(json);
113                 JSONArray jsonSuggestions = results.getJSONArray(1);
114 
115                 int added = 0;
116                 for (int i = 0; (i < jsonSuggestions.length()) && (added < mMaxResults); i++) {
117                     String suggestion = jsonSuggestions.getString(i);
118                     if (!suggestion.equalsIgnoreCase(query)) {
119                         suggestions.add(suggestion);
120                         added++;
121                     }
122                 }
123             } else {
124                 Log.e(LOGTAG, "Suggestion query failed");
125             }
126         } catch (Exception e) {
127             Log.e(LOGTAG, "Error", e);
128         }
129 
130         mPrevQuery = query;
131         mPrevResults = suggestions;
132         return suggestions;
133     }
134 
135     private String convertStreamToString(java.io.InputStream is) {
136         try {
137             return new java.util.Scanner(is).useDelimiter("\\A").next();
138         } catch (java.util.NoSuchElementException e) {
139             return "";
140         }
141     }
142 }
143