SearchSettingsImpl.java revision 96fec862c3d494aebcb4e1d93589a241385a2ba7
196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert/*
296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * Copyright (C) 2009 The Android Open Source Project
396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert *
496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License");
596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * you may not use this file except in compliance with the License.
696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * You may obtain a copy of the License at
796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert *
896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert *      http://www.apache.org/licenses/LICENSE-2.0
996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert *
1096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * Unless required by applicable law or agreed to in writing, software
1196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS,
1296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * See the License for the specific language governing permissions and
1496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * limitations under the License.
1596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert */
1696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
1796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertpackage com.android.quicksearchbox;
1896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
1996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.app.SearchManager;
2096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.content.Context;
2196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.content.Intent;
2296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.content.SharedPreferences;
2396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.provider.Settings;
2496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.util.Log;
2596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.view.Menu;
2696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.view.MenuInflater;
2796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertimport android.view.MenuItem;
2896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
2996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert/**
3096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert * Manages user settings.
3196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert */
3296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringertpublic class SearchSettingsImpl implements SearchSettings {
3396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
3496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final boolean DBG = false;
3596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String TAG = "QSB.SearchSettingsImpl";
3696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
3796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    // Name of the preferences file used to store search preference
3896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public static final String PREFERENCES_NAME = "SearchSettings";
3996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
4096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    // Intent action that opens the "Searchable Items" preference
4196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String ACTION_SEARCHABLE_ITEMS =
4296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            "com.android.quicksearchbox.action.SEARCHABLE_ITEMS";
4396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
4496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String SHOW_WEB_SUGGESTIONS_PREF = "show_web_suggestions";
4596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
4696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
4796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Preference key used for storing the index of the next voice search hint to show.
4896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
4996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint";
5096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
5196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
5296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Preference key used to store the time at which the first voice search hint was displayed.
5396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
5496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String FIRST_VOICE_HINT_DISPLAY_TIME = "first_voice_search_hint_time";
5596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
5696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
5796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Preference key for the version of voice search we last got hints from.
5896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
5996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String LAST_SEEN_VOICE_SEARCH_VERSION = "voice_search_version";
6096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
6196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
6296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Prefix of per-corpus enable preference
6396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
6496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static final String CORPUS_ENABLED_PREF_PREFIX = "enable_corpus_";
6596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
6696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private final Context mContext;
6796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
6896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private final Config mConfig;
6996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
7096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public SearchSettingsImpl(Context context, Config config) {
7196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        mContext = context;
7296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        mConfig = config;
7396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
7496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
7596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    protected Context getContext() {
7696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return mContext;
7796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
7896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
7996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    protected Config getConfig() {
8096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return mConfig;
8196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
8296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
8396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public void upgradeSettingsIfNeeded() {
8496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        upgradeShowWebSuggestionsIfNeeded();
8596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
8696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
8796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public Intent getSearchableItemsIntent() {
8896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        Intent intent = new Intent(ACTION_SEARCHABLE_ITEMS);
8996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        intent.setPackage(getContext().getPackageName());
9096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return intent;
9196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
9296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
9396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
9496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Gets the preference key of the preference for whether the given corpus
9596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * is enabled. The preference is stored in the {@link #PREFERENCES_NAME}
9696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * preferences file.
9796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
9896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public static String getCorpusEnabledPreference(Corpus corpus) {
9996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return CORPUS_ENABLED_PREF_PREFIX + corpus.getName();
10096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
10196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
10296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public boolean isCorpusEnabled(Corpus corpus) {
10396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        boolean defaultEnabled = corpus.isCorpusDefaultEnabled();
10496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        String sourceEnabledPref = getCorpusEnabledPreference(corpus);
10596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return getSearchPreferences().getBoolean(sourceEnabledPref, defaultEnabled);
10696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
10796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
10896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    protected SharedPreferences getSearchPreferences() {
10996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return getContext().getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
11096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
11196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
11296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public boolean getShowWebSuggestions() {
11396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return getSearchPreferences().getBoolean(SHOW_WEB_SUGGESTIONS_PREF, true);
11496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
11596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
11696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
11796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Copies value from the old deprecated SHOW_WEB_SUGGESTIONS system setting.
11896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
11996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private void upgradeShowWebSuggestionsIfNeeded() {
12096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        SharedPreferences prefs = getSearchPreferences();
12196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        if (!prefs.contains(SHOW_WEB_SUGGESTIONS_PREF)) {
12296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            // Default to true if the old setting is not set
12396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            boolean oldValue = Settings.System.getInt(getContext().getContentResolver(),
12496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                    Settings.System.SHOW_WEB_SUGGESTIONS, 1) == 1;
12596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            prefs.edit().putBoolean(SHOW_WEB_SUGGESTIONS_PREF, oldValue).commit();
12696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            Log.i(TAG, "Copied value from Settings.System.SHOW_WEB_SUGGESTIONS: " + oldValue);
12796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        }
12896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
12996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
13096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    /**
13196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     * Informs our listeners about the updated settings data.
13296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert     */
13396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public void broadcastSettingsChanged() {
13496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        // We use a message broadcast since the listeners could be in multiple processes.
13596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED);
13696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        Log.i(TAG, "Broadcasting: " + intent);
13796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        getContext().sendBroadcast(intent);
13896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
13996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
14096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public void addMenuItems(Menu menu) {
14196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        MenuInflater inflater = new MenuInflater(getContext());
14296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        inflater.inflate(R.menu.settings, menu);
14396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        MenuItem item = menu.findItem(R.id.menu_settings);
14496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        item.setIntent(getSearchSettingsIntent());
14596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
14696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
14796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public void updateMenuItems(Menu menu) {
14896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
14996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
15096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public Intent getSearchSettingsIntent() {
15196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        Intent settings = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS);
15296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        settings.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
15396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        settings.setPackage(getContext().getPackageName());
15496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return settings;
15596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
15696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
15796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public int getNextVoiceSearchHintIndex(int size) {
15896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            int i = getAndIncrementIntPreference(getSearchPreferences(),
15996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                    NEXT_VOICE_SEARCH_HINT_INDEX_PREF);
16096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            return i % size;
16196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
16296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
16396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    // TODO: Could this be made atomic to avoid races?
16496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    private static int getAndIncrementIntPreference(SharedPreferences prefs, String name) {
16596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        int i = prefs.getInt(name, 0);
16696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        prefs.edit().putInt(name, i + 1).commit();
16796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        return i;
16896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
16996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
17096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public void resetVoiceSearchHintFirstSeenTime() {
17196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        getSearchPreferences().edit()
17296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                .putLong(FIRST_VOICE_HINT_DISPLAY_TIME, System.currentTimeMillis()).commit();
17396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
17496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
17596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    public boolean haveVoiceSearchHintsExpired(int currentVoiceSearchVersion) {
17696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        SharedPreferences prefs = getSearchPreferences();
17796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
17896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        if (currentVoiceSearchVersion != 0) {
17996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            long currentTime = System.currentTimeMillis();
18096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            int lastVoiceSearchVersion = prefs.getInt(LAST_SEEN_VOICE_SEARCH_VERSION, 0);
18196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            long firstHintTime = prefs.getLong(FIRST_VOICE_HINT_DISPLAY_TIME, 0);
18296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            if (firstHintTime == 0 || currentVoiceSearchVersion != lastVoiceSearchVersion) {
18396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                prefs.edit()
18496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                        .putInt(LAST_SEEN_VOICE_SEARCH_VERSION, currentVoiceSearchVersion)
18596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                        .putLong(FIRST_VOICE_HINT_DISPLAY_TIME, currentTime)
18696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                        .commit();
18796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                firstHintTime = currentTime;
18896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            }
18996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            if (currentTime - firstHintTime > getConfig().getVoiceSearchHintActivePeriod()) {
19096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                if (DBG) Log.d(TAG, "Voice seach hint period expired; not showing hints.");
19196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                return true;
19296fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            } else {
19396fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert                return false;
19496fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            }
19596fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        } else {
19696fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            if (DBG) Log.d(TAG, "Could not determine voice search version; not showing hints.");
19796fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert            return true;
19896fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert        }
19996fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert    }
20096fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert
20196fec862c3d494aebcb4e1d93589a241385a2ba7Bjorn Bringert}
202