1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package sk.baka.ambient.activity.search;
20
21 import java.io.Serializable;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.CopyOnWriteArrayList;
26
27 import sk.baka.ambient.AmbientApplication;
28 import sk.baka.ambient.R;
29 import sk.baka.ambient.activity.main.MainActivity;
30 import sk.baka.ambient.collection.TrackMetadataBean;
31 import sk.baka.ambient.collection.local.MediaStoreCollection;
32 import sk.baka.ambient.commons.MiscUtils;
33 import sk.baka.ambient.views.LongOperationExecutor;
34 import android.app.Activity;
35 import android.app.SearchManager;
36 import android.content.ContentResolver;
37 import android.content.Intent;
38 import android.os.Bundle;
39 import android.view.View;
40 import android.view.View.OnClickListener;
41 import android.widget.TextView;
42
43 /***
44 * The search activity, searches for miscellaneous data.
45 *
46 * @author Martin Vysny
47 */
48 public final class SearchActivity extends Activity {
49 /***
50 * When an intent is sent to the main activity, this key will hold the
51 * <code>{@link List}<{@link TrackMetadataBean}></code> of results.
52 */
53 public static final String INTENTKEY_MODEL = "MODEL";
54
55 /***
56 * A query string.
57 */
58 public static final String INTENTKEY_QUERY = "QUERY";
59
60 /***
61 * A response string.
62 */
63 public static final String INTENTKEY_STRING = "STRING";
64
65 /***
66 * Holds type of model data - the {@link SearchType} enum.
67 */
68 public static final String INTENTKEY_RESULTTYPE = "RESULTTYPE";
69
70 private final AmbientApplication app = AmbientApplication.getInstance();
71
72 /***
73 * Maps button id to the search engine instance.
74 */
75 private final Map<Integer, ISearchEngine> engines = new HashMap<Integer, ISearchEngine>();
76
77 @Override
78 protected void onCreate(Bundle icicle) {
79 super.onCreate(icicle);
80 setContentView(R.layout.search);
81 engines.put(R.id.searchEngineSkreemR, new SkreemrEngine());
82
83 final ContentResolver resolver=getContentResolver();
84 engines.put(R.id.searchEngineCollection, new CollectionEngine(new MediaStoreCollection(resolver)));
85 engines.put(R.id.searchEngineStoredPlaylistNames, new PlaylistEngine());
86 engines.put(R.id.searchEngineShoutcastGenres, new ShoutcastEngine());
87 engines.put(R.id.searchEngineMagnatune, new MagnatuneEngine());
88 engines.put(R.id.searchEngineAmpache, new AmpacheEngine());
89 findViewById(R.id.searchTrackPadded).setPadding(5, 2, 5, 3);
90 findViewById(R.id.searchTrackCaptionPadded).setPadding(5, 4, 5, 0);
91 findViewById(R.id.searchClose).setOnClickListener(listener);
92 findViewById(R.id.searchEmbedToPlayer).setOnClickListener(listener);
93 for (final Integer buttonId : engines.keySet()) {
94 findViewById(buttonId).setOnClickListener(listener);
95 }
96 final Intent queryIntent = getIntent();
97 processIntent(queryIntent);
98 }
99
100 @Override
101 protected void onDestroy() {
102 passivateEngine();
103 super.onDestroy();
104 }
105
106 private void processIntent(final Intent queryIntent) {
107 setSelectorVisibility(true);
108 passivateEngine();
109 final String queryAction = queryIntent.getAction();
110 if (!Intent.ACTION_SEARCH.equals(queryAction)) {
111 throw new IllegalArgumentException("Intended for search only");
112 }
113 queryString = queryIntent.getStringExtra(SearchManager.QUERY);
114 final TextView caption = (TextView) findViewById(R.id.searchPickEngineText);
115 caption.setText(getString(R.string.searchPickEngine, queryString));
116 }
117
118 @Override
119 protected void onNewIntent(Intent intent) {
120 processIntent(intent);
121 }
122
123 private void setSelectorVisibility(boolean visible) {
124 findViewById(R.id.searchPickEngine).setVisibility(
125 visible ? View.VISIBLE : View.GONE);
126 findViewById(R.id.searchResultList).setVisibility(
127 visible ? View.GONE : View.VISIBLE);
128 for (final Integer buttonId : engines.keySet()) {
129 findViewById(buttonId).setEnabled(
130 engines.get(buttonId).canPerformSearch());
131 }
132 }
133
134 private String queryString;
135
136 private final OnClickListener listener = new OnClickListener() {
137 public void onClick(View arg0) {
138 if (engines.containsKey(arg0.getId())) {
139 activateSearchEngine(engines.get(arg0.getId()));
140 return;
141 }
142 switch (arg0.getId()) {
143 case R.id.searchClose:
144 passivateEngine();
145 finish();
146 break;
147 case R.id.searchEmbedToPlayer:
148 final SearchType type = engine.getType();
149 passivateEngine();
150 finish();
151 final Intent intent = new Intent(SearchActivity.this,
152 MainActivity.class);
153 intent.putExtra(INTENTKEY_QUERY, queryString);
154 intent.putExtra(INTENTKEY_MODEL, MiscUtils
155 .serializeToBytes((Serializable) searchResult));
156 intent.putExtra(INTENTKEY_RESULTTYPE, MiscUtils
157 .serializeToBytes(type));
158 startActivity(intent);
159 break;
160 }
161 }
162 };
163
164 /***
165 * A string item was clicked. Close the activity and fire intent.
166 *
167 * @param item
168 * the item being clicked.
169 */
170 void stringItemClicked(final String item) {
171 final SearchType type = engine.getType();
172 passivateEngine();
173 finish();
174 final Intent intent = new Intent(this, MainActivity.class);
175 intent.putExtra(INTENTKEY_QUERY, queryString);
176 intent.putExtra(INTENTKEY_STRING, item);
177 intent.putExtra(INTENTKEY_RESULTTYPE, MiscUtils.serializeToBytes(type));
178 startActivity(intent);
179 }
180
181 /***
182 * Returns stored tracks from the bundle.
183 *
184 * @param bundle
185 * the bundle.
186 * @return stored tracks.
187 */
188 @SuppressWarnings("unchecked")
189 public static List<TrackMetadataBean> getModel(final Bundle bundle) {
190 final byte[] serializedTracks = (byte[]) bundle
191 .getSerializable(INTENTKEY_MODEL);
192 if (serializedTracks == null)
193 throw new IllegalArgumentException(
194 "The bundle does not contain model");
195 final List<TrackMetadataBean> tracks = (List<TrackMetadataBean>) MiscUtils
196 .deserialize(serializedTracks);
197 return tracks;
198 }
199
200 /***
201 * This engine is active and performing the queries.
202 */
203 private ISearchEngine engine = null;
204
205 private void passivateEngine() {
206 if (engine != null) {
207 engine.passivate();
208 engine = null;
209 }
210 }
211
212 /***
213 * The result of the search.
214 */
215 private final List<Object> searchResult = new CopyOnWriteArrayList<Object>();
216
217 /***
218 * Activates given search engine and switches to the list view.
219 *
220 * @param engine
221 * the engine to activate.
222 */
223 private void activateSearchEngine(final ISearchEngine engine) {
224 passivateEngine();
225 if (!engine.canPerformSearch()) {
226 return;
227 }
228 if (!engine.canSearchOffline()
229 && !app.getStateHandler().getStartupState().online) {
230 app.error(SearchActivity.class, false,
231 getString(R.string.inOfflineMode), null);
232 return;
233 }
234 this.engine = engine;
235 engine.init(this, R.id.searchResults);
236 final Runnable searchProc = new Runnable() {
237 public void run() {
238 try {
239 final List<? extends Object> result = engine
240 .performSearch(queryString);
241 if (Thread.currentThread().isInterrupted()) {
242 return;
243 }
244 searchResult.clear();
245 if (result != null) {
246 searchResult.addAll(result);
247 }
248 AmbientApplication.getHandler().post(new Runnable() {
249 public void run() {
250 onSuccesfullSearch();
251 }
252 });
253 } catch (final Exception e) {
254 throw new RuntimeException(e);
255 }
256 }
257 };
258 final LongOperationExecutor executor = new LongOperationExecutor(this,
259 R.string.searching, R.string.searchingFailed, searchProc);
260 executor.start();
261 }
262
263 private void onSuccesfullSearch() {
264 engine.update(searchResult);
265 findViewById(R.id.searchEmbedToPlayer).setVisibility(
266 engine.getType().returnsTrackList ? View.VISIBLE : View.GONE);
267 setSelectorVisibility(false);
268 }
269
270 /***
271 * Returns stored query from the bundle.
272 *
273 * @param bundle
274 * the bundle.
275 * @return stored tracks.
276 */
277 public static String getQuery(final Bundle bundle) {
278 final String result = bundle.getString(INTENTKEY_QUERY);
279 if (result == null) {
280 throw new IllegalArgumentException("Null query");
281 }
282 return result;
283 }
284
285 /***
286 * Returns stored query from the bundle.
287 *
288 * @param bundle
289 * the bundle.
290 * @return stored tracks.
291 */
292 public static SearchType getType(final Bundle bundle) {
293 final byte[] result = (byte[]) bundle
294 .getSerializable(INTENTKEY_RESULTTYPE);
295 if (result == null) {
296 throw new IllegalArgumentException("Null query");
297 }
298 return (SearchType) MiscUtils.deserialize(result);
299 }
300 }