1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.quicksearchbox.google;
18
19import com.android.quicksearchbox.R;
20import com.android.quicksearchbox.SearchSettings;
21import com.android.quicksearchbox.SearchSettingsImpl;
22import com.android.quicksearchbox.util.HttpHelper;
23
24import android.content.Context;
25import android.content.SharedPreferences;
26import android.os.AsyncTask;
27import android.text.TextUtils;
28import android.util.Log;
29
30import java.util.Locale;
31
32/**
33 * Helper to build the base URL for all search requests.
34 */
35public class SearchBaseUrlHelper implements SharedPreferences.OnSharedPreferenceChangeListener {
36    private static final boolean DBG = false;
37    private static final String TAG = "QSB.SearchBaseUrlHelper";
38
39    private static final String DOMAIN_CHECK_URL =
40            "https://www.google.com/searchdomaincheck?format=domain";
41
42    private static final long SEARCH_BASE_URL_EXPIRY_MS = 24 * 3600 * 1000L;
43
44    private final HttpHelper mHttpHelper;
45    private final Context mContext;
46    private final SearchSettings mSearchSettings;
47
48    /**
49     * Note that this constructor will spawn a thread to issue a HTTP
50     * request if shouldUseGoogleCom is false.
51     */
52    public SearchBaseUrlHelper(Context context, HttpHelper helper,
53            SearchSettings searchSettings, SharedPreferences prefs) {
54        mHttpHelper = helper;
55        mContext = context;
56        mSearchSettings = searchSettings;
57
58        // Note: This earlier used an inner class, but that causes issues
59        // because SharedPreferencesImpl uses a WeakHashMap< > and the listener
60        // will be GC'ed unless we keep a reference to it here.
61        prefs.registerOnSharedPreferenceChangeListener(this);
62
63        maybeUpdateBaseUrlSetting(false);
64    }
65
66    /**
67     * Update the base search url, either:
68     * (a) it has never been set (first run)
69     * (b) it has expired
70     * (c) if the caller forces an update by setting the "force" parameter.
71     *
72     * @param force if true, then the URL is reset whether or not it has
73     *     expired.
74     */
75    public void maybeUpdateBaseUrlSetting(boolean force) {
76        long lastUpdateTime = mSearchSettings.getSearchBaseDomainApplyTime();
77        long currentTime = System.currentTimeMillis();
78
79        if (force || lastUpdateTime == -1 ||
80                currentTime - lastUpdateTime >= SEARCH_BASE_URL_EXPIRY_MS) {
81            if (mSearchSettings.shouldUseGoogleCom()) {
82                setSearchBaseDomain(getDefaultBaseDomain());
83            } else {
84                checkSearchDomain();
85            }
86        }
87    }
88
89    /**
90     * @return the base url for searches.
91     */
92    public String getSearchBaseUrl() {
93        return mContext.getResources().getString(R.string.google_search_base_pattern,
94                getSearchDomain(), GoogleSearch.getLanguage(Locale.getDefault()));
95    }
96
97    /**
98     * @return the search domain. This is of the form "google.co.xx" or "google.com",
99     *     used by UI code.
100     */
101    public String getSearchDomain() {
102        String domain = mSearchSettings.getSearchBaseDomain();
103
104        if (domain == null) {
105            if (DBG) {
106                Log.w(TAG, "Search base domain was null, last apply time=" +
107                        mSearchSettings.getSearchBaseDomainApplyTime());
108            }
109
110            // This is required to deal with the case wherein getSearchDomain
111            // is called before checkSearchDomain returns a valid URL. This will
112            // happen *only* on the first run of the app when the "use google.com"
113            // option is unchecked. In other cases, the previously set domain (or
114            // the default) will be returned.
115            //
116            // We have no choice in this case but to use the default search domain.
117            domain = getDefaultBaseDomain();
118        }
119
120        if (domain.startsWith(".")) {
121            if (DBG) Log.d(TAG, "Prepending www to " + domain);
122            domain = "www" + domain;
123        }
124        return domain;
125    }
126
127    /**
128     * Issue a request to google.com/searchdomaincheck to retrieve the base
129     * URL for search requests.
130     */
131    private void checkSearchDomain() {
132        final HttpHelper.GetRequest request = new HttpHelper.GetRequest(DOMAIN_CHECK_URL);
133
134        new AsyncTask<Void, Void, Void>() {
135            @Override
136            protected Void doInBackground(Void ... params) {
137                if (DBG) Log.d(TAG, "Starting request to /searchdomaincheck");
138                String domain;
139                try {
140                    domain = mHttpHelper.get(request);
141                } catch (Exception e) {
142                    if (DBG) Log.d(TAG, "Request to /searchdomaincheck failed : " + e);
143                    // Swallow any exceptions thrown by the HTTP helper, in
144                    // this rare case, we just use the default URL.
145                    domain = getDefaultBaseDomain();
146
147                    return null;
148                }
149
150                if (DBG) Log.d(TAG, "Request to /searchdomaincheck succeeded");
151                setSearchBaseDomain(domain);
152
153                return null;
154            }
155        }.execute();
156    }
157
158    private String getDefaultBaseDomain() {
159        return mContext.getResources().getString(R.string.default_search_domain);
160    }
161
162    private void setSearchBaseDomain(String domain) {
163        if (DBG) Log.d(TAG, "Setting search domain to : " + domain);
164
165        mSearchSettings.setSearchBaseDomain(domain);
166    }
167
168    @Override
169    public void onSharedPreferenceChanged(SharedPreferences pref, String key) {
170        // Listen for changes only to the SEARCH_BASE_URL preference.
171        if (DBG) Log.d(TAG, "Handling changed preference : " + key);
172        if (SearchSettingsImpl.USE_GOOGLE_COM_PREF.equals(key)) {
173            maybeUpdateBaseUrlSetting(true);
174        }
175    }
176}