OSDN Git Service

Automatic translation import
[android-x86/packages-apps-Eleven.git] / src / com / cyngn / eleven / ui / activities / SearchActivity.java
1 /*
2  * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3  * (the "License"); you may not use this file except in compliance with the
4  * License. You may obtain a copy of the License at
5  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6  * or agreed to in writing, software distributed under the License is
7  * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8  * KIND, either express or implied. See the License for the specific language
9  * governing permissions and limitations under the License.
10  */
11
12 package com.cyngn.eleven.ui.activities;
13
14 import static com.cyngn.eleven.utils.MusicUtils.mService;
15
16 import android.app.Activity;
17 import android.app.ActionBar;
18 import android.app.LoaderManager.LoaderCallbacks;
19 import android.app.SearchManager;
20 import android.app.SearchableInfo;
21 import android.content.ComponentName;
22 import android.content.CursorLoader;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.Loader;
26 import android.content.ServiceConnection;
27 import android.database.Cursor;
28 import android.media.AudioManager;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.provider.BaseColumns;
33 import android.provider.MediaStore;
34 import android.text.TextUtils;
35 import android.view.Menu;
36 import android.view.MenuItem;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.view.Window;
40 import android.view.inputmethod.InputMethodManager;
41 import android.widget.AbsListView;
42 import android.widget.AbsListView.OnScrollListener;
43 import android.widget.AdapterView;
44 import android.widget.AdapterView.OnItemClickListener;
45 import android.widget.CursorAdapter;
46 import android.widget.FrameLayout;
47 import android.widget.GridView;
48 import android.widget.ImageView.ScaleType;
49 import android.widget.SearchView;
50 import android.widget.SearchView.OnQueryTextListener;
51 import android.widget.TextView;
52
53 import com.cyngn.eleven.IElevenService;
54 import com.cyngn.eleven.R;
55 import com.cyngn.eleven.cache.ImageFetcher;
56 import com.cyngn.eleven.format.PrefixHighlighter;
57 import com.cyngn.eleven.recycler.RecycleHolder;
58 import com.cyngn.eleven.ui.MusicHolder;
59 import com.cyngn.eleven.utils.ApolloUtils;
60 import com.cyngn.eleven.utils.MusicUtils;
61 import com.cyngn.eleven.utils.MusicUtils.ServiceToken;
62 import com.cyngn.eleven.utils.NavUtils;
63 import com.cyngn.eleven.utils.ThemeUtils;
64
65 import java.util.Locale;
66
67 /**
68  * Provides the search interface for Apollo.
69  * 
70  * @author Andrew Neal (andrewdneal@gmail.com)
71  */
72 public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
73         OnScrollListener, OnQueryTextListener, OnItemClickListener, ServiceConnection {
74     /**
75      * Grid view column count. ONE - list, TWO - normal grid
76      */
77     private static final int ONE = 1, TWO = 2;
78
79     /**
80      * The service token
81      */
82     private ServiceToken mToken;
83
84     /**
85      * The query
86      */
87     private String mFilterString;
88
89     /**
90      * Grid view
91      */
92     private GridView mGridView;
93
94     /**
95      * List view adapter
96      */
97     private SearchAdapter mAdapter;
98
99     // Used the filter the user's music
100     private SearchView mSearchView;
101
102     /**
103      * Theme resources
104      */
105     private ThemeUtils mResources;
106
107     /**
108      * {@inheritDoc}
109      */
110     @SuppressWarnings("deprecation")
111     @Override
112     protected void onCreate(final Bundle savedInstanceState) {
113         super.onCreate(savedInstanceState);
114
115         // Initialze the theme resources
116         mResources = new ThemeUtils(this);
117         // Set the overflow style
118         mResources.setOverflowStyle(this);
119
120         // Fade it in
121         overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
122
123         // Control the media volume
124         setVolumeControlStream(AudioManager.STREAM_MUSIC);
125
126         // Bind Apollo's service
127         mToken = MusicUtils.bindToService(this, this);
128
129         // Theme the action bar
130         final ActionBar actionBar = getActionBar();
131         mResources.themeActionBar(actionBar, getString(R.string.app_name));
132         actionBar.setDisplayHomeAsUpEnabled(true);
133
134         // Set the layout
135         setContentView(R.layout.grid_base);
136
137         // Give the background a little UI
138         final FrameLayout background = (FrameLayout)findViewById(R.id.grid_base_container);
139         background.setBackgroundDrawable(getResources().getDrawable(R.drawable.pager_background));
140
141         // Get the query
142         final String query = getIntent().getStringExtra(SearchManager.QUERY);
143         mFilterString = !TextUtils.isEmpty(query) ? query : null;
144
145         // Action bar subtitle
146         mResources.setSubtitle("\"" + mFilterString + "\"");
147
148         // Initialize the adapter
149         mAdapter = new SearchAdapter(this);
150         // Set the prefix
151         mAdapter.setPrefix(mFilterString);
152         // Initialze the list
153         mGridView = (GridView)findViewById(R.id.grid_base);
154         // Bind the data
155         mGridView.setAdapter(mAdapter);
156         // Recycle the data
157         mGridView.setRecyclerListener(new RecycleHolder());
158         // Seepd up scrolling
159         mGridView.setOnScrollListener(this);
160         mGridView.setOnItemClickListener(this);
161         if (ApolloUtils.isLandscape(this)) {
162             mGridView.setNumColumns(TWO);
163         } else {
164             mGridView.setNumColumns(ONE);
165         }
166         // Prepare the loader. Either re-connect with an existing one,
167         // or start a new one.
168         getLoaderManager().initLoader(0, null, this);
169     }
170
171     /**
172      * {@inheritDoc}
173      */
174     @Override
175     protected void onNewIntent(final Intent intent) {
176         super.onNewIntent(intent);
177         final String query = intent.getStringExtra(SearchManager.QUERY);
178         mFilterString = !TextUtils.isEmpty(query) ? query : null;
179         // Set the prefix
180         mAdapter.setPrefix(mFilterString);
181         getLoaderManager().restartLoader(0, null, this);
182     }
183
184     /**
185      * {@inheritDoc}
186      */
187     @Override
188     public boolean onCreateOptionsMenu(final Menu menu) {
189         // Search view
190         getMenuInflater().inflate(R.menu.search, menu);
191         // Theme the search icon
192         mResources.setSearchIcon(menu);
193
194         // Filter the list the user is looking it via SearchView
195         mSearchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
196         mSearchView.setOnQueryTextListener(this);
197
198         // Add voice search
199         final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
200         final SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
201         mSearchView.setSearchableInfo(searchableInfo);
202         return super.onCreateOptionsMenu(menu);
203     }
204
205     /**
206      * {@inheritDoc}
207      */
208     @Override
209     protected void onStart() {
210         super.onStart();
211         MusicUtils.notifyForegroundStateChanged(this, true);
212     }
213
214     /**
215      * {@inheritDoc}
216      */
217     @Override
218     protected void onStop() {
219         super.onStop();
220         MusicUtils.notifyForegroundStateChanged(this, false);
221     }
222
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     protected void onDestroy() {
228         super.onDestroy();
229         // Unbind from the service
230         if (mService != null) {
231             MusicUtils.unbindFromService(mToken);
232             mToken = null;
233         }
234     }
235
236     /**
237      * {@inheritDoc}
238      */
239     @Override
240     public boolean onOptionsItemSelected(final MenuItem item) {
241         switch (item.getItemId()) {
242             case android.R.id.home:
243                 finish();
244                 return true;
245             default:
246                 break;
247         }
248         return super.onOptionsItemSelected(item);
249     }
250
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
256         final Uri uri = Uri.parse("content://media/external/audio/search/fancy/"
257                 + Uri.encode(mFilterString));
258         final String[] projection = new String[] {
259                 BaseColumns._ID, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Artists.ARTIST,
260                 MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2"
261         };
262         return new CursorLoader(this, uri, projection, null, null, null);
263     }
264
265     /**
266      * {@inheritDoc}
267      */
268     @Override
269     public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
270         if (data == null || data.isClosed() || data.getCount() <= 0) {
271             // Set the empty text
272             final TextView empty = (TextView)findViewById(R.id.empty);
273             empty.setText(getString(R.string.empty_search));
274             mGridView.setEmptyView(empty);
275             return;
276         }
277         // Swap the new cursor in. (The framework will take care of closing the
278         // old cursor once we return.)
279         mAdapter.swapCursor(data);
280     }
281
282     /**
283      * {@inheritDoc}
284      */
285     @Override
286     public void onLoaderReset(final Loader<Cursor> loader) {
287         // This is called when the last Cursor provided to onLoadFinished()
288         // above is about to be closed. We need to make sure we are no
289         // longer using it.
290         mAdapter.swapCursor(null);
291     }
292
293     /**
294      * {@inheritDoc}
295      */
296     @Override
297     public void onScrollStateChanged(final AbsListView view, final int scrollState) {
298         // Pause disk cache access to ensure smoother scrolling
299         if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING
300                 || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
301             mAdapter.setPauseDiskCache(true);
302         } else {
303             mAdapter.setPauseDiskCache(false);
304             mAdapter.notifyDataSetChanged();
305         }
306     }
307
308     /**
309      * {@inheritDoc}
310      */
311     @Override
312     public boolean onQueryTextSubmit(final String query) {
313         if (TextUtils.isEmpty(query)) {
314             return false;
315         }
316         // When the search is "committed" by the user, then hide the keyboard so
317         // the user can
318         // more easily browse the list of results.
319         if (mSearchView != null) {
320             final InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
321             if (imm != null) {
322                 imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
323             }
324             mSearchView.clearFocus();
325         }
326         // Action bar subtitle
327         mResources.setSubtitle("\"" + mFilterString + "\"");
328         return true;
329     }
330
331     /**
332      * {@inheritDoc}
333      */
334     @Override
335     public boolean onQueryTextChange(final String newText) {
336         if (TextUtils.isEmpty(newText)) {
337             return false;
338         }
339         // Called when the action bar search text has changed. Update
340         // the search filter, and restart the loader to do a new query
341         // with this filter.
342         mFilterString = !TextUtils.isEmpty(newText) ? newText : null;
343         // Set the prefix
344         mAdapter.setPrefix(mFilterString);
345         getLoaderManager().restartLoader(0, null, this);
346         return true;
347     }
348
349     /**
350      * {@inheritDoc}
351      */
352     @Override
353     public void onItemClick(final AdapterView<?> parent, final View view, final int position,
354             final long id) {
355         Cursor cursor = mAdapter.getCursor();
356         cursor.moveToPosition(position);
357         if (cursor.isBeforeFirst() || cursor.isAfterLast()) {
358             return;
359         }
360         // Get the MIME type
361         final String mimeType = cursor.getString(cursor
362                 .getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
363
364         // If it's an artist, open the artist profile
365         if ("artist".equals(mimeType)) {
366             NavUtils.openArtistProfile(this,
367                     cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)));
368         } else if ("album".equals(mimeType)) {
369             // If it's an album, open the album profile
370             NavUtils.openAlbumProfile(this,
371                     cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)),
372                     cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST)),
373                     cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)));
374         } else if (position >= 0 && id >= 0) {
375             // If it's a song, play it and leave
376             final long[] list = new long[] {
377                 id
378             };
379             MusicUtils.playAll(this, list, 0, false);
380         }
381
382         // Close it up
383         cursor.close();
384         cursor = null;
385         // All done
386         finish();
387     }
388
389     /**
390      * {@inheritDoc}
391      */
392     @Override
393     public void onServiceConnected(final ComponentName name, final IBinder service) {
394         mService = IElevenService.Stub.asInterface(service);
395     }
396
397     /**
398      * {@inheritDoc}
399      */
400     @Override
401     public void onServiceDisconnected(final ComponentName name) {
402         mService = null;
403     }
404
405     /**
406      * Used to populate the list view with the search results.
407      */
408     private static final class SearchAdapter extends CursorAdapter {
409
410         /**
411          * Number of views (ImageView and TextView)
412          */
413         private static final int VIEW_TYPE_COUNT = 2;
414
415         /**
416          * Image cache and image fetcher
417          */
418         private final ImageFetcher mImageFetcher;
419
420         /**
421          * Highlights the query
422          */
423         private final PrefixHighlighter mHighlighter;
424
425         /**
426          * The prefix that's highlighted
427          */
428         private char[] mPrefix;
429
430         /**
431          * Constructor for <code>SearchAdapter</code>
432          * 
433          * @param context The {@link Context} to use.
434          */
435         public SearchAdapter(final Activity context) {
436             super(context, null, false);
437             // Initialize the cache & image fetcher
438             mImageFetcher = ApolloUtils.getImageFetcher(context);
439             // Create the prefix highlighter
440             mHighlighter = new PrefixHighlighter(context);
441         }
442
443         /**
444          * {@inheritDoc}
445          */
446         @Override
447         public void bindView(final View convertView, final Context context, final Cursor cursor) {
448             /* Recycle ViewHolder's items */
449             MusicHolder holder = (MusicHolder)convertView.getTag();
450             if (holder == null) {
451                 holder = new MusicHolder(convertView);
452                 convertView.setTag(holder);
453             }
454
455             // Get the MIME type
456             final String mimetype = cursor.getString(cursor
457                     .getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
458
459             if (mimetype.equals("artist")) {
460                 holder.mImage.get().setScaleType(ScaleType.CENTER_CROP);
461
462                 // Get the artist name
463                 final String artist = cursor.getString(cursor
464                         .getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
465                 holder.mLineOne.get().setText(artist);
466
467                 // Get the album count
468                 final int albumCount = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
469                 holder.mLineTwo.get().setText(
470                         MusicUtils.makeLabel(context, R.plurals.Nalbums, albumCount));
471
472                 // Get the song count
473                 final int songCount = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
474                 holder.mLineThree.get().setText(
475                         MusicUtils.makeLabel(context, R.plurals.Nsongs, songCount));
476
477                 // Asynchronously load the artist image into the adapter
478                 mImageFetcher.loadArtistImage(artist, holder.mImage.get());
479
480                 // Highlght the query
481                 mHighlighter.setText(holder.mLineOne.get(), artist, mPrefix);
482             } else if (mimetype.equals("album")) {
483                 holder.mImage.get().setScaleType(ScaleType.FIT_XY);
484
485                 // Get the Id of the album
486                 final long id = cursor.getLong(cursor
487                         .getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
488
489                 // Get the album name
490                 final String album = cursor.getString(cursor
491                         .getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
492                 holder.mLineOne.get().setText(album);
493
494                 // Get the artist name
495                 final String artist = cursor.getString(cursor
496                         .getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
497                 holder.mLineTwo.get().setText(artist);
498
499                 // Asynchronously load the album images into the adapter
500                 mImageFetcher.loadAlbumImage(artist, album, id, holder.mImage.get());
501                 // Asynchronously load the artist image into the adapter
502                 mImageFetcher.loadArtistImage(artist, holder.mBackground.get());
503
504                 // Highlght the query
505                 mHighlighter.setText(holder.mLineOne.get(), album, mPrefix);
506
507             } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
508                     || mimetype.equals("application/x-ogg")) {
509                 holder.mImage.get().setScaleType(ScaleType.FIT_XY);
510                 holder.mImage.get().setImageResource(R.drawable.header_temp);
511
512                 // Get the track name
513                 final String track = cursor.getString(cursor
514                         .getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
515                 holder.mLineOne.get().setText(track);
516
517                 // Get the album name
518                 final String album = cursor.getString(cursor
519                         .getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
520                 holder.mLineTwo.get().setText(album);
521
522                 final String artist = cursor.getString(cursor
523                         .getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
524                 // Asynchronously load the artist image into the adapter
525                 mImageFetcher.loadArtistImage(artist, holder.mBackground.get());
526                 holder.mLineThree.get().setText(artist);
527
528                 // Highlght the query
529                 mHighlighter.setText(holder.mLineOne.get(), track, mPrefix);
530             }
531         }
532
533         /**
534          * {@inheritDoc}
535          */
536         @Override
537         public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
538             return ((Activity)context).getLayoutInflater().inflate(
539                     R.layout.list_item_detailed, parent, false);
540         }
541
542         /**
543          * {@inheritDoc}
544          */
545         @Override
546         public boolean hasStableIds() {
547             return true;
548         }
549
550         /**
551          * {@inheritDoc}
552          */
553         @Override
554         public int getViewTypeCount() {
555             return VIEW_TYPE_COUNT;
556         }
557
558         /**
559          * @param pause True to temporarily pause the disk cache, false
560          *            otherwise.
561          */
562         public void setPauseDiskCache(final boolean pause) {
563             if (mImageFetcher != null) {
564                 mImageFetcher.setPauseDiskCache(pause);
565             }
566         }
567
568         /**
569          * @param prefix The query to filter.
570          */
571         public void setPrefix(final CharSequence prefix) {
572             if (!TextUtils.isEmpty(prefix)) {
573                 mPrefix = prefix.toString().toUpperCase(Locale.getDefault()).toCharArray();
574             } else {
575                 mPrefix = null;
576             }
577         }
578     }
579
580     /**
581      * {@inheritDoc}
582      */
583     @Override
584     public void onScroll(final AbsListView view, final int firstVisibleItem,
585             final int visibleItemCount, final int totalItemCount) {
586         // Nothing to do
587     }
588
589 }