| /* | 
 |  * Copyright (C) 2009 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | package com.android.globalsearch; | 
 |  | 
 | import android.app.SearchManager; | 
 | import android.content.ContentProvider; | 
 | import android.content.ContentValues; | 
 | import android.content.UriMatcher; | 
 | import android.database.Cursor; | 
 | import android.net.Uri; | 
 | import android.os.Handler; | 
 | import android.os.Looper; | 
 | import android.os.Process; | 
 | import android.util.Log; | 
 |  | 
 | import java.util.concurrent.ExecutorService; | 
 | import java.util.concurrent.ThreadFactory; | 
 | import java.util.concurrent.ThreadPoolExecutor; | 
 | import java.util.concurrent.TimeUnit; | 
 | import java.util.concurrent.LinkedBlockingQueue; | 
 | import java.util.concurrent.atomic.AtomicInteger; | 
 |  | 
 | /** | 
 |  * Fetches query results from contacts, applications and network-based Google Suggests to provide | 
 |  * search suggestions. | 
 |  */ | 
 | public class SuggestionProvider extends ContentProvider { | 
 |  | 
 |     // set to true to enable the more verbose debug logging for this file | 
 |     private static final boolean DBG = false; | 
 |     private static final String TAG = "GlobalSearch"; | 
 |  | 
 |     // the core thread pool size for suggestion queries.  this number of threads may stay alive | 
 |     // for up to {@link #THREAD_KEEPALIVE_SECONDS} awaiting new tasks to execute. | 
 |     private static final int QUERY_THREAD_CORE_POOL_SIZE = SuggestionSession.NUM_PROMOTED_SOURCES ; | 
 |  | 
 |     // the maximum number of threads used for suggestion queries | 
 |     private static final int QUERY_THREAD_MAX_POOL_SIZE = | 
 |             SuggestionSession.NUM_PROMOTED_SOURCES + 2; | 
 |  | 
 |     // the number of threads used for the asynchronous refreshing of shortcuts | 
 |     private static final int SHORTCUT_REFRESH_POOL_SIZE = 3; | 
 |  | 
 |     // the maximum time that excess idle threads will wait for new tasks before terminating. | 
 |     private static final int THREAD_KEEPALIVE_SECONDS = 5; | 
 |  | 
 |     // the maximum number of concurrent queries allowed for each source. | 
 |     private static final int PER_SOURCE_CONCURRENT_QUERY_LIMIT = 3; | 
 |  | 
 |     private static final String AUTHORITY = "com.android.globalsearch.SuggestionProvider"; | 
 |  | 
 |     private static final UriMatcher sUriMatcher = buildUriMatcher(); | 
 |  | 
 |     // UriMatcher constants | 
 |     private static final int SEARCH_SUGGEST = 0; | 
 |  | 
 |     private SuggestionSources mSources; | 
 |  | 
 |     // Executes notifications from the SuggestionCursor on | 
 |     // the main event handling thread. | 
 |     private Handler mNotifyHandler; | 
 |     private ExecutorService mQueryExecutor; | 
 |     private ExecutorService mRefreshExecutor; | 
 |  | 
 |     private static ThreadFactory sThreadFactory = new ThreadFactory() { | 
 |         private final AtomicInteger mCount = new AtomicInteger(1); | 
 |  | 
 |         public Thread newThread(Runnable r) { | 
 |             final Thread thread = new SuggestionThread( | 
 |                     r, "GlobalSearch #" + mCount.getAndIncrement()); | 
 |             return thread; | 
 |         } | 
 |     }; | 
 |  | 
 |     /** | 
 |      * Sets the thread priority to {@link Process#THREAD_PRIORITY_BACKGROUND}. | 
 |      */ | 
 |     private static class SuggestionThread extends Thread { | 
 |  | 
 |         private SuggestionThread(Runnable runnable, String threadName) { | 
 |             super(runnable, threadName); | 
 |         } | 
 |  | 
 |         @Override | 
 |         public void run() { | 
 |             // take it easy on the UI thread | 
 |             android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); | 
 |             super.run(); | 
 |         } | 
 |     } | 
 |  | 
 |     private SessionManager mSessionManager; | 
 |  | 
 |     public SuggestionProvider() { | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean onCreate() { | 
 |         if (DBG) Log.d("SESSION", "SuggestionProvider.onCreate"); | 
 |         mSources = new SuggestionSources(getContext()); | 
 |         mSources.load(); | 
 |  | 
 |         mNotifyHandler = new Handler(Looper.getMainLooper()); | 
 |  | 
 |         mQueryExecutor = new ThreadPoolExecutor( | 
 |                 QUERY_THREAD_CORE_POOL_SIZE, QUERY_THREAD_MAX_POOL_SIZE, | 
 |                 THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, | 
 |                 new LinkedBlockingQueue<Runnable>(), | 
 |                 sThreadFactory); | 
 |          | 
 |         mRefreshExecutor = new ThreadPoolExecutor( | 
 |                 SHORTCUT_REFRESH_POOL_SIZE, SHORTCUT_REFRESH_POOL_SIZE, | 
 |                 THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, | 
 |                 new LinkedBlockingQueue<Runnable>(), | 
 |                 sThreadFactory); | 
 |  | 
 |         mSessionManager = SessionManager.refreshSessionmanager( | 
 |                 getContext(), | 
 |                 mSources, ShortcutRepositoryImplLog.create(getContext()), | 
 |                 new PerTagExecutor(mQueryExecutor, PER_SOURCE_CONCURRENT_QUERY_LIMIT), | 
 |                 mRefreshExecutor, | 
 |                 mNotifyHandler); | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     /** | 
 |      * This will always return {@link SearchManager#SUGGEST_MIME_TYPE} as this | 
 |      * provider is purely to provide suggestions. | 
 |      */ | 
 |     @Override | 
 |     public String getType(Uri uri) { | 
 |         return SearchManager.SUGGEST_MIME_TYPE; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Queries for a given search term and returns a cursor containing | 
 |      * suggestions ordered by best match. | 
 |      */ | 
 |     @Override | 
 |     public Cursor query(Uri uri, String[] projection, String selection, | 
 |             String[] selectionArgs, String sortOrder) { | 
 |  | 
 |         if (DBG) Log.d(TAG, "query(" + uri + ")"); | 
 |  | 
 |         // Get the search text | 
 |         String query; | 
 |         if (uri.getPathSegments().size() > 1) { | 
 |             query = uri.getLastPathSegment().toLowerCase(); | 
 |         } else { | 
 |             query = ""; | 
 |         } | 
 |  | 
 |         switch (sUriMatcher.match(uri)) { | 
 |             case SEARCH_SUGGEST: | 
 |                 return mSessionManager.query(getContext(), query); | 
 |             default: | 
 |                 throw new IllegalArgumentException("Unknown URI " + uri); | 
 |         } | 
 |     } | 
 |  | 
 |     @Override | 
 |     public Uri insert(Uri uri, ContentValues values) { | 
 |         throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int update(Uri uri, ContentValues values, String selection, | 
 |             String[] selectionArgs) { | 
 |         throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int delete(Uri uri, String selection, String[] selectionArgs) { | 
 |         throw new UnsupportedOperationException(); | 
 |     } | 
 |  | 
 |     private static UriMatcher buildUriMatcher() { | 
 |         UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); | 
 |         matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, | 
 |                 SEARCH_SUGGEST); | 
 |         matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", | 
 |                 SEARCH_SUGGEST); | 
 |         return matcher; | 
 |     } | 
 | } |