diff options
author | linus_lee <llee@cyngn.com> | 2014-09-22 18:38:29 -0700 |
---|---|---|
committer | linus_lee <llee@cyngn.com> | 2014-11-20 12:03:04 -0800 |
commit | 78603d13a50f79b91ae9b9c945314a6a79ed4a04 (patch) | |
tree | 794df6dae1220403ddee5eb223a9238457bfa149 | |
parent | 0e486adaf71cec5e75217a32a281f2eb6c799ba5 (diff) | |
download | android_packages_apps_Eleven-78603d13a50f79b91ae9b9c945314a6a79ed4a04.tar.gz android_packages_apps_Eleven-78603d13a50f79b91ae9b9c945314a6a79ed4a04.tar.bz2 android_packages_apps_Eleven-78603d13a50f79b91ae9b9c945314a6a79ed4a04.zip |
Eleven: Add search history implementation. Fix padding and a small bug in Top tracks
Padding layouts were wrong in some layouts
Fixed a bug in top tracks where we were looking at id and not loading time
https://cyanogen.atlassian.net/browse/MUSIC-39
Change-Id: Ie304a9a7cc07770a2b699310b0ecbe94142863cf
-rw-r--r-- | res/drawable-hdpi/history_icon.png | bin | 0 -> 481 bytes | |||
-rw-r--r-- | res/drawable-mdpi/history_icon.png | bin | 0 -> 360 bytes | |||
-rw-r--r-- | res/drawable-xhdpi/history_icon.png | bin | 0 -> 575 bytes | |||
-rw-r--r-- | res/drawable-xxhdpi/history_icon.png | bin | 0 -> 860 bytes | |||
-rw-r--r-- | res/layout/activity_search.xml | 21 | ||||
-rw-r--r-- | res/layout/list_item_common.xml | 3 | ||||
-rw-r--r-- | res/layout/list_item_queue.xml | 3 | ||||
-rw-r--r-- | res/layout/list_item_search_history.xml | 31 | ||||
-rw-r--r-- | res/layout/list_item_top_tracks.xml | 2 | ||||
-rw-r--r-- | res/values/dimens.xml | 1 | ||||
-rw-r--r-- | src/com/cyngn/eleven/provider/MusicDB.java | 2 | ||||
-rw-r--r-- | src/com/cyngn/eleven/provider/RecentStore.java | 2 | ||||
-rw-r--r-- | src/com/cyngn/eleven/provider/SearchHistory.java | 155 | ||||
-rw-r--r-- | src/com/cyngn/eleven/ui/activities/SearchActivity.java | 269 |
14 files changed, 447 insertions, 42 deletions
diff --git a/res/drawable-hdpi/history_icon.png b/res/drawable-hdpi/history_icon.png Binary files differnew file mode 100644 index 0000000..ec76e2c --- /dev/null +++ b/res/drawable-hdpi/history_icon.png diff --git a/res/drawable-mdpi/history_icon.png b/res/drawable-mdpi/history_icon.png Binary files differnew file mode 100644 index 0000000..808c72e --- /dev/null +++ b/res/drawable-mdpi/history_icon.png diff --git a/res/drawable-xhdpi/history_icon.png b/res/drawable-xhdpi/history_icon.png Binary files differnew file mode 100644 index 0000000..700b172 --- /dev/null +++ b/res/drawable-xhdpi/history_icon.png diff --git a/res/drawable-xxhdpi/history_icon.png b/res/drawable-xxhdpi/history_icon.png Binary files differnew file mode 100644 index 0000000..6fef335 --- /dev/null +++ b/res/drawable-xxhdpi/history_icon.png diff --git a/res/layout/activity_search.xml b/res/layout/activity_search.xml new file mode 100644 index 0000000..5cd479c --- /dev/null +++ b/res/layout/activity_search.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 Cyanogen, Inc. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ListView + android:id="@+id/list_search_history" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:cacheColorHint="@color/transparent" + android:drawSelectorOnTop="false" + android:fastScrollEnabled="true" + android:dividerHeight="@dimen/divider_height" + android:divider="@drawable/inset_list_divider"/> + + <include + layout="@layout/list_base" /> +</FrameLayout>
\ No newline at end of file diff --git a/res/layout/list_item_common.xml b/res/layout/list_item_common.xml index 6f025ce..4ba3914 100644 --- a/res/layout/list_item_common.xml +++ b/res/layout/list_item_common.xml @@ -3,8 +3,7 @@ android:id="@+id/image" android:layout_width="@dimen/list_item_image_width" android:layout_height="@dimen/list_item_image_height" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" + android:layout_centerVertical="true" android:scaleType="centerCrop" /> <RelativeLayout diff --git a/res/layout/list_item_queue.xml b/res/layout/list_item_queue.xml index 9cf1fc5..ddbc66f 100644 --- a/res/layout/list_item_queue.xml +++ b/res/layout/list_item_queue.xml @@ -13,8 +13,7 @@ android:id="@+id/image" android:layout_width="@dimen/list_item_image_width" android:layout_height="@dimen/list_item_image_height" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" + android:layout_centerVertical="true" android:scaleType="centerCrop" /> <!-- center the text views vertically --> diff --git a/res/layout/list_item_search_history.xml b/res/layout/list_item_search_history.xml new file mode 100644 index 0000000..2bb520e --- /dev/null +++ b/res/layout/list_item_search_history.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 Cyanogen, Inc. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/item_short_height" + android:gravity="center_vertical" + android:minHeight="@dimen/item_short_height" + android:paddingBottom="@dimen/list_item_padding_bottom" + android:paddingTop="@dimen/list_item_padding_top" + android:paddingLeft="@dimen/list_preferred_item_padding" + android:paddingRight="@dimen/list_preferred_item_padding"> + + <com.cyngn.eleven.widgets.SquareImageView + android:id="@+id/image" + android:layout_width="@dimen/list_item_image_width" + android:layout_height="@dimen/list_item_image_height" + android:layout_centerVertical="true" + android:scaleType="centerCrop" + android:src="@drawable/history_icon" /> + + <TextView + android:id="@+id/line_one" + style="@style/ListItemMainText.Single" + android:gravity="center_vertical" + android:layout_centerVertical="true" + android:layout_toRightOf="@id/image" + android:layout_width="match_parent" + android:layout_height="fill_parent" /> +</RelativeLayout>
\ No newline at end of file diff --git a/res/layout/list_item_top_tracks.xml b/res/layout/list_item_top_tracks.xml index 2b4a795..70fe32f 100644 --- a/res/layout/list_item_top_tracks.xml +++ b/res/layout/list_item_top_tracks.xml @@ -22,6 +22,8 @@ android:minHeight="@dimen/item_normal_height" android:paddingTop="@dimen/list_item_padding_top" android:paddingBottom="@dimen/list_item_padding_bottom" + android:paddingLeft="@dimen/list_preferred_item_padding" + android:paddingRight="@dimen/list_preferred_item_padding" tools:ignore="ContentDescription" > <FrameLayout diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 06189b5..5e4be37 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -61,6 +61,7 @@ <dimen name="grid_bottom_height">40dip</dimen> <!-- Grid and list item normal height --> <dimen name="item_normal_height">70.0dip</dimen> + <dimen name="item_short_height">60.0dip</dimen> <dimen name="list_item_image_height">50.0dip</dimen> <dimen name="list_item_image_width">50.0dip</dimen> <dimen name="list_item_top_track_image_size">26.0dip</dimen> diff --git a/src/com/cyngn/eleven/provider/MusicDB.java b/src/com/cyngn/eleven/provider/MusicDB.java index d1cc348..794b661 100644 --- a/src/com/cyngn/eleven/provider/MusicDB.java +++ b/src/com/cyngn/eleven/provider/MusicDB.java @@ -41,6 +41,7 @@ public class MusicDB extends SQLiteOpenHelper { PlaylistArtworkStore.getInstance(mContext).onCreate(db); RecentStore.getInstance(mContext).onCreate(db); SongPlayCount.getInstance(mContext).onCreate(db); + SearchHistory.getInstance(mContext).onCreate(db); } @Override @@ -48,5 +49,6 @@ public class MusicDB extends SQLiteOpenHelper { PlaylistArtworkStore.getInstance(mContext).onUpgrade(db, oldVersion, newVersion); RecentStore.getInstance(mContext).onUpgrade(db, oldVersion, newVersion); SongPlayCount.getInstance(mContext).onUpgrade(db, oldVersion, newVersion); + SearchHistory.getInstance(mContext).onUpgrade(db, oldVersion, newVersion); } } diff --git a/src/com/cyngn/eleven/provider/RecentStore.java b/src/com/cyngn/eleven/provider/RecentStore.java index b8ffbb4..285d53e 100644 --- a/src/com/cyngn/eleven/provider/RecentStore.java +++ b/src/com/cyngn/eleven/provider/RecentStore.java @@ -91,7 +91,7 @@ public class RecentStore { Cursor oldest = null; try { database.query(RecentStoreColumns.NAME, - new String[]{RecentStoreColumns.ID}, null, null, null, null, + new String[]{RecentStoreColumns.TIMEPLAYED}, null, null, null, null, RecentStoreColumns.TIMEPLAYED + " ASC"); if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) { diff --git a/src/com/cyngn/eleven/provider/SearchHistory.java b/src/com/cyngn/eleven/provider/SearchHistory.java new file mode 100644 index 0000000..3f02c65 --- /dev/null +++ b/src/com/cyngn/eleven/provider/SearchHistory.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2014 Cyanogen, Inc. + */ +package com.cyngn.eleven.provider; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import java.util.ArrayList; + +public class SearchHistory { + /* Maximum # of items in the db */ + private static final int MAX_ITEMS_IN_DB = 25; + + private static SearchHistory sInstance = null; + + private MusicDB mMusicDatabase = null; + + public SearchHistory(final Context context) { + mMusicDatabase = MusicDB.getInstance(context); + } + + public void onCreate(final SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + SearchHistoryColumns.NAME + " (" + + SearchHistoryColumns.SEARCHSTRING + " STRING NOT NULL," + + SearchHistoryColumns.TIMESEARCHED + " LONG NOT NULL);"); + } + + public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + SearchHistoryColumns.NAME); + onCreate(db); + } + + /** + * @param context The {@link android.content.Context} to use + * @return A new instance of this class. + */ + public static final synchronized SearchHistory getInstance(final Context context) { + if (sInstance == null) { + sInstance = new SearchHistory(context.getApplicationContext()); + } + return sInstance; + } + + /** + * Used to save a search request into the db. This function will remove any old + * searches for that string before inserting it into the db + * @param searchString The string that has been searched + */ + public void addSearchString(final String searchString) { + if (searchString == null) { + return; + } + + String trimmedString = searchString.trim(); + + if (trimmedString.isEmpty()) { + return; + } + + final SQLiteDatabase database = mMusicDatabase.getWritableDatabase(); + database.beginTransaction(); + + try { + // delete existing searches with the same search string + database.delete(SearchHistoryColumns.NAME, + SearchHistoryColumns.SEARCHSTRING + " = ? COLLATE NOCASE", + new String[] { trimmedString }); + + // add the entry + final ContentValues values = new ContentValues(2); + values.put(SearchHistoryColumns.SEARCHSTRING, trimmedString); + values.put(SearchHistoryColumns.TIMESEARCHED, System.currentTimeMillis()); + database.insert(SearchHistoryColumns.NAME, null, values); + + // if our db is too large, delete the extra items + Cursor oldest = null; + try { + database.query(SearchHistoryColumns.NAME, + new String[]{SearchHistoryColumns.TIMESEARCHED}, null, null, null, null, + SearchHistoryColumns.TIMESEARCHED + " ASC"); + + if (oldest != null && oldest.getCount() > MAX_ITEMS_IN_DB) { + oldest.moveToPosition(oldest.getCount() - MAX_ITEMS_IN_DB); + long timeOfRecordToKeep = oldest.getLong(0); + + database.delete(SearchHistoryColumns.NAME, + SearchHistoryColumns.TIMESEARCHED + " < ?", + new String[] { String.valueOf(timeOfRecordToKeep) }); + + } + } finally { + if (oldest != null) { + oldest.close(); + oldest = null; + } + } + } finally { + database.setTransactionSuccessful(); + database.endTransaction(); + } + } + + /** + * Gets a cursor to the list of recently searched strings + * @param limit number of items to limit to + * @return cursor + */ + public Cursor queryRecentSearches(final String limit) { + final SQLiteDatabase database = mMusicDatabase.getReadableDatabase(); + return database.query(SearchHistoryColumns.NAME, + new String[]{SearchHistoryColumns.SEARCHSTRING}, null, null, null, null, + SearchHistoryColumns.TIMESEARCHED + " DESC", limit); + } + + /** + * Gets the recently searched strings + * @return list of esarched strings + */ + public ArrayList<String> getRecentSearches() { + Cursor searches = queryRecentSearches(String.valueOf(MAX_ITEMS_IN_DB)); + + ArrayList<String> results = new ArrayList<String>(MAX_ITEMS_IN_DB); + + try { + if (searches != null && searches.moveToFirst()) { + int colIdx = searches.getColumnIndex(SearchHistoryColumns.SEARCHSTRING); + + do { + results.add(searches.getString(colIdx)); + } while (searches.moveToNext()); + } + } finally { + if (searches != null) { + searches.close(); + searches = null; + } + } + + return results; + } + + public interface SearchHistoryColumns { + /* Table name */ + public static final String NAME = "searchhistory"; + + /* What was searched */ + public static final String SEARCHSTRING = "searchstring"; + + /* Time of search */ + public static final String TIMESEARCHED = "timesearched"; + } +} diff --git a/src/com/cyngn/eleven/ui/activities/SearchActivity.java b/src/com/cyngn/eleven/ui/activities/SearchActivity.java index aede621..48422cb 100644 --- a/src/com/cyngn/eleven/ui/activities/SearchActivity.java +++ b/src/com/cyngn/eleven/ui/activities/SearchActivity.java @@ -13,15 +13,14 @@ package com.cyngn.eleven.ui.activities; import android.app.ActionBar; import android.app.SearchManager; -import android.app.SearchableInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.database.Cursor; -import android.graphics.Bitmap; import android.media.AudioManager; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.provider.BaseColumns; import android.provider.MediaStore; @@ -33,26 +32,27 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SearchView; import android.widget.SearchView.OnQueryTextListener; -import android.widget.TextView; import com.cyngn.eleven.Config; import com.cyngn.eleven.IElevenService; import com.cyngn.eleven.R; import com.cyngn.eleven.adapters.SummarySearchAdapter; +import com.cyngn.eleven.loaders.WrappedAsyncTaskLoader; import com.cyngn.eleven.model.AlbumArtistDetails; import com.cyngn.eleven.model.SearchResult; import com.cyngn.eleven.model.SearchResult.ResultType; +import com.cyngn.eleven.provider.SearchHistory; import com.cyngn.eleven.recycler.RecycleHolder; import com.cyngn.eleven.sectionadapter.SectionAdapter; import com.cyngn.eleven.sectionadapter.SectionCreator; @@ -78,10 +78,26 @@ import static com.cyngn.eleven.utils.MusicUtils.mService; * * @author Andrew Neal (andrewdneal@gmail.com) */ -public class SearchActivity extends FragmentActivity implements LoaderCallbacks<SectionListContainer<SearchResult>>, +public class SearchActivity extends FragmentActivity implements + LoaderCallbacks<SectionListContainer<SearchResult>>, OnScrollListener, OnQueryTextListener, OnItemClickListener, ServiceConnection, OnTouchListener { /** + * Loading delay of 500ms so we don't flash the screen too much when loading new searches + */ + private static int LOADING_DELAY = 500; + + /** + * Identifier for the search loader + */ + private static int SEARCH_LOADER = 0; + + /** + * Identifier for the search history loader + */ + private static int HISTORY_LOADER = 1; + + /** * The service token */ private ServiceToken mToken; @@ -128,6 +144,43 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< private ResultType mSearchType; /** + * Search History loader callback + */ + private SearchHistoryCallback mSearchHistoryCallback; + + /** + * List view + */ + private ListView mSearchHistoryListView; + + /** + * This tracks our current visible state between the different views + */ + enum VisibleState { + SearchHistory, + Empty, + SearchResults, + Loading, + } + + private VisibleState mCurrentState; + + /** + * Handler for posting runnables + */ + private Handler mHandler; + + /** + * A runnable to show the loading view that will be posted with a delay to prevent flashing + */ + private Runnable mLoadingRunnable; + + /** + * Flag used to track if we are quitting so we don't flash loaders while finishing the activity + */ + private boolean mQuitting = false; + + /** * {@inheritDoc} */ @Override @@ -144,7 +197,7 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< mToken = MusicUtils.bindToService(this, this); // Set the layout - setContentView(R.layout.list_base); + setContentView(R.layout.activity_search); // get the input method manager mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -164,6 +217,15 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< initListView(); + // setup handler and runnable + mHandler = new Handler(); + mLoadingRunnable = new Runnable() { + @Override + public void run() { + setState(VisibleState.Loading); + } + }; + // Theme the action bar final ActionBar actionBar = getActionBar(); actionBar.setHomeButtonEnabled(true); @@ -173,7 +235,7 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< mFilterString = getIntent().getStringExtra(SearchManager.QUERY); // if we have a non-empty search string, this is a 2nd lvl search - if (mFilterString != null && !mFilterString.isEmpty()) { + if (!TextUtils.isEmpty(mFilterString)) { mTopLevelSearch = false; // get the search type to filter by @@ -203,11 +265,14 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< // Set the prefix mAdapter.getUnderlyingAdapter().setPrefix(mFilterString); - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getSupportLoaderManager().initLoader(0, null, this); + // Start the loader for the query + getSupportLoaderManager().initLoader(SEARCH_LOADER, null, this); } else { mTopLevelSearch = true; + mSearchHistoryCallback = new SearchHistoryCallback(); + + // Start the loader for the search history + getSupportLoaderManager().initLoader(HISTORY_LOADER, null, mSearchHistoryCallback); } // set the background on the root view @@ -235,15 +300,33 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< // when updating the search. For now let's manually toggle visibility and come back // to this later //mListView.setEmptyView(mNoResultsContainer); + + // load the search history list view + mSearchHistoryListView = (ListView)findViewById(R.id.list_search_history); + mSearchHistoryListView.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + String searchItem = (String)mSearchHistoryListView.getAdapter().getItem(position); + mSearchView.setQuery(searchItem, true); + } + }); + mSearchHistoryListView.setOnTouchListener(this); } /** * {@inheritDoc} */ @Override - public Loader<SectionListContainer<SearchResult>> onCreateLoader(final int id, final Bundle args) { + public Loader<SectionListContainer<SearchResult>> onCreateLoader(final int id, + final Bundle args) { IItemCompare<SearchResult> comparator = null; + // prep the loader in case the query takes a long time + setLoading(); + + // set the no results string ahead of time in case the user changes the string whiel loading + mNoResultsContainer.setMainHighlightText("\"" + mFilterString + "\""); + // if we are at the top level, create a comparator to separate the different types into // their own sections (artists, albums, etc) if (mTopLevelSearch) { @@ -291,8 +374,8 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< @Override public boolean onMenuItemActionCollapse(MenuItem item) { - finish(); - return true; + quit(); + return false; } }); @@ -301,6 +384,11 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< return super.onCreateOptionsMenu(menu); } + private void quit() { + mQuitting = true; + finish(); + } + /** * {@inheritDoc} */ @@ -339,7 +427,7 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case android.R.id.home: - finish(); + quit(); return true; default: break; @@ -357,13 +445,13 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< if (data.mListResults.isEmpty()) { // clear the adapter mAdapter.clear(); - mListView.setVisibility(View.INVISIBLE); - mNoResultsContainer.setVisibility(View.VISIBLE); + // show the empty state + setState(VisibleState.Empty); } else { // Set the data mAdapter.setData(data); - mListView.setVisibility(View.VISIBLE); - mNoResultsContainer.setVisibility(View.INVISIBLE); + // show the search results + setState(VisibleState.SearchResults); } } @@ -372,8 +460,6 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< */ @Override public void onLoaderReset(final Loader<SectionListContainer<SearchResult>> loader) { - // Clear the data in the adapter - mAdapter.unload(); } /** @@ -396,11 +482,9 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< */ @Override public boolean onQueryTextSubmit(final String query) { - if (TextUtils.isEmpty(query)) { - mFilterString = ""; - return false; - } - + // simulate an on query text change + onQueryTextChange(query); + // hide the input manager hideInputManager(); return true; @@ -408,13 +492,64 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< public void hideInputManager() { // When the search is "committed" by the user, then hide the keyboard so - // the user can - // more easily browse the list of results. + // the user can more easily browse the list of results. if (mSearchView != null) { if (mImm != null && mImm.isImeShowing()) { mImm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0); } mSearchView.clearFocus(); + + // add our search string + SearchHistory.getInstance(this).addSearchString(mFilterString); + } + } + + /** + * This posts a delayed for showing the loading screen. The reason for the delayed is we + * don't want to flash the loading icon very often since searches usually are pretty fast + */ + public void setLoading() { + if (mCurrentState != VisibleState.Loading) { + if (!mHandler.hasCallbacks(mLoadingRunnable)) { + mHandler.postDelayed(mLoadingRunnable, LOADING_DELAY); + } + } + } + + /** + * Sets the currently visible view + * @param state the current visible state + */ + public void setState(VisibleState state) { + // remove any delayed runnables. This has to be before mCurrentState == state + // in case the state doesn't change but we've created a loading runnable + mHandler.removeCallbacks(mLoadingRunnable); + + // if we are already looking at view already, just quit + if (mCurrentState == state) { + return; + } + + mCurrentState = state; + + mSearchHistoryListView.setVisibility(View.INVISIBLE); + mListView.setVisibility(View.INVISIBLE); + mNoResultsContainer.setVisibility(View.INVISIBLE); + + switch (mCurrentState) { + case SearchHistory: + mSearchHistoryListView.setVisibility(View.VISIBLE); + break; + case SearchResults: + mListView.setVisibility(View.VISIBLE); + break; + case Empty: + mNoResultsContainer.setVisibility(View.VISIBLE); + break; + default: + // Don't show anything for now - we need a loading screen + // see bug: https://cyanogen.atlassian.net/browse/MUSIC-63 + break; } } @@ -423,19 +558,34 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< */ @Override public boolean onQueryTextChange(final String newText) { + if (mQuitting) { + return true; + } + if (TextUtils.isEmpty(newText)) { - mListView.setVisibility(View.INVISIBLE); - mNoResultsContainer.setVisibility(View.INVISIBLE); - mFilterString = ""; - return false; + if (!TextUtils.isEmpty(mFilterString)) { + mFilterString = ""; + getSupportLoaderManager().restartLoader(HISTORY_LOADER, null, + mSearchHistoryCallback); + getSupportLoaderManager().destroyLoader(SEARCH_LOADER); + } + + return true; + } + + // if the strings are the same, return + if (newText.equals(mFilterString)) { + return true; } + // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. - mFilterString = !TextUtils.isEmpty(newText) ? newText : null; + mFilterString = newText; // Set the prefix mAdapter.getUnderlyingAdapter().setPrefix(mFilterString); - getSupportLoaderManager().restartLoader(0, null, this); + getSupportLoaderManager().restartLoader(SEARCH_LOADER, null, this); + getSupportLoaderManager().destroyLoader(HISTORY_LOADER); return true; } @@ -523,7 +673,8 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< case Song: item = SearchResult.createSearchResult(cursor); if (item != null) { - AlbumArtistDetails details = MusicUtils.getAlbumArtDetails(getContext(), item.mId); + AlbumArtistDetails details = MusicUtils.getAlbumArtDetails(getContext(), + item.mId); if (details != null) { item.mArtist = details.mArtistName; item.mAlbum = details.mAlbumName; @@ -681,14 +832,14 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< public static Cursor makePlaylistSearchCursor(final Context context, final String searchTerms) { - if (searchTerms == null || searchTerms.isEmpty()) { + if (TextUtils.isEmpty(searchTerms)) { return null; } // trim out special characters like % or \ as well as things like "a" "and" etc String trimmedSearchTerms = MusicUtils.getTrimmedName(searchTerms); - if (trimmedSearchTerms == null || trimmedSearchTerms.isEmpty()) { + if (TextUtils.isEmpty(trimmedSearchTerms)) { return null; } @@ -710,7 +861,8 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< } } - return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + return context.getContentResolver().query( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, new String[]{ /* 0 */ BaseColumns._ID, @@ -720,7 +872,50 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< } } + /** + * Loads the search history in the background and creates an array adapter + */ + public static class SearchHistoryLoader extends WrappedAsyncTaskLoader<ArrayAdapter<String>> { + public SearchHistoryLoader(Context context) { + super(context); + } + + @Override + public ArrayAdapter<String> loadInBackground() { + ArrayList<String> strings = SearchHistory.getInstance(getContext()).getRecentSearches(); + ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), + R.layout.list_item_search_history, R.id.line_one); + adapter.addAll(strings); + return adapter; + } + } + /** + * This handles the Loader callbacks for the search history + */ + public class SearchHistoryCallback implements LoaderCallbacks<ArrayAdapter<String>> { + @Override + public Loader<ArrayAdapter<String>> onCreateLoader(int i, Bundle bundle) { + // prep the loader in case the query takes a long time + setLoading(); + + return new SearchHistoryLoader(SearchActivity.this); + } + + @Override + public void onLoadFinished(Loader<ArrayAdapter<String>> searchHistoryAdapterLoader, + ArrayAdapter<String> searchHistoryAdapter) { + // show the search history + setState(VisibleState.SearchHistory); + + mSearchHistoryListView.setAdapter(searchHistoryAdapter); + } + + @Override + public void onLoaderReset(Loader<ArrayAdapter<String>> cursorAdapterLoader) { + + } + } /** * {@inheritDoc} |