1/*
2 * Copyright (C) 2009 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;
18
19import android.content.Context;
20import android.content.pm.PackageInfo;
21import android.content.pm.PackageManager;
22import android.os.Build;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Process;
26import android.view.ContextThemeWrapper;
27
28import com.android.quicksearchbox.google.GoogleSource;
29import com.android.quicksearchbox.google.GoogleSuggestClient;
30import com.android.quicksearchbox.google.SearchBaseUrlHelper;
31import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory;
32import com.android.quicksearchbox.ui.SuggestionViewFactory;
33import com.android.quicksearchbox.util.Factory;
34import com.android.quicksearchbox.util.HttpHelper;
35import com.android.quicksearchbox.util.JavaNetHttpHelper;
36import com.android.quicksearchbox.util.NamedTaskExecutor;
37import com.android.quicksearchbox.util.PerNameExecutor;
38import com.android.quicksearchbox.util.PriorityThreadFactory;
39import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor;
40import com.google.common.util.concurrent.ThreadFactoryBuilder;
41
42import java.util.concurrent.Executor;
43import java.util.concurrent.Executors;
44import java.util.concurrent.ThreadFactory;
45
46public class QsbApplication {
47    private final Context mContext;
48
49    private int mVersionCode;
50    private Handler mUiThreadHandler;
51    private Config mConfig;
52    private SearchSettings mSettings;
53    private NamedTaskExecutor mSourceTaskExecutor;
54    private ThreadFactory mQueryThreadFactory;
55    private SuggestionsProvider mSuggestionsProvider;
56    private SuggestionViewFactory mSuggestionViewFactory;
57    private GoogleSource mGoogleSource;
58    private VoiceSearch mVoiceSearch;
59    private Logger mLogger;
60    private SuggestionFormatter mSuggestionFormatter;
61    private TextAppearanceFactory mTextAppearanceFactory;
62    private NamedTaskExecutor mIconLoaderExecutor;
63    private HttpHelper mHttpHelper;
64    private SearchBaseUrlHelper mSearchBaseUrlHelper;
65
66    public QsbApplication(Context context) {
67        // the application context does not use the theme from the <application> tag
68        mContext = new ContextThemeWrapper(context, R.style.Theme_QuickSearchBox);
69    }
70
71    public static boolean isFroyoOrLater() {
72        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
73    }
74
75    public static boolean isHoneycombOrLater() {
76        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
77    }
78
79    public static QsbApplication get(Context context) {
80        return ((QsbApplicationWrapper) context.getApplicationContext()).getApp();
81    }
82
83    protected Context getContext() {
84        return mContext;
85    }
86
87    public int getVersionCode() {
88        if (mVersionCode == 0) {
89            try {
90                PackageManager pm = getContext().getPackageManager();
91                PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0);
92                mVersionCode = pkgInfo.versionCode;
93            } catch (PackageManager.NameNotFoundException ex) {
94                // The current package should always exist, how else could we
95                // run code from it?
96                throw new RuntimeException(ex);
97            }
98        }
99        return mVersionCode;
100    }
101
102    protected void checkThread() {
103        if (Looper.myLooper() != Looper.getMainLooper()) {
104            throw new IllegalStateException("Accessed Application object from thread "
105                    + Thread.currentThread().getName());
106        }
107    }
108
109    protected void close() {
110        checkThread();
111        if (mConfig != null) {
112            mConfig.close();
113            mConfig = null;
114        }
115        if (mSuggestionsProvider != null) {
116            mSuggestionsProvider.close();
117            mSuggestionsProvider = null;
118        }
119    }
120
121    public synchronized Handler getMainThreadHandler() {
122        if (mUiThreadHandler == null) {
123            mUiThreadHandler = new Handler(Looper.getMainLooper());
124        }
125        return mUiThreadHandler;
126    }
127
128    public void runOnUiThread(Runnable action) {
129        getMainThreadHandler().post(action);
130    }
131
132    public synchronized NamedTaskExecutor getIconLoaderExecutor() {
133        if (mIconLoaderExecutor == null) {
134            mIconLoaderExecutor = createIconLoaderExecutor();
135        }
136        return mIconLoaderExecutor;
137    }
138
139    protected NamedTaskExecutor createIconLoaderExecutor() {
140        ThreadFactory iconThreadFactory = new PriorityThreadFactory(
141                    Process.THREAD_PRIORITY_BACKGROUND);
142        return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory));
143    }
144
145    /**
146     * Indicates that construction of the QSB UI is now complete.
147     */
148    public void onStartupComplete() {
149    }
150
151    /**
152     * Gets the QSB configuration object.
153     * May be called from any thread.
154     */
155    public synchronized Config getConfig() {
156        if (mConfig == null) {
157            mConfig = createConfig();
158        }
159        return mConfig;
160    }
161
162    protected Config createConfig() {
163        return new Config(getContext());
164    }
165
166    public synchronized SearchSettings getSettings() {
167        if (mSettings == null) {
168            mSettings = createSettings();
169            mSettings.upgradeSettingsIfNeeded();
170        }
171        return mSettings;
172    }
173
174    protected SearchSettings createSettings() {
175        return new SearchSettingsImpl(getContext(), getConfig());
176    }
177
178    protected Factory<Executor> createExecutorFactory(final int numThreads) {
179        final ThreadFactory threadFactory = getQueryThreadFactory();
180        return new Factory<Executor>() {
181            @Override
182            public Executor create() {
183                return Executors.newFixedThreadPool(numThreads, threadFactory);
184            }
185        };
186    }
187
188    /**
189    /**
190     * Gets the source task executor.
191     * May only be called from the main thread.
192     */
193    public NamedTaskExecutor getSourceTaskExecutor() {
194        checkThread();
195        if (mSourceTaskExecutor == null) {
196            mSourceTaskExecutor = createSourceTaskExecutor();
197        }
198        return mSourceTaskExecutor;
199    }
200
201    protected NamedTaskExecutor createSourceTaskExecutor() {
202        ThreadFactory queryThreadFactory = getQueryThreadFactory();
203        return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
204    }
205
206    /**
207     * Gets the query thread factory.
208     * May only be called from the main thread.
209     */
210    protected ThreadFactory getQueryThreadFactory() {
211        checkThread();
212        if (mQueryThreadFactory == null) {
213            mQueryThreadFactory = createQueryThreadFactory();
214        }
215        return mQueryThreadFactory;
216    }
217
218    protected ThreadFactory createQueryThreadFactory() {
219        String nameFormat = "QSB #%d";
220        int priority = getConfig().getQueryThreadPriority();
221        return new ThreadFactoryBuilder()
222                .setNameFormat(nameFormat)
223                .setThreadFactory(new PriorityThreadFactory(priority))
224                .build();
225    }
226
227    /**
228     * Gets the suggestion provider.
229     *
230     * May only be called from the main thread.
231     */
232    protected SuggestionsProvider getSuggestionsProvider() {
233        checkThread();
234        if (mSuggestionsProvider == null) {
235            mSuggestionsProvider = createSuggestionsProvider();
236        }
237        return mSuggestionsProvider;
238    }
239
240    protected SuggestionsProvider createSuggestionsProvider() {
241        return new SuggestionsProviderImpl(getConfig(),
242              getSourceTaskExecutor(),
243              getMainThreadHandler(),
244              getLogger());
245    }
246
247    /**
248     * Gets the default suggestion view factory.
249     * May only be called from the main thread.
250     */
251    public SuggestionViewFactory getSuggestionViewFactory() {
252        checkThread();
253        if (mSuggestionViewFactory == null) {
254            mSuggestionViewFactory = createSuggestionViewFactory();
255        }
256        return mSuggestionViewFactory;
257    }
258
259    protected SuggestionViewFactory createSuggestionViewFactory() {
260        return new DefaultSuggestionViewFactory(getContext());
261    }
262
263    /**
264     * Gets the Google source.
265     * May only be called from the main thread.
266     */
267    public GoogleSource getGoogleSource() {
268        checkThread();
269        if (mGoogleSource == null) {
270            mGoogleSource = createGoogleSource();
271        }
272        return mGoogleSource;
273    }
274
275    protected GoogleSource createGoogleSource() {
276        return new GoogleSuggestClient(getContext(), getMainThreadHandler(),
277                getIconLoaderExecutor(), getConfig());
278    }
279
280    /**
281     * Gets Voice Search utilities.
282     */
283    public VoiceSearch getVoiceSearch() {
284        checkThread();
285        if (mVoiceSearch == null) {
286            mVoiceSearch = createVoiceSearch();
287        }
288        return mVoiceSearch;
289    }
290
291    protected VoiceSearch createVoiceSearch() {
292        return new VoiceSearch(getContext());
293    }
294
295    /**
296     * Gets the event logger.
297     * May only be called from the main thread.
298     */
299    public Logger getLogger() {
300        checkThread();
301        if (mLogger == null) {
302            mLogger = createLogger();
303        }
304        return mLogger;
305    }
306
307    protected Logger createLogger() {
308        return new EventLogLogger(getContext(), getConfig());
309    }
310
311    public SuggestionFormatter getSuggestionFormatter() {
312        if (mSuggestionFormatter == null) {
313            mSuggestionFormatter = createSuggestionFormatter();
314        }
315        return mSuggestionFormatter;
316    }
317
318    protected SuggestionFormatter createSuggestionFormatter() {
319        return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
320    }
321
322    public TextAppearanceFactory getTextAppearanceFactory() {
323        if (mTextAppearanceFactory == null) {
324            mTextAppearanceFactory = createTextAppearanceFactory();
325        }
326        return mTextAppearanceFactory;
327    }
328
329    protected TextAppearanceFactory createTextAppearanceFactory() {
330        return new TextAppearanceFactory(getContext());
331    }
332
333    public synchronized HttpHelper getHttpHelper() {
334        if (mHttpHelper == null) {
335            mHttpHelper = createHttpHelper();
336        }
337        return mHttpHelper;
338    }
339
340    protected HttpHelper createHttpHelper() {
341        return new JavaNetHttpHelper(
342                new JavaNetHttpHelper.PassThroughRewriter(),
343                getConfig().getUserAgent());
344    }
345
346    public synchronized SearchBaseUrlHelper getSearchBaseUrlHelper() {
347        if (mSearchBaseUrlHelper == null) {
348            mSearchBaseUrlHelper = createSearchBaseUrlHelper();
349        }
350
351        return mSearchBaseUrlHelper;
352    }
353
354    protected SearchBaseUrlHelper createSearchBaseUrlHelper() {
355        // This cast to "SearchSettingsImpl" is somewhat ugly.
356        return new SearchBaseUrlHelper(getContext(), getHttpHelper(),
357                getSettings(), ((SearchSettingsImpl)getSettings()).getSearchPreferences());
358    }
359
360    public Help getHelp() {
361        // No point caching this, it's super cheap.
362        return new Help(getContext(), getConfig());
363    }
364}
365