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.globalsearch; 18 19 import android.app.SearchManager; 20 import android.content.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.UriMatcher; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.Process; 28 import android.util.Log; 29 30 import java.util.concurrent.ExecutorService; 31 import java.util.concurrent.ThreadFactory; 32 import java.util.concurrent.ThreadPoolExecutor; 33 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.LinkedBlockingQueue; 35 import java.util.concurrent.atomic.AtomicInteger; 36 37 /** 38 * Fetches query results from contacts, applications and network-based Google Suggests to provide 39 * search suggestions. 40 */ 41 public class SuggestionProvider extends ContentProvider { 42 43 // set to true to enable the more verbose debug logging for this file 44 private static final boolean DBG = false; 45 private static final String TAG = "GlobalSearch"; 46 47 // the core thread pool size for suggestion queries. this number of threads may stay alive 48 // for up to {@link #THREAD_KEEPALIVE_SECONDS} awaiting new tasks to execute. 49 private static final int QUERY_THREAD_CORE_POOL_SIZE = SuggestionSession.NUM_PROMOTED_SOURCES ; 50 51 // the maximum number of threads used for suggestion queries 52 private static final int QUERY_THREAD_MAX_POOL_SIZE = 53 SuggestionSession.NUM_PROMOTED_SOURCES + 2; 54 55 // the number of threads used for the asynchronous refreshing of shortcuts 56 private static final int SHORTCUT_REFRESH_POOL_SIZE = 3; 57 58 // the maximum time that excess idle threads will wait for new tasks before terminating. 59 private static final int THREAD_KEEPALIVE_SECONDS = 5; 60 61 // the maximum number of concurrent queries allowed for each source. 62 private static final int PER_SOURCE_CONCURRENT_QUERY_LIMIT = 3; 63 64 private static final String AUTHORITY = "com.android.globalsearch.SuggestionProvider"; 65 66 private static final UriMatcher sUriMatcher = buildUriMatcher(); 67 68 // UriMatcher constants 69 private static final int SEARCH_SUGGEST = 0; 70 71 private SuggestionSources mSources; 72 73 // Executes notifications from the SuggestionCursor on 74 // the main event handling thread. 75 private Handler mNotifyHandler; 76 private ExecutorService mQueryExecutor; 77 private ExecutorService mRefreshExecutor; 78 79 private static ThreadFactory sThreadFactory = new ThreadFactory() { 80 private final AtomicInteger mCount = new AtomicInteger(1); 81 82 public Thread newThread(Runnable r) { 83 final Thread thread = new SuggestionThread( 84 r, "GlobalSearch #" + mCount.getAndIncrement()); 85 return thread; 86 } 87 }; 88 89 /** 90 * Sets the thread priority to {@link Process#THREAD_PRIORITY_BACKGROUND}. 91 */ 92 private static class SuggestionThread extends Thread { 93 SuggestionThread(Runnable runnable, String threadName)94 private SuggestionThread(Runnable runnable, String threadName) { 95 super(runnable, threadName); 96 } 97 98 @Override run()99 public void run() { 100 // take it easy on the UI thread 101 android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 102 super.run(); 103 } 104 } 105 106 private SessionManager mSessionManager; 107 SuggestionProvider()108 public SuggestionProvider() { 109 } 110 111 @Override onCreate()112 public boolean onCreate() { 113 if (DBG) Log.d("SESSION", "SuggestionProvider.onCreate"); 114 mSources = new SuggestionSources(getContext()); 115 mSources.load(); 116 117 mNotifyHandler = new Handler(Looper.getMainLooper()); 118 119 mQueryExecutor = new ThreadPoolExecutor( 120 QUERY_THREAD_CORE_POOL_SIZE, QUERY_THREAD_MAX_POOL_SIZE, 121 THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, 122 new LinkedBlockingQueue<Runnable>(), 123 sThreadFactory); 124 125 mRefreshExecutor = new ThreadPoolExecutor( 126 SHORTCUT_REFRESH_POOL_SIZE, SHORTCUT_REFRESH_POOL_SIZE, 127 THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, 128 new LinkedBlockingQueue<Runnable>(), 129 sThreadFactory); 130 131 mSessionManager = SessionManager.refreshSessionmanager( 132 getContext(), 133 mSources, ShortcutRepositoryImplLog.create(getContext()), 134 new PerTagExecutor(mQueryExecutor, PER_SOURCE_CONCURRENT_QUERY_LIMIT), 135 mRefreshExecutor, 136 mNotifyHandler); 137 138 return true; 139 } 140 141 /** 142 * This will always return {@link SearchManager#SUGGEST_MIME_TYPE} as this 143 * provider is purely to provide suggestions. 144 */ 145 @Override getType(Uri uri)146 public String getType(Uri uri) { 147 return SearchManager.SUGGEST_MIME_TYPE; 148 } 149 150 /** 151 * Queries for a given search term and returns a cursor containing 152 * suggestions ordered by best match. 153 */ 154 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)155 public Cursor query(Uri uri, String[] projection, String selection, 156 String[] selectionArgs, String sortOrder) { 157 158 if (DBG) Log.d(TAG, "query(" + uri + ")"); 159 160 // Get the search text 161 String query; 162 if (uri.getPathSegments().size() > 1) { 163 query = uri.getLastPathSegment().toLowerCase(); 164 } else { 165 query = ""; 166 } 167 168 switch (sUriMatcher.match(uri)) { 169 case SEARCH_SUGGEST: 170 return mSessionManager.query(getContext(), query); 171 default: 172 throw new IllegalArgumentException("Unknown URI " + uri); 173 } 174 } 175 176 @Override insert(Uri uri, ContentValues values)177 public Uri insert(Uri uri, ContentValues values) { 178 throw new UnsupportedOperationException(); 179 } 180 181 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)182 public int update(Uri uri, ContentValues values, String selection, 183 String[] selectionArgs) { 184 throw new UnsupportedOperationException(); 185 } 186 187 @Override delete(Uri uri, String selection, String[] selectionArgs)188 public int delete(Uri uri, String selection, String[] selectionArgs) { 189 throw new UnsupportedOperationException(); 190 } 191 buildUriMatcher()192 private static UriMatcher buildUriMatcher() { 193 UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 194 matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, 195 SEARCH_SUGGEST); 196 matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", 197 SEARCH_SUGGEST); 198 return matcher; 199 } 200 } 201