GoogleSuggestClient.java revision 09bfc59959dc7bab36edb66a41cb93c354e74626
149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert/*
249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Copyright (C) 2010 The Android Open Source Project
349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert *
449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License");
549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * you may not use this file except in compliance with the License.
649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * You may obtain a copy of the License at
749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert *
849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert *      http://www.apache.org/licenses/LICENSE-2.0
949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert *
1049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Unless required by applicable law or agreed to in writing, software
1149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS,
1249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * See the License for the specific language governing permissions and
1449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * limitations under the License.
1549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */
1649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
1749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertpackage com.android.quicksearchbox.google;
1849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
19cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwoodimport com.android.quicksearchbox.Config;
2049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport com.android.quicksearchbox.R;
2193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.Source;
2293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.SourceResult;
2393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.SuggestionCursor;
24516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwoodimport com.android.quicksearchbox.util.NamedTaskExecutor;
2549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
2649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.HttpResponse;
2749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.client.HttpClient;
2849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.client.methods.HttpGet;
2949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.impl.client.DefaultHttpClient;
3049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.params.HttpParams;
3149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.params.HttpProtocolParams;
3249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.apache.http.util.EntityUtils;
3349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.json.JSONArray;
3449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport org.json.JSONException;
3549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
3649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.content.ComponentName;
3749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.content.Context;
3849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.net.ConnectivityManager;
3949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.net.NetworkInfo;
4009bfc59959dc7bab36edb66a41cb93c354e74626Mathew Inwoodimport android.os.Build;
41e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwoodimport android.os.Handler;
4249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.text.TextUtils;
4349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport android.util.Log;
4449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
4549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.io.IOException;
4649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.io.UnsupportedEncodingException;
4749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.net.URLEncoder;
4849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertimport java.util.Locale;
4949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
5049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert/**
5149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert * Use network-based Google Suggests to provide search suggestions.
5249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert */
53848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasanipublic class GoogleSuggestClient extends AbstractGoogleSource {
5449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
5549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final boolean DBG = false;
5649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private static final String LOG_TAG = "GoogleSearch";
5749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
5809bfc59959dc7bab36edb66a41cb93c354e74626Mathew Inwood    private static final String USER_AGENT = "Android/" + Build.VERSION.RELEASE;
5949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private String mSuggestUri;
6049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
6149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    // TODO: this should be defined somewhere
6249ecac0f60307c1b8c6a29f4f9aca8ee07728869Jean-Baptiste Queru    private static final String HTTP_TIMEOUT = "http.conn-manager.timeout";
6349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
6493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert    private final HttpClient mHttpClient;
6549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
66cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwood    public GoogleSuggestClient(Context context, Handler uiThread,
67cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwood            NamedTaskExecutor iconLoader, Config config) {
68516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood        super(context, uiThread, iconLoader);
6949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        mHttpClient = new DefaultHttpClient();
7049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        HttpParams params = mHttpClient.getParams();
7149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        HttpProtocolParams.setUserAgent(params, USER_AGENT);
72cd4accc7899fa7b756e1c430d6b196525abd5c3cMathew Inwood        params.setLongParameter(HTTP_TIMEOUT, config.getHttpConnectTimeout());
7349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
7449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        // NOTE:  Do not look up the resource here;  Localization changes may not have completed
7549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        // yet (e.g. we may still be reading the SIM card).
7649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        mSuggestUri = null;
7749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
7849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
79848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    @Override
8049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    public ComponentName getIntentComponent() {
8149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return new ComponentName(getContext(), GoogleSearch.class);
8249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
8349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
84848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    @Override
8569494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert    public SourceResult queryInternal(String query) {
8669494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert        return query(query);
8769494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert    }
8869494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert
8969494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert    @Override
9069494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert    public SourceResult queryExternal(String query) {
9169494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert        return query(query);
9269494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert    }
9369494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert
9449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    /**
9549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert     * Queries for a given search term and returns a cursor containing
9649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert     * suggestions ordered by best match.
9749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert     */
9869494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert    private SourceResult query(String query) {
9949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        if (TextUtils.isEmpty(query)) {
10049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return null;
10149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
10249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        if (!isNetworkConnected()) {
10349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.i(LOG_TAG, "Not connected to network.");
10449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return null;
10549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
10649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        try {
10749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            query = URLEncoder.encode(query, "UTF-8");
10849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            if (mSuggestUri == null) {
10949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                Locale l = Locale.getDefault();
110c4e96e6c09422ab54984144ad618b2ecefb4b891Mathew Inwood                String language = GoogleSearch.getLanguage(l);
11149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                mSuggestUri = getContext().getResources().getString(R.string.google_suggest_base,
11209bfc59959dc7bab36edb66a41cb93c354e74626Mathew Inwood                                                                    language);
11349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
11449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
11549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            String suggestUri = mSuggestUri + query;
11649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            if (DBG) Log.d(LOG_TAG, "Sending request: " + suggestUri);
11749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            HttpGet method = new HttpGet(suggestUri);
11849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            HttpResponse response = mHttpClient.execute(method);
11949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            if (response.getStatusLine().getStatusCode() == 200) {
12049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
12149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                /* Goto http://www.google.com/complete/search?json=true&q=foo
12249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 * to see what the data format looks like. It's basically a json
12349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 * array containing 4 other arrays. We only care about the middle
12449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 * 2 which contain the suggestions and their popularity.
12549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                 */
12649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                JSONArray results = new JSONArray(EntityUtils.toString(response.getEntity()));
12749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                JSONArray suggestions = results.getJSONArray(1);
12849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                JSONArray popularity = results.getJSONArray(2);
12949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                if (DBG) Log.d(LOG_TAG, "Got " + suggestions.length() + " results");
13069494b842a3f907164a457852c385f86dbe71d15Bjorn Bringert                return new GoogleSuggestCursor(this, query, suggestions, popularity);
13149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            } else {
13249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                if (DBG) Log.d(LOG_TAG, "Request failed " + response.getStatusLine());
13349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
13449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } catch (UnsupportedEncodingException e) {
13549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.w(LOG_TAG, "Error", e);
13649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } catch (IOException e) {
13749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.w(LOG_TAG, "Error", e);
13849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } catch (JSONException e) {
13949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Log.w(LOG_TAG, "Error", e);
14049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
14149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return null;
14249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
14349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
144848fa7a19abedc372452073abaf52780c7b6d78dAmith Yamasani    @Override
14593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert    public SuggestionCursor refreshShortcut(String shortcutId, String oldExtraData) {
14649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return null;
14749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
14849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
14949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private boolean isNetworkConnected() {
15049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        NetworkInfo networkInfo = getActiveNetworkInfo();
15149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return networkInfo != null && networkInfo.isConnected();
15249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
15349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
15449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    private NetworkInfo getActiveNetworkInfo() {
15549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        ConnectivityManager connectivity =
15649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
15749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        if (connectivity == null) {
15849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return null;
15949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
16049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return connectivity.getActiveNetworkInfo();
16149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
16249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
16393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert    private static class GoogleSuggestCursor extends AbstractGoogleSourceResult {
16449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
16549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        /* Contains the actual suggestions */
16693bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        private final JSONArray mSuggestions;
16749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
16849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        /* This contains the popularity of each suggestion
16949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert         * i.e. 165,000 results. It's not related to sorting.
17049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert         */
17193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        private final JSONArray mPopularity;
17293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert
17393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        public GoogleSuggestCursor(Source source, String userQuery,
17493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                JSONArray suggestions, JSONArray popularity) {
17593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert            super(source, userQuery);
17649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            mSuggestions = suggestions;
17749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            mPopularity = popularity;
17849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
17949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
18049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        @Override
18149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        public int getCount() {
18249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return mSuggestions.length();
18349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
18449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
18549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        @Override
18693bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        public String getSuggestionQuery() {
18749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            try {
18893bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                return mSuggestions.getString(getPosition());
18949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            } catch (JSONException e) {
19049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                Log.w(LOG_TAG, "Error parsing response: " + e);
19149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                return null;
19249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
19349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
19449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert
19549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        @Override
19693bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        public String getSuggestionText2() {
19793bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert            try {
19893bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                return mPopularity.getString(getPosition());
19993bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert            } catch (JSONException e) {
20093bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                Log.w(LOG_TAG, "Error parsing response: " + e);
20193bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert                return null;
20249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            }
20349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
20449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    }
20549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert}
206