• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.database.Cursor;
20 import android.content.Context;
21 import android.content.ComponentName;
22 import android.os.Handler;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 import java.util.concurrent.Executor;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.HashMap;
30 import java.util.LinkedHashMap;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 
34 /**
35  * Holds onto the current {@link SuggestionSession} and manages its lifecycle.  When a session ends,
36  * it gets the session stats and reports them to the {@link ShortcutRepository}.
37  */
38 public class SessionManager implements SuggestionSession.SessionCallback {
39 
40     private static final String TAG = "SessionManager";
41     private static final boolean DBG = false;
42     private static SessionManager sInstance;
43 
44     private final Context mContext;
45 
getInstance()46     public static synchronized SessionManager getInstance() {
47         return sInstance;
48     }
49 
50     /**
51      * Refreshes the global session manager.
52      *
53      * @param sources The suggestion sources.
54      * @param shortcutRepo The shortcut repository.
55      * @param queryExecutor The executor used to execute search suggestion tasks.
56      * @param refreshExecutor The executor used execute shortcut refresh tasks.
57      * @param handler The handler passed along to the session.
58      * @return The up to date session manager.
59      */
refreshSessionmanager(Context context, SuggestionSources sources, ShortcutRepository shortcutRepo, PerTagExecutor queryExecutor, Executor refreshExecutor, Handler handler)60     public static synchronized SessionManager refreshSessionmanager(Context context,
61             SuggestionSources sources, ShortcutRepository shortcutRepo,
62             PerTagExecutor queryExecutor,
63             Executor refreshExecutor, Handler handler) {
64         if (DBG) Log.d(TAG, "refreshSessionmanager()");
65 
66         sInstance = new SessionManager(context, sources, shortcutRepo,
67                 queryExecutor, refreshExecutor, handler);
68         return sInstance;
69     }
70 
SessionManager(Context context, SuggestionSources sources, ShortcutRepository shortcutRepo, PerTagExecutor queryExecutor, Executor refreshExecutor, Handler handler)71     private SessionManager(Context context,
72             SuggestionSources sources, ShortcutRepository shortcutRepo,
73             PerTagExecutor queryExecutor, Executor refreshExecutor, Handler handler) {
74         mContext = context;
75         mSources = sources;
76         mShortcutRepo = shortcutRepo;
77         mQueryExecutor = queryExecutor;
78         mRefreshExecutor = refreshExecutor;
79         mHandler = handler;
80     }
81 
82     private final SuggestionSources mSources;
83     private final ShortcutRepository mShortcutRepo;
84     private final PerTagExecutor mQueryExecutor;
85     private final Executor mRefreshExecutor;
86     private final Handler mHandler;
87     private SuggestionSession mSession;
88 
89     /**
90      * Queries the current session for results.
91      *
92      * @see SuggestionSession#query(String)
93      */
query(Context context, String query)94     public synchronized Cursor query(Context context, String query) {
95         // create a new session if there is none,
96         // or when starting a new typing session
97         if (mSession == null || TextUtils.isEmpty(query)) {
98             mSession = createSession();
99         }
100 
101         return mSession.query(query);
102     }
103 
104     /** {@inheritDoc} */
closeSession()105     public synchronized void closeSession() {
106         if (DBG) Log.d(TAG, "closeSession()");
107         mSession = null;
108     }
109 
createSession()110     private SuggestionSession createSession() {
111         if (DBG) Log.d(TAG, "createSession()");
112         final SuggestionSource webSearchSource = mSources.getSelectedWebSearchSource();
113 
114         // Fire off a warm-up query to the web search source, which that source can use for
115         // whatever it sees fit. For example, EnhancedGoogleSearchProvider uses this to
116         // determine whether a opt-in needs to be shown for use of location.
117         if (webSearchSource != null) {
118             warmUpWebSource(webSearchSource);
119         }
120 
121         Sources sources = orderSources(
122                 mSources.getEnabledSuggestionSources(),
123                 mSources,
124                 mShortcutRepo.getSourceRanking(),
125                 SuggestionSession.NUM_PROMOTED_SOURCES);
126 
127         // implement the delayed executor using the handler
128         final DelayedExecutor delayedExecutor = new DelayedExecutor() {
129             public void postDelayed(Runnable runnable, long delayMillis) {
130                 mHandler.postDelayed(runnable, delayMillis);
131             }
132 
133             public void postAtTime(Runnable runnable, long uptimeMillis) {
134                 mHandler.postAtTime(runnable, uptimeMillis);
135             }
136         };
137 
138         SuggestionSession session = new SuggestionSession(
139                 mSources, sources.mPromotableSources, sources.mUnpromotableSources,
140                 mQueryExecutor,
141                 mRefreshExecutor,
142                 delayedExecutor, new SuggestionFactoryImpl(mContext),
143                 SuggestionSession.CACHE_SUGGESTION_RESULTS);
144         session.setListener(this);
145         session.setShortcutRepo(mShortcutRepo);
146         return session;
147     }
148 
warmUpWebSource(final SuggestionSource webSearchSource)149     private void warmUpWebSource(final SuggestionSource webSearchSource) {
150         mQueryExecutor.execute("warmup", new Runnable() {
151             public void run() {
152                 try {
153                     webSearchSource.getSuggestionTask("", 0, 0).call();
154                 } catch (Exception e) {
155                     Log.e(TAG, "exception from web search warm-up query", e);
156                 }
157             }
158         });
159     }
160 
161     /**
162      * Orders sources by source ranking, and into two groups: one that are candidates for the
163      * promoted list (mPromotableSources), and the other containing sources that should not be in
164      * the promoted list (mUnpromotableSources).
165      *
166      * The promotable list is as follows:
167      * - the web source
168      * - up to 'numPromoted' - 1 of the best ranked sources, among source for whom we have enough
169      * data (e.g are in the 'sourceRanking' list)
170      *
171      * The unpromotoable list is as follows:
172      * - the sources lacking any impression / click data
173      * - the rest of the ranked sources
174      *
175      * The idea is to have the best ranked sources in the promoted list, and give newer sources the
176      * best slots under the "more results" positions to get a little extra attention until we have
177      * enough data to rank them as usual.
178      *
179      * Finally, to solve the empty room problem when there is no data about any sources, we allow
180      * a for a small whitelist of known system apps to be in the promoted list when there is no other
181      * ranked source available.  This should only take effect for the first few usages of
182      * Quick search box.
183      *
184      * @param enabledSources The enabled sources.
185      * @param sourceRanking The order the sources should be in.
186      * @param sourceLookup For getting the web search source and trusted sources.
187      * @param numPromoted  The number of promoted sources.
188      * @return The order of the promotable and non-promotable sources.
189      */
orderSources( List<SuggestionSource> enabledSources, SourceLookup sourceLookup, ArrayList<ComponentName> sourceRanking, int numPromoted)190     static Sources orderSources(
191             List<SuggestionSource> enabledSources,
192             SourceLookup sourceLookup,
193             ArrayList<ComponentName> sourceRanking,
194             int numPromoted) {
195 
196         // get any sources that are in the enabled sources in the order
197         final int numSources = enabledSources.size();
198         HashMap<ComponentName, SuggestionSource> linkMap =
199                 new LinkedHashMap<ComponentName, SuggestionSource>(numSources);
200         for (int i = 0; i < numSources; i++) {
201             final SuggestionSource source = enabledSources.get(i);
202             linkMap.put(source.getComponentName(), source);
203         }
204 
205         Sources sources = new Sources();
206 
207         // gather set of ranked
208         final HashSet<ComponentName> allRanked = new HashSet<ComponentName>(sourceRanking);
209 
210         // start with the web source if it exists
211         SuggestionSource webSearchSource = sourceLookup.getSelectedWebSearchSource();
212         if (webSearchSource != null) {
213             if (DBG) Log.d(TAG, "Adding web search source: " + webSearchSource);
214             sources.add(webSearchSource, true);
215         }
216 
217         // add ranked for rest of promoted slots
218         final int numRanked = sourceRanking.size();
219         int nextRanked = 0;
220         for (; nextRanked < numRanked && sources.mPromotableSources.size() < numPromoted;
221                 nextRanked++) {
222             final ComponentName ranked = sourceRanking.get(nextRanked);
223             final SuggestionSource source = linkMap.remove(ranked);
224             if (DBG) Log.d(TAG, "Adding promoted ranked source: (" + ranked + ") " + source);
225             sources.add(source, true);
226         }
227 
228         // now add the unranked
229         final Iterator<SuggestionSource> sourceIterator = linkMap.values().iterator();
230         while (sourceIterator.hasNext()) {
231             SuggestionSource source = sourceIterator.next();
232             if (!allRanked.contains(source.getComponentName())) {
233                 if (DBG) Log.d(TAG, "Adding unranked source: " + source);
234                 // To fix the empty room problem, we allow a small set of system apps
235                 // to start putting their results in the promoted list before we
236                 // have enough data to pick the high ranking ones.
237                 sources.add(source, sourceLookup.isTrustedSource(source));
238                 sourceIterator.remove();
239             }
240         }
241 
242         // finally, add any remaining ranked
243         for (int i = nextRanked; i < numRanked; i++) {
244             final ComponentName ranked = sourceRanking.get(i);
245             final SuggestionSource source = linkMap.get(ranked);
246             if (source != null) {
247                 if (DBG) Log.d(TAG, "Adding ranked source: (" + ranked + ") " + source);
248                 sources.add(source, sourceLookup.isTrustedSource(source));
249             }
250         }
251 
252         if (DBG) Log.d(TAG, "Promotable sources: " + sources.mPromotableSources);
253         if (DBG) Log.d(TAG, "Unpromotable sources: " + sources.mUnpromotableSources);
254 
255         return sources;
256     }
257 
258     static class Sources {
259         public final ArrayList<SuggestionSource> mPromotableSources;
260         public final ArrayList<SuggestionSource> mUnpromotableSources;
Sources()261         public Sources() {
262             mPromotableSources = new ArrayList<SuggestionSource>();
263             mUnpromotableSources = new ArrayList<SuggestionSource>();
264         }
add(SuggestionSource source, boolean forcePromotable)265         public void add(SuggestionSource source, boolean forcePromotable) {
266             if (source == null) return;
267             if (forcePromotable) {
268                 if (DBG) Log.d(TAG, "  Promotable: " + source);
269                 mPromotableSources.add(source);
270             } else {
271                 if (DBG) Log.d(TAG, "  Unpromotable: " + source);
272                 mUnpromotableSources.add(source);
273             }
274         }
275     }
276 }
277