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 
17 package com.android.quicksearchbox;
18 
19 import android.content.Context;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageManager;
22 import android.os.Build;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.Process;
26 import android.view.ContextThemeWrapper;
27 
28 import com.android.quicksearchbox.google.GoogleSource;
29 import com.android.quicksearchbox.google.GoogleSuggestClient;
30 import com.android.quicksearchbox.google.SearchBaseUrlHelper;
31 import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory;
32 import com.android.quicksearchbox.ui.SuggestionViewFactory;
33 import com.android.quicksearchbox.util.Factory;
34 import com.android.quicksearchbox.util.HttpHelper;
35 import com.android.quicksearchbox.util.JavaNetHttpHelper;
36 import com.android.quicksearchbox.util.NamedTaskExecutor;
37 import com.android.quicksearchbox.util.PerNameExecutor;
38 import com.android.quicksearchbox.util.PriorityThreadFactory;
39 import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor;
40 import com.google.common.util.concurrent.ThreadFactoryBuilder;
41 
42 import java.util.concurrent.Executor;
43 import java.util.concurrent.Executors;
44 import java.util.concurrent.ThreadFactory;
45 
46 public 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 
QsbApplication(Context context)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 
isFroyoOrLater()71     public static boolean isFroyoOrLater() {
72         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
73     }
74 
isHoneycombOrLater()75     public static boolean isHoneycombOrLater() {
76         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
77     }
78 
get(Context context)79     public static QsbApplication get(Context context) {
80         return ((QsbApplicationWrapper) context.getApplicationContext()).getApp();
81     }
82 
getContext()83     protected Context getContext() {
84         return mContext;
85     }
86 
getVersionCode()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 
checkThread()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 
close()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 
getMainThreadHandler()121     public synchronized Handler getMainThreadHandler() {
122         if (mUiThreadHandler == null) {
123             mUiThreadHandler = new Handler(Looper.getMainLooper());
124         }
125         return mUiThreadHandler;
126     }
127 
runOnUiThread(Runnable action)128     public void runOnUiThread(Runnable action) {
129         getMainThreadHandler().post(action);
130     }
131 
getIconLoaderExecutor()132     public synchronized NamedTaskExecutor getIconLoaderExecutor() {
133         if (mIconLoaderExecutor == null) {
134             mIconLoaderExecutor = createIconLoaderExecutor();
135         }
136         return mIconLoaderExecutor;
137     }
138 
createIconLoaderExecutor()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      */
onStartupComplete()148     public void onStartupComplete() {
149     }
150 
151     /**
152      * Gets the QSB configuration object.
153      * May be called from any thread.
154      */
getConfig()155     public synchronized Config getConfig() {
156         if (mConfig == null) {
157             mConfig = createConfig();
158         }
159         return mConfig;
160     }
161 
createConfig()162     protected Config createConfig() {
163         return new Config(getContext());
164     }
165 
getSettings()166     public synchronized SearchSettings getSettings() {
167         if (mSettings == null) {
168             mSettings = createSettings();
169             mSettings.upgradeSettingsIfNeeded();
170         }
171         return mSettings;
172     }
173 
createSettings()174     protected SearchSettings createSettings() {
175         return new SearchSettingsImpl(getContext(), getConfig());
176     }
177 
createExecutorFactory(final int numThreads)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