<!-- Search interface -->
<activity
android:name=".ui.activities.SearchActivity"
+ android:windowSoftInputMode="adjustResize"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/no_search_results"
+ android:layout_width="@dimen/no_results_width"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <ImageView
+ android:layout_width="@dimen/white_note_width"
+ android:layout_height="@dimen/white_note_height"
+ android:layout_gravity="center_horizontal"
+ android:paddingBottom="@dimen/white_note_padding_bottom"
+ android:scaleType="centerInside"
+ android:src="@drawable/white_note" />
+
+ <TextView
+ android:gravity="center"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/empty_search"
+ android:textColor="@color/no_search_results"
+ android:textSize="@dimen/search_text_main"
+ android:textStyle="bold" />
+
+ <TextView
+ android:id="@+id/no_search_results_text"
+ android:gravity="center"
+ android:ellipsize="end"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/no_results_text_height"
+ android:paddingBottom="@dimen/no_results_text_padding_bottom"
+ android:singleLine="true"
+ android:textColor="@color/no_search_results"
+ android:textSize="@dimen/search_text_main"
+ android:textStyle="bold|italic" />
+
+ <TextView
+ android:gravity="center"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/no_results_text_height"
+ android:fontFamily="sans-serif-light"
+ android:text="@string/empty_search_check"
+ android:textColor="@color/no_search_results"
+ android:textSize="@dimen/search_text_secondary" />
+ </LinearLayout>
+
+ <include android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ layout="@layout/list_base" />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012 Andrew Neal
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:id="@+id/menu_search"
+ android:icon="@drawable/ic_action_search"
+ android:orderInCategory="1"
+ android:showAsAction="ifRoom"
+ android:title="@string/menu_search"/>
+</menu>
<!-- Color for the Progress bar -->
<color name="circular_progress_bar_background">#bfababab</color>
<color name="circular_progress_bar">@color/white</color>
+
+ <!-- search no results text color -->
+ <color name="no_search_results">#ff231f20</color>
</resources>
<dimen name="list_item_progress_height">45.0dip</dimen>
<dimen name="list_item_progress_padding_left">10.0dip</dimen>
<dimen name="list_item_progress_padding_right">17.0dip</dimen>
+
+ <!-- Search Page Settings -->
+ <dimen name="white_note_width">60.0dip</dimen>
+ <dimen name="white_note_height">70.0dip</dimen>
+ <dimen name="white_note_padding_bottom">24.0dip</dimen>
+ <dimen name="no_results_width">260.0dip</dimen>
+ <dimen name="no_results_text_padding_bottom">16.0dip</dimen>
+ <dimen name="no_results_text_height">54.0dip</dimen>
+ <dimen name="search_text_main">20.0sp</dimen>
+ <dimen name="search_text_secondary">14.0sp</dimen>
</resources>
<string name="no_effects_for_you">The equalizer could not be opened.</string>
<string name="empty_music">To copy music from your computer to your device, use a USB cable.</string>
<string name="empty_last_added">Songs you have added over the last month will be shown here.</string>
- <string name="empty_search">No search results found</string>
+ <string name="empty_search">NO SEARCH RESULTS FOR</string>
+ <string name="empty_search_check">Please check that you have the correct spelling or try a different keyword.</string>
<string name="empty_favorite">Songs you mark as favorites will be shown here.</string>
<string name="empty_recent">Albums you have listened to will show up here. Try playing some music.</string>
<item name="android:listViewStyle">@style/ListView</item>
<item name="android:actionBarStyle">@style/ActionBar</item>
<item name="android:actionOverflowButtonStyle">@style/ActionOverFlowButton</item>
- <!-- this guy is too large - need to get a smaller one -->
- <!--item name="android:homeAsUpIndicator">@drawable/back_arrow</item-->
+ <item name="android:actionBarWidgetTheme">@style/ActionBarWidgetTheme</item>
+ <item name="android:homeAsUpIndicator">@drawable/ic_action_back</item>
</style>
- <!-- Make the action bar not take up space - I will remove action bar this upcoming week -->
+ <style name="ActionBarWidgetTheme" parent="@android:style/Theme.Holo">
+ <!-- This is the color of the search text in the action bar -->
+ <item name="android:textColorHint">@android:color/white</item>
+ <item name="android:popupMenuStyle">@style/PopupMenu</item>
+ <item name="android:dropDownListViewStyle">@style/DropDownListView</item>
+ <item name="android:textAppearanceSmallPopupMenu">@style/SmallPopupMenu</item>
+ <item name="android:textAppearanceLargePopupMenu">@style/LargePopupMenu</item>
+ </style>
+
+ <!-- Make the action bar not take up space -->
<style name="Eleven.Theme.ActionBar.Overlay" parent="@style/Eleven.Theme">
<item name="android:windowActionBarOverlay">true</item>
</style>
<item name="android:divider">@color/list_item_divider_color</item>
<item name="android:dividerHeight">1dp</item>
</style>
-
+
<!-- Sets up the pop up menu backgroudn resource -->
<style name="PopupMenu" parent="@android:style/Widget.Holo.PopupMenu">
<item name="android:popupBackground">@drawable/menu_background</item>
import com.cyngn.eleven.MusicPlaybackService;
import com.cyngn.eleven.R;
+import com.cyngn.eleven.model.AlbumArtistDetails;
import com.cyngn.eleven.utils.ApolloUtils;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.widgets.SquareImageView;
}
/**
- * Simple container for the album and artist name as well as album id
- */
- private static class AlbumArtistDetails {
- public long mAudioId;
- public long mAlbumId;
- public String mAlbumName;
- public String mArtistName;
- }
-
- /**
* This looks up the album and artist details for a track
*/
private static class AlbumArtistLoader extends AsyncTask<Long, Void, AlbumArtistDetails> {
@Override
protected AlbumArtistDetails doInBackground(final Long... params) {
long id = params[0];
-
- final StringBuilder selection = new StringBuilder();
- selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
- selection.append(" AND " + BaseColumns._ID + " = '" + id + "'");
-
- Cursor cursor = mContext.getContentResolver().query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- new String[] {
- /* 0 */
- MediaStore.Audio.AudioColumns.ALBUM_ID,
- /* 1 */
- MediaStore.Audio.AudioColumns.ALBUM,
- /* 2 */
- MediaStore.Audio.AlbumColumns.ARTIST,
- }, selection.toString(), null, null);
-
- if (!cursor.moveToFirst()) {
- cursor.close();
- return null;
- }
-
- AlbumArtistDetails result = new AlbumArtistDetails();
- result.mAudioId = id;
- result.mAlbumId = cursor.getLong(0);
- result.mAlbumName = cursor.getString(1);
- result.mArtistName = cursor.getString(2);
- cursor.close();
-
- return result;
+ return MusicUtils.getAlbumArtDetails(mContext, id);
}
@Override
--- /dev/null
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.adapters;
+
+import android.app.Activity;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.format.PrefixHighlighter;
+import com.cyngn.eleven.model.SearchResult;
+import com.cyngn.eleven.sectionadapter.SectionAdapter;
+import com.cyngn.eleven.ui.MusicHolder;
+import com.cyngn.eleven.utils.ApolloUtils;
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.Locale;
+
+/**
+ * Used to populate the list view with the search results.
+ */
+public final class SummarySearchAdapter extends ArrayAdapter<SearchResult> implements SectionAdapter.BasicAdapter {
+
+ /**
+ * Number of views (ImageView and TextView)
+ */
+ private static final int VIEW_TYPE_COUNT = 2;
+
+ /**
+ * Image cache and image fetcher
+ */
+ private final ImageFetcher mImageFetcher;
+
+ /**
+ * Highlights the query
+ */
+ private final PrefixHighlighter mHighlighter;
+
+ /**
+ * The prefix that's highlighted
+ */
+ private char[] mPrefix;
+
+ /**
+ * Combines two strings
+ */
+ private String mCombineString;
+
+ /**
+ * Constructor for <code>SearchAdapter</code>
+ *
+ * @param context The {@link Activity} to use.
+ */
+ public SummarySearchAdapter(final Activity context) {
+ super(context, 0);
+ // Initialize the cache & image fetcher
+ mImageFetcher = ApolloUtils.getImageFetcher(context);
+ // Create the prefix highlighter
+ mHighlighter = new PrefixHighlighter(context);
+
+ mCombineString = getContext().getResources().getString(R.string.combine_two_strings);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView(final int position, View convertView, final ViewGroup parent) {
+ /* Recycle ViewHolder's items */
+ MusicHolder holder;
+
+ if (convertView == null) {
+ convertView = LayoutInflater.from(getContext()).inflate(
+ getViewResourceId(position), parent, false);
+ holder = new MusicHolder(convertView);
+ convertView.setTag(holder);
+ } else {
+ holder = (MusicHolder)convertView.getTag();
+ }
+
+ final SearchResult item = getItem(position);
+
+ switch (item.mType) {
+ case Artist:
+ // Asynchronously load the artist image into the adapter
+ mImageFetcher.loadArtistImage(item.mArtist, holder.mImage.get());
+
+ mHighlighter.setText(holder.mLineOne.get(), item.mArtist, mPrefix);
+
+ String songCount = MusicUtils.makeLabel(getContext(), R.plurals.Nsongs, item.mSongCount);
+ String albumCount = MusicUtils.makeLabel(getContext(), R.plurals.Nalbums, item.mAlbumCount);
+ holder.mLineTwo.get().setText(String.format(mCombineString, songCount, albumCount));
+
+ break;
+ case Album:
+ // Asynchronously load the album images into the adapter
+ mImageFetcher.loadAlbumImage(item.mArtist, item.mAlbum,
+ item.mId, holder.mImage.get());
+
+ mHighlighter.setText(holder.mLineOne.get(), item.mAlbum, mPrefix);
+ mHighlighter.setText(holder.mLineTwo.get(), item.mArtist, mPrefix);
+ break;
+ case Song:
+ // Asynchronously load the album images into the adapter
+ mImageFetcher.loadAlbumImage(item.mArtist, item.mAlbum,
+ item.mAlbumId, holder.mImage.get());
+
+ mHighlighter.setText(holder.mLineOne.get(), item.mTitle, mPrefix);
+ mHighlighter.setText(holder.mLineTwo.get(),
+ String.format(mCombineString, item.mArtist, item.mAlbum), mPrefix);
+ break;
+ case Playlist:
+ mHighlighter.setText(holder.mLineOne.get(), item.mTitle, mPrefix);
+ String songs = MusicUtils.makeLabel(getContext(), R.plurals.Nsongs, item.mSongCount);
+ holder.mLineTwo.get().setText(songs);
+ holder.mLineThree.get().setVisibility(View.GONE);
+ break;
+ }
+
+ return convertView;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getViewTypeCount() {
+ return VIEW_TYPE_COUNT;
+ }
+
+ /**
+ * This categorizes the view types we want for each item type
+ * @param position of the item
+ * @return categorization
+ */
+ @Override
+ public int getItemViewType(int position) {
+ switch (getItem(position).mType) {
+ case Artist:
+ case Album:
+ case Song:
+ return 0;
+ default:
+ case Playlist:
+ return 1;
+ }
+ }
+
+ /**
+ * this returns the layout needed for the item
+ * @param position of the item
+ * @return layout id
+ */
+ public int getViewResourceId(int position) {
+ switch (getItemViewType(position)) {
+ case 0:
+ return R.layout.list_item_normal;
+ default:
+ return R.layout.list_item_simple;
+ }
+ }
+
+ /**
+ * @param pause True to temporarily pause the disk cache, false
+ * otherwise.
+ */
+ public void setPauseDiskCache(final boolean pause) {
+ if (mImageFetcher != null) {
+ mImageFetcher.setPauseDiskCache(pause);
+ }
+ }
+
+ /**
+ * @param prefix The query to filter.
+ */
+ public void setPrefix(final CharSequence prefix) {
+ if (!TextUtils.isEmpty(prefix)) {
+ mPrefix = prefix.toString().toUpperCase(Locale.getDefault()).toCharArray();
+ } else {
+ mPrefix = null;
+ }
+ }
+
+ @Override
+ public void unload() {
+ clear();
+ }
+
+ @Override
+ public void buildCache() {
+
+ }
+
+ @Override
+ public void flush() {
+ mImageFetcher.flush();
+ }
+
+ /**
+ * Gets the item position for a given id
+ * @param id identifies the object
+ * @return the position if found, -1 otherwise
+ */
+ @Override
+ public int getItemPosition(long id) {
+ for (int i = 0; i < getCount(); i++) {
+ if (getItem(i).mId == id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+}
\ No newline at end of file
* @param prefix the prefix to look for
*/
public CharSequence apply(final CharSequence text, final char[] prefix) {
- final int mIndex = indexOfWordPrefix(text, prefix);
+ int mIndex = indexOfPrefix(text, prefix, true);
+ // prefer word prefix, if not search through the entire word
+ if (mIndex == -1) {
+ mIndex = indexOfPrefix(text, prefix, false);
+ }
+
if (mIndex != -1) {
if (mPrefixColorSpan == null) {
mPrefixColorSpan = new ForegroundColorSpan(mPrefixHighlightColor);
}
/**
- * Finds the index of the first word that starts with the given prefix. If
+ * Finds the index of the first character that starts with the given prefix. If
* not found, returns -1.
*
* @param text the text in which to search for the prefix
* @param prefix the text to find, in upper case letters
+ * @param wordOnly only search for word prefixes if true
*/
- private int indexOfWordPrefix(final CharSequence text, final char[] prefix) {
+ private int indexOfPrefix(final CharSequence text, final char[] prefix, boolean wordOnly) {
if (TextUtils.isEmpty(text) || prefix == null) {
return -1;
}
return i;
}
- /* Skip this word */
- while (i < mTextLength && Character.isLetterOrDigit(text.charAt(i))) {
+ if (wordOnly) {
+ /* Skip this word */
+ while (i < mTextLength && Character.isLetterOrDigit(text.charAt(i))) {
+ i++;
+ }
+ } else {
i++;
}
}
return -1;
}
-
}
// Copy the playlist name
final String name = mCursor.getString(1);
- final int songCount = getSongCount(getContext(), id);
+ final int songCount = MusicUtils.getSongCountForPlaylist(getContext(), id);
// Create a new playlist
final Playlist playlist = new Playlist(id, name, songCount);
PlaylistsColumns.NAME
}, null, null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
}
-
- /**
- * Gets the number of songs for a playlist
- * @param context The {@link Context} to use.
- * @param playlistId the id of the playlist
- * @return the # of songs in the playlist or -1 if not found
- */
- public static final int getSongCount(final Context context, final long playlistId) {
- Cursor c = context.getContentResolver().query(
- MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
- new String[]{ BaseColumns._ID }, MusicUtils.MUSIC_ONLY_SELECTION, null, null);
-
- if (c != null && c.moveToFirst()) {
- int count = c.getCount();
- c.close();
- c = null;
- return count;
- }
-
- return -1;
- }
}
--- /dev/null
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.model;
+
+/**
+ * Simple containing class to query for album art
+ */
+public class AlbumArtistDetails {
+ public long mAudioId;
+ public long mAlbumId;
+ public String mAlbumName;
+ public String mArtistName;
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.model;
+
+import android.database.Cursor;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.Comparator;
+
+public class SearchResult {
+ private static final String TAG = SearchResult.class.getSimpleName();
+
+ public static final Comparator COMPARATOR = new Comparator<SearchResult>() {
+ @Override
+ public int compare(final SearchResult lhs, final SearchResult rhs) {
+ return lhs.mType.ordinal() - rhs.mType.ordinal();
+ }
+ };
+
+ public static enum ResultType {
+ Song,
+ Artist,
+ Album,
+ Playlist,
+ Unknown;
+
+ public static int getNumTypes() {
+ // # of items minus the unknown
+ return ResultType.values().length - 1;
+ }
+
+ public static ResultType getResultType(final String mimetype) {
+ if (mimetype != null) {
+ if (mimetype.equals("artist")) {
+ return Artist;
+ } else if (mimetype.equals("album")) {
+ return Album;
+ } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
+ || mimetype.equals("application/x-ogg")) {
+ return Song;
+ }
+ }
+
+ return Unknown;
+ }
+
+ public static ResultType getResultType(final Cursor cursor, int index) {
+ return getResultType(cursor.getString(index));
+ }
+
+ public static ResultType getResultType(final Cursor cursor) {
+ try {
+ return getResultType(cursor,
+ cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
+ } catch(IllegalArgumentException ex) {
+ return Unknown;
+ }
+ }
+ };
+
+ public ResultType mType;
+ public String mArtist;
+ public String mAlbum;
+ public String mTitle;
+ public long mId;
+ public long mAlbumId;
+ public int mAlbumCount;
+ public int mSongCount;
+
+ public static SearchResult createSearchResult(final Cursor cursor) {
+ SearchResult result = new SearchResult();
+
+ result.mType = ResultType.getResultType(cursor);
+
+ // not a valid mime type - quitting
+ if (result.mType == ResultType.Unknown) {
+ Log.e(TAG, "No valid mime type found!");
+ return null;
+ }
+
+ // Get the Id of the content
+ result.mId = cursor.getLong(cursor
+ .getColumnIndexOrThrow(android.provider.BaseColumns._ID));
+
+ // title
+ result.mTitle = cursor.getString(cursor
+ .getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
+
+ // Get the artist name
+ result.mArtist = cursor.getString(cursor
+ .getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
+
+ // Get the album name
+ result.mAlbum = cursor.getString(cursor
+ .getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+
+ // album count
+ result.mAlbumCount = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
+
+ // song count
+ result.mSongCount = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
+
+ return result;
+ }
+
+ public static SearchResult createPlaylistResult(final Cursor cursor) {
+ SearchResult result = new SearchResult();
+
+ result.mType = ResultType.Playlist;
+
+ // Get the Id of the content
+ result.mId = cursor.getLong(cursor
+ .getColumnIndexOrThrow(android.provider.BaseColumns._ID));
+
+ // title
+ result.mTitle = cursor.getString(cursor
+ .getColumnIndexOrThrow(MediaStore.Audio.PlaylistsColumns.NAME));
+
+ return result;
+ }
+}
\ No newline at end of file
return getViewTypeCount() - 1;
}
- return mUnderlyingAdapter.getItemViewType(position);
+ return mUnderlyingAdapter.getItemViewType(getInternalPosition(position));
}
/**
notifyDataSetChanged();
}
+ public void clear() {
+ mUnderlyingAdapter.clear();
+ mSectionHeaders.clear();
+ }
+
/**
* Gets the item position for the given identifier
* @param identifier used to identify the object
* {@inheritDoc}
*/
@Override
- public SectionListContainer loadInBackground() {
+ public SectionListContainer<T> loadInBackground() {
List<T> results = mLoader.loadInBackground();
TreeMap<Integer, String> sectionHeaders = null;
sectionHeaders = SectionCreatorUtils.createSections(results, mComparator);
}
- return new SectionListContainer(sectionHeaders, results);
+ return new SectionListContainer<T>(sectionHeaders, results);
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
// Search view
- getMenuInflater().inflate(R.menu.search, menu);
+ getMenuInflater().inflate(R.menu.search_btn, menu);
// Settings
getMenuInflater().inflate(R.menu.activity_base, menu);
- final SearchView searchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
- // Add voice search
- final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
- final SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
- searchView.setSearchableInfo(searchableInfo);
- // Perform the search
- searchView.setOnQueryTextListener(new OnQueryTextListener() {
-
- @Override
- public boolean onQueryTextSubmit(final String query) {
- // Open the search activity
- NavUtils.openSearch(BaseActivity.this, query);
- return true;
- }
-
- @Override
- public boolean onQueryTextChange(final String newText) {
- // Nothing to do
- return false;
- }
- });
return super.onCreateOptionsMenu(menu);
}
NavUtils.openSettings(this);
return true;
+ case R.id.menu_search:
+ NavUtils.openSearch(BaseActivity.this, "");
+ break;
+
default:
break;
}
});
// Set up the action bar
final ActionBar actionBar = getActionBar();
- actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setIcon(R.drawable.ic_action_back);
+ actionBar.setHomeButtonEnabled(true);
/* Set up the artist profile */
if (isArtist()) {
package com.cyngn.eleven.ui.activities;
-import static com.cyngn.eleven.utils.MusicUtils.mService;
-
-import android.app.Activity;
import android.app.ActionBar;
-import android.app.LoaderManager.LoaderCallbacks;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
-import android.content.CursorLoader;
import android.content.Context;
-import android.content.Intent;
-import android.content.Loader;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.media.AudioManager;
-import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.BaseColumns;
import android.provider.MediaStore;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
import android.text.TextUtils;
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.CursorAdapter;
-import android.widget.FrameLayout;
-import android.widget.GridView;
-import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;
import com.cyngn.eleven.IElevenService;
import com.cyngn.eleven.R;
-import com.cyngn.eleven.cache.ImageFetcher;
-import com.cyngn.eleven.format.PrefixHighlighter;
+import com.cyngn.eleven.adapters.SummarySearchAdapter;
+import com.cyngn.eleven.model.AlbumArtistDetails;
+import com.cyngn.eleven.model.SearchResult;
+import com.cyngn.eleven.model.SearchResult.ResultType;
import com.cyngn.eleven.recycler.RecycleHolder;
-import com.cyngn.eleven.ui.MusicHolder;
+import com.cyngn.eleven.sectionadapter.SectionAdapter;
+import com.cyngn.eleven.sectionadapter.SectionCreator;
+import com.cyngn.eleven.sectionadapter.SectionCreator.SimpleListLoader;
+import com.cyngn.eleven.sectionadapter.SectionListContainer;
import com.cyngn.eleven.utils.ApolloUtils;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.utils.MusicUtils.ServiceToken;
import com.cyngn.eleven.utils.NavUtils;
+import com.cyngn.eleven.utils.SectionCreatorUtils;
+import com.cyngn.eleven.utils.SectionCreatorUtils.IItemCompare;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
-import java.util.Locale;
+import static android.view.View.OnTouchListener;
+import static com.cyngn.eleven.utils.MusicUtils.mService;
/**
* Provides the search interface for Apollo.
*
* @author Andrew Neal (andrewdneal@gmail.com)
*/
-public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
- OnScrollListener, OnQueryTextListener, OnItemClickListener, ServiceConnection {
- /**
- * Grid view column count. ONE - list, TWO - normal grid
- */
- private static final int ONE = 1, TWO = 2;
-
+public class SearchActivity extends FragmentActivity implements LoaderCallbacks<SectionListContainer<SearchResult>>,
+ OnScrollListener, OnQueryTextListener, OnItemClickListener, ServiceConnection,
+ OnTouchListener {
/**
* The service token
*/
private String mFilterString;
/**
- * Grid view
+ * List view
*/
- private GridView mGridView;
+ private ListView mListView;
/**
- * List view adapter
+ * Used the filter the user's music
*/
- private SearchAdapter mAdapter;
-
- // Used the filter the user's music
private SearchView mSearchView;
/**
+ * IME manager
+ */
+ private InputMethodManager mImm;
+
+ /**
+ * The view that container the no search results text
+ */
+ private View mNoSearchResultsView;
+
+ /**
+ * List view adapter
+ */
+ private SectionAdapter<SearchResult, SummarySearchAdapter> mAdapter;
+
+ /**
* {@inheritDoc}
*/
- @SuppressWarnings("deprecation")
@Override
- protected void onCreate(final Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Fade it in
// Control the media volume
setVolumeControlStream(AudioManager.STREAM_MUSIC);
- // Bind Apollo's service
- mToken = MusicUtils.bindToService(this, this);
-
// Theme the action bar
final ActionBar actionBar = getActionBar();
- actionBar.setTitle(getString(R.string.app_name));
- actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(getString(R.string.app_name_uppercase));
+ actionBar.setHomeButtonEnabled(true);
+ // Bind Apollo's service
+ mToken = MusicUtils.bindToService(this, this);
// Set the layout
- setContentView(R.layout.grid_base);
+ setContentView(R.layout.search_layout);
- // Get the query
- final String query = getIntent().getStringExtra(SearchManager.QUERY);
- mFilterString = !TextUtils.isEmpty(query) ? query : null;
+ // get the input method manager
+ mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- // Action bar subtitle
- getActionBar().setSubtitle("\"" + mFilterString + "\"");
+ // Get the query String
+ mFilterString = getIntent().getStringExtra(SearchManager.QUERY);
// Initialize the adapter
- mAdapter = new SearchAdapter(this);
+ SummarySearchAdapter adapter = new SummarySearchAdapter(this);
+ mAdapter = new SectionAdapter<SearchResult, SummarySearchAdapter>(this, adapter);
// Set the prefix
- mAdapter.setPrefix(mFilterString);
- // Initialze the list
- mGridView = (GridView)findViewById(R.id.grid_base);
- // Bind the data
- mGridView.setAdapter(mAdapter);
- // Recycle the data
- mGridView.setRecyclerListener(new RecycleHolder());
- // Seepd up scrolling
- mGridView.setOnScrollListener(this);
- mGridView.setOnItemClickListener(this);
- if (ApolloUtils.isLandscape(this)) {
- mGridView.setNumColumns(TWO);
- } else {
- mGridView.setNumColumns(ONE);
+ mAdapter.getUnderlyingAdapter().setPrefix(mFilterString);
+
+ initListView();
+
+ mNoSearchResultsView = findViewById(R.id.no_search_results);
+ mNoSearchResultsView.setVisibility(View.INVISIBLE);
+
+ if (mFilterString != null && !mFilterString.isEmpty()) {
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getSupportLoaderManager().initLoader(0, null, this);
+
+ // Action bar subtitle
+ getActionBar().setSubtitle("\"" + mFilterString + "\"");
}
- // Prepare the loader. Either re-connect with an existing one,
- // or start a new one.
- getLoaderManager().initLoader(0, null, this);
+ }
+
+ /**
+ * Sets up the list view
+ */
+ private void initListView() {
+ // Initialize the grid
+ mListView = (ListView)findViewById(R.id.list_base);
+ // Set the data behind the list
+ mListView.setAdapter(mAdapter);
+ // Release any references to the recycled Views
+ mListView.setRecyclerListener(new RecycleHolder());
+ // Show the albums and songs from the selected artist
+ mListView.setOnItemClickListener(this);
+ // To help make scrolling smooth
+ mListView.setOnScrollListener(this);
+ mListView.setOnTouchListener(this);
}
/**
* {@inheritDoc}
*/
@Override
- protected void onNewIntent(final Intent intent) {
- super.onNewIntent(intent);
- final String query = intent.getStringExtra(SearchManager.QUERY);
- mFilterString = !TextUtils.isEmpty(query) ? query : null;
- // Set the prefix
- mAdapter.setPrefix(mFilterString);
- getLoaderManager().restartLoader(0, null, this);
+ public Loader<SectionListContainer<SearchResult>> onCreateLoader(final int id, final Bundle args) {
+ IItemCompare<SearchResult> comparator = SectionCreatorUtils.createSearchResultComparison(this);
+ return new SectionCreator<SearchResult>(this, new SummarySearchLoader(this, mFilterString),
+ comparator);
}
/**
getMenuInflater().inflate(R.menu.search, menu);
// Filter the list the user is looking it via SearchView
- mSearchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
+ MenuItem searchItem = menu.findItem(R.id.menu_search);
+ mSearchView = (SearchView)searchItem.getActionView();
mSearchView.setOnQueryTextListener(this);
+ searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ finish();
+ return true;
+ }
+ });
+
+ if (mFilterString == null || mFilterString.isEmpty()) {
+ menu.findItem(R.id.menu_search).expandActionView();
+ }
// Add voice search
final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
* {@inheritDoc}
*/
@Override
- public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
- final Uri uri = Uri.parse("content://media/external/audio/search/fancy/"
- + Uri.encode(mFilterString));
- final String[] projection = new String[] {
- BaseColumns._ID, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Artists.ARTIST,
- MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2"
- };
- return new CursorLoader(this, uri, projection, null, null, null);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
- if (data == null || data.isClosed() || data.getCount() <= 0) {
+ public void onLoadFinished(final Loader<SectionListContainer<SearchResult>> loader,
+ final SectionListContainer<SearchResult> data) {
+ // Check for any errors
+ if (data.mListResults.isEmpty()) {
// Set the empty text
- final TextView empty = (TextView)findViewById(R.id.empty);
- empty.setText(getString(R.string.empty_search));
- mGridView.setEmptyView(empty);
- return;
+ final TextView empty = (TextView)findViewById(R.id.no_search_results_text);
+ empty.setText("\"" + mFilterString + "\"");
+
+ // clear the adapter
+ mAdapter.clear();
+
+ // hide listview, show no results text
+ mListView.setVisibility(View.INVISIBLE);
+ mNoSearchResultsView.setVisibility(View.VISIBLE);
+ } else {
+ // Set the data
+ mAdapter.setData(data);
+
+ // show listview, hide no results text
+ mListView.setVisibility(View.VISIBLE);
+ mNoSearchResultsView.setVisibility(View.INVISIBLE);
}
- // Swap the new cursor in. (The framework will take care of closing the
- // old cursor once we return.)
- mAdapter.swapCursor(data);
}
/**
* {@inheritDoc}
*/
@Override
- public void onLoaderReset(final Loader<Cursor> loader) {
- // This is called when the last Cursor provided to onLoadFinished()
- // above is about to be closed. We need to make sure we are no
- // longer using it.
- mAdapter.swapCursor(null);
+ public void onLoaderReset(final Loader<SectionListContainer<SearchResult>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
}
/**
// Pause disk cache access to ensure smoother scrolling
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING
|| scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
- mAdapter.setPauseDiskCache(true);
+ mAdapter.getUnderlyingAdapter().setPauseDiskCache(true);
} else {
- mAdapter.setPauseDiskCache(false);
+ mAdapter.getUnderlyingAdapter().setPauseDiskCache(false);
mAdapter.notifyDataSetChanged();
}
}
@Override
public boolean onQueryTextSubmit(final String query) {
if (TextUtils.isEmpty(query)) {
+ mFilterString = "";
+ getActionBar().setSubtitle("");
return false;
}
+
+ hideInputManager();
+
+ // Action bar subtitle
+ getActionBar().setSubtitle("\"" + mFilterString + "\"");
+ return true;
+ }
+
+ 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.
if (mSearchView != null) {
- final InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) {
- imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
+ if (mImm != null && mImm.isImeShowing()) {
+ mImm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
}
mSearchView.clearFocus();
}
- // Action bar subtitle
- getActionBar().setSubtitle("\"" + mFilterString + "\"");
- return true;
}
/**
@Override
public boolean onQueryTextChange(final String newText) {
if (TextUtils.isEmpty(newText)) {
+ mListView.setVisibility(View.INVISIBLE);
+ mNoSearchResultsView.setVisibility(View.INVISIBLE);
+ mFilterString = "";
return false;
}
// Called when the action bar search text has changed. Update
// with this filter.
mFilterString = !TextUtils.isEmpty(newText) ? newText : null;
// Set the prefix
- mAdapter.setPrefix(mFilterString);
- getLoaderManager().restartLoader(0, null, this);
+ mAdapter.getUnderlyingAdapter().setPrefix(mFilterString);
+ getSupportLoaderManager().restartLoader(0, null, this);
return true;
}
@Override
public void onItemClick(final AdapterView<?> parent, final View view, final int position,
final long id) {
- Cursor cursor = mAdapter.getCursor();
- cursor.moveToPosition(position);
- if (cursor.isBeforeFirst() || cursor.isAfterLast()) {
- return;
- }
- // Get the MIME type
- final String mimeType = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
-
- // If it's an artist, open the artist profile
- if ("artist".equals(mimeType)) {
- NavUtils.openArtistProfile(this,
- cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)));
- } else if ("album".equals(mimeType)) {
- // If it's an album, open the album profile
- NavUtils.openAlbumProfile(this,
- cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)),
- cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST)),
- cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)));
- } else if (position >= 0 && id >= 0) {
- // If it's a song, play it and leave
- final long[] list = new long[] {
- id
- };
- MusicUtils.playAll(this, list, 0, false);
+ SearchResult item = mAdapter.getTItem(position);
+ switch (item.mType) {
+ case Artist:
+ NavUtils.openArtistProfile(this, item.mArtist);
+ break;
+ case Album:
+ NavUtils.openAlbumProfile(this, item.mAlbum, item.mArtist, item.mId);
+ break;
+ case Playlist:
+ NavUtils.openPlaylist(this, item.mId, null, item.mTitle);
+ break;
+ case Song:
+ // If it's a song, play it and leave
+ final long[] list = new long[]{
+ item.mId
+ };
+ MusicUtils.playAll(this, list, 0, false);
+ break;
}
-
- // Close it up
- cursor.close();
- cursor = null;
- // All done
- finish();
}
/**
}
/**
- * Used to populate the list view with the search results.
+ * This class loads a search result summary of items
*/
- private static final class SearchAdapter extends CursorAdapter {
-
- /**
- * Number of views (ImageView and TextView)
- */
- private static final int VIEW_TYPE_COUNT = 2;
-
- /**
- * Image cache and image fetcher
- */
- private final ImageFetcher mImageFetcher;
-
- /**
- * Highlights the query
- */
- private final PrefixHighlighter mHighlighter;
-
- /**
- * The prefix that's highlighted
- */
- private char[] mPrefix;
-
- /**
- * Constructor for <code>SearchAdapter</code>
- *
- * @param context The {@link Context} to use.
- */
- public SearchAdapter(final Activity context) {
- super(context, null, false);
- // Initialize the cache & image fetcher
- mImageFetcher = ApolloUtils.getImageFetcher(context);
- // Create the prefix highlighter
- mHighlighter = new PrefixHighlighter(context);
+ private static final class SummarySearchLoader extends SimpleListLoader<SearchResult> {
+ private static final int NUM_RESULTS_TO_GET = 3;
+
+ private final String mQuery;
+
+ public SummarySearchLoader(final Context context, final String query) {
+ super(context);
+ mQuery = query;
}
- /**
- * {@inheritDoc}
- */
@Override
- public void bindView(final View convertView, final Context context, final Cursor cursor) {
- /* Recycle ViewHolder's items */
- MusicHolder holder = (MusicHolder)convertView.getTag();
- if (holder == null) {
- holder = new MusicHolder(convertView);
- convertView.setTag(holder);
+ public List<SearchResult> loadInBackground() {
+ ArrayList<SearchResult> results = new ArrayList<SearchResult>();
+ // number of types to query for
+ final int numTypes = ResultType.getNumTypes();
+
+ // number of results we want
+ final int numResultsNeeded = NUM_RESULTS_TO_GET * numTypes;
+
+ // current number of results we have
+ int numResultsAdded = 0;
+
+ // count for each result type
+ int[] numOfEachType = new int[numTypes];
+
+ // search playlists first
+ Cursor playlistCursor = makePlaylistSearchCursor(getContext(), mQuery);
+ if (playlistCursor != null && playlistCursor.moveToFirst()) {
+ do {
+ // create the item
+ SearchResult item = SearchResult.createPlaylistResult(playlistCursor);
+ if (item != null) {
+ // get the song count
+ item.mSongCount = MusicUtils.getSongCountForPlaylist(getContext(), item.mId);
+
+ /// add the results
+ numResultsAdded++;
+ results.add(item);
+ }
+ } while (playlistCursor.moveToNext() && numResultsAdded < NUM_RESULTS_TO_GET);
+
+ // because we deal with playlists separately,
+ // just mark that we have the full # of playlists
+ // so that logic later can quit out early if full
+ numResultsAdded = NUM_RESULTS_TO_GET;
+
+ // close the cursor
+ playlistCursor.close();
+ playlistCursor = null;
}
- // Get the MIME type
- final String mimetype = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
-
- if (mimetype.equals("artist")) {
- holder.mImage.get().setScaleType(ScaleType.CENTER_CROP);
-
- // Get the artist name
- final String artist = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
- holder.mLineOne.get().setText(artist);
-
- // Get the album count
- final int albumCount = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
- holder.mLineTwo.get().setText(
- MusicUtils.makeLabel(context, R.plurals.Nalbums, albumCount));
-
- // Get the song count
- final int songCount = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
- holder.mLineThree.get().setText(
- MusicUtils.makeLabel(context, R.plurals.Nsongs, songCount));
-
- // Asynchronously load the artist image into the adapter
- mImageFetcher.loadArtistImage(artist, holder.mImage.get());
-
- // Highlght the query
- mHighlighter.setText(holder.mLineOne.get(), artist, mPrefix);
- } else if (mimetype.equals("album")) {
- holder.mImage.get().setScaleType(ScaleType.FIT_XY);
-
- // Get the Id of the album
- final long id = cursor.getLong(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
-
- // Get the album name
- final String album = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
- holder.mLineOne.get().setText(album);
-
- // Get the artist name
- final String artist = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
- holder.mLineTwo.get().setText(artist);
-
- // Asynchronously load the album images into the adapter
- mImageFetcher.loadAlbumImage(artist, album, id, holder.mImage.get());
- // Asynchronously load the artist image into the adapter
- mImageFetcher.loadArtistImage(artist, holder.mBackground.get());
-
- // Highlght the query
- mHighlighter.setText(holder.mLineOne.get(), album, mPrefix);
-
- } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
- || mimetype.equals("application/x-ogg")) {
- holder.mImage.get().setScaleType(ScaleType.CENTER_CROP);
- holder.mImage.get().setImageResource(R.drawable.default_artwork);
-
- // Get the track name
- final String track = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
- holder.mLineOne.get().setText(track);
-
- // Get the album name
- final String album = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
- holder.mLineTwo.get().setText(album);
-
- final String artist = cursor.getString(cursor
- .getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
- // Asynchronously load the artist image into the adapter
- mImageFetcher.loadArtistImage(artist, holder.mBackground.get());
- holder.mLineThree.get().setText(artist);
-
- // Highlght the query
- mHighlighter.setText(holder.mLineOne.get(), track, mPrefix);
+ // do fancy audio search
+ Cursor cursor = ApolloUtils.createSearchQueryCursor(getContext(), mQuery);
+
+ // pre-cache this index
+ final int mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE);
+
+ // walk through the cursor
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ // get the result type
+ ResultType type = ResultType.getResultType(cursor, mimeTypeIndex);
+
+ // if we still need this type
+ if (numOfEachType[type.ordinal()] < NUM_RESULTS_TO_GET) {
+ // get the search result
+ SearchResult item = SearchResult.createSearchResult(cursor);
+ if (item != null) {
+ if (item.mType == ResultType.Song) {
+ // get the album art details
+ AlbumArtistDetails details = MusicUtils.getAlbumArtDetails(getContext(), item.mId);
+ if (details != null) {
+ item.mArtist = details.mArtistName;
+ item.mAlbum = details.mAlbumName;
+ item.mAlbumId = details.mAlbumId;
+ }
+ }
+
+ // add it
+ results.add(item);
+ numOfEachType[type.ordinal()]++;
+ numResultsAdded++;
+
+ // if we have enough then quit
+ if (numResultsAdded >= numResultsNeeded) {
+ break;
+ }
+ }
+ }
+ } while (cursor.moveToNext());
+
+ cursor.close();
+ cursor = null;
}
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
- return ((Activity)context).getLayoutInflater().inflate(
- R.layout.list_item_detailed, parent, false);
- }
+ // sort our results
+ Collections.sort(results, SearchResult.COMPARATOR);
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean hasStableIds() {
- return true;
+ return results;
}
- /**
- * {@inheritDoc}
- */
- @Override
- public int getViewTypeCount() {
- return VIEW_TYPE_COUNT;
- }
+ public static Cursor makePlaylistSearchCursor(final Context context,
+ final String searchTerms) {
+ if (searchTerms == null || searchTerms.isEmpty()) {
+ return null;
+ }
- /**
- * @param pause True to temporarily pause the disk cache, false
- * otherwise.
- */
- public void setPauseDiskCache(final boolean pause) {
- if (mImageFetcher != null) {
- mImageFetcher.setPauseDiskCache(pause);
+ // trim out special characters like % or \ as well as things like "a" "and" etc
+ String trimmedSearchTerms = MusicUtils.getTrimmedName(searchTerms);
+
+ if (trimmedSearchTerms == null || trimmedSearchTerms.isEmpty()) {
+ return null;
}
- }
- /**
- * @param prefix The query to filter.
- */
- public void setPrefix(final CharSequence prefix) {
- if (!TextUtils.isEmpty(prefix)) {
- mPrefix = prefix.toString().toUpperCase(Locale.getDefault()).toCharArray();
- } else {
- mPrefix = null;
+ String[] keywords = trimmedSearchTerms.split(" ");
+
+ // prep the keyword for like search
+ for (int i = 0; i < keywords.length; i++) {
+ keywords[i] = "%" + keywords[i] + "%";
+ }
+
+ String where = "";
+
+ // make the where clause
+ for (int i = 0; i < keywords.length; i++) {
+ if (i == 0) {
+ where = "name LIKE ?";
+ } else {
+ where += " AND name LIKE ?";
+ }
}
+
+ return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+ new String[]{
+ /* 0 */
+ BaseColumns._ID,
+ /* 1 */
+ MediaStore.Audio.PlaylistsColumns.NAME
+ }, where, keywords, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
}
}
+
+
/**
* {@inheritDoc}
*/
// Nothing to do
}
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ hideInputManager();
+ return false;
+ }
}
mImageCache = ImageCache.getInstance(this);
// UP
- getActionBar().setDisplayHomeAsUpEnabled(true);
+ getActionBar().setIcon(R.drawable.ic_action_back);
+ getActionBar().setHomeButtonEnabled(true);
// Add the preferences
addPreferencesFromResource(R.xml.settings);
import com.cyngn.eleven.ui.activities.BaseActivity;
import com.cyngn.eleven.ui.activities.ProfileActivity;
import com.cyngn.eleven.utils.MusicUtils;
+import com.cyngn.eleven.utils.NavUtils;
import java.util.List;
mPlaylist = mAdapter.getItem(position);
String playlistName;
if (position == 0) {
- playlistName = getString(R.string.playlist_last_added);
- bundle.putString(Config.MIME_TYPE, getString(R.string.playlist_last_added));
+ String lastAdded = getString(R.string.playlist_last_added);
+ NavUtils.openPlaylist(getActivity(), -1, lastAdded, lastAdded);
} else {
- // User created
- playlistName = mPlaylist.mPlaylistName;
- bundle.putString(Config.MIME_TYPE, MediaStore.Audio.Playlists.CONTENT_TYPE);
- bundle.putLong(Config.ID, mPlaylist.mPlaylistId);
+ NavUtils.openPlaylist(getActivity(), mPlaylist.mPlaylistId, null, mPlaylist.mPlaylistName);
}
-
- bundle.putString(Config.NAME, playlistName);
-
- // Create the intent to launch the profile activity
- final Intent intent = new Intent(getActivity(), ProfileActivity.class);
- intent.putExtras(bundle);
- startActivity(intent);
}
/**
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Configuration;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
+import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.util.Log;
import android.util.TypedValue;
return 0;
}
+
+ /**
+ * Returns a fancy search query cursor
+ * @param context
+ * @param query query string
+ * @return cursor of the results
+ */
+ public static Cursor createSearchQueryCursor(final Context context, final String query) {
+ final Uri uri = Uri.parse("content://media/external/audio/search/fancy/"
+ + Uri.encode(query));
+ final String[] projection = new String[] {
+ BaseColumns._ID, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Artists.ARTIST,
+ MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2"
+ };
+
+ // no selection/selection/sort args - they are ignored by fancy search anyways
+ return context.getContentResolver().query(uri, projection, null, null, null);
+ }
}
import com.cyngn.eleven.loaders.PlaylistLoader;
import com.cyngn.eleven.loaders.SongLoader;
import com.cyngn.eleven.menu.FragmentMenuItems;
+import com.cyngn.eleven.model.AlbumArtistDetails;
import com.cyngn.eleven.provider.RecentStore;
import com.devspark.appmsg.AppMsg;
*/
public static final long getIdForArtist(final Context context, final String name) {
Cursor cursor = context.getContentResolver().query(
- MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[] {
- BaseColumns._ID
- }, ArtistColumns.ARTIST + "=?", new String[] {
- name
+ MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{
+ BaseColumns._ID
+ }, ArtistColumns.ARTIST + "=?", new String[]{
+ name
}, ArtistColumns.ARTIST);
int id = -1;
if (cursor != null) {
}
/**
+ * Gets the number of songs for a playlist
+ * @param context The {@link Context} to use.
+ * @param playlistId the id of the playlist
+ * @return the # of songs in the playlist
+ */
+ public static final int getSongCountForPlaylist(final Context context, final long playlistId) {
+ Cursor c = context.getContentResolver().query(
+ MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
+ new String[]{BaseColumns._ID}, MusicUtils.MUSIC_ONLY_SELECTION, null, null);
+
+ if (c != null && c.moveToFirst()) {
+ int count = c.getCount();
+ c.close();
+ c = null;
+ return count;
+ }
+
+ return 0;
+ }
+
+ public static final AlbumArtistDetails getAlbumArtDetails(final Context context, final long trackId) {
+ final StringBuilder selection = new StringBuilder();
+ selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
+ selection.append(" AND " + BaseColumns._ID + " = '" + trackId + "'");
+
+ Cursor cursor = context.getContentResolver().query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ new String[] {
+ /* 0 */
+ MediaStore.Audio.AudioColumns.ALBUM_ID,
+ /* 1 */
+ MediaStore.Audio.AudioColumns.ALBUM,
+ /* 2 */
+ MediaStore.Audio.AlbumColumns.ARTIST,
+ }, selection.toString(), null, null
+ );
+
+ if (!cursor.moveToFirst()) {
+ cursor.close();
+ return null;
+ }
+
+ AlbumArtistDetails result = new AlbumArtistDetails();
+ result.mAudioId = trackId;
+ result.mAlbumId = cursor.getLong(0);
+ result.mAlbumName = cursor.getString(1);
+ result.mArtistName = cursor.getString(2);
+ cursor.close();
+
+ return result;
+ }
+
+ /**
* @param context The {@link Context} to use.
* @param id The id of the album.
* @return The release date for an album.
/**
* A snippet is taken from MediaStore.Audio.keyFor method
* This will take a name, removes things like "the", "an", etc
+ * as well as special characters and return it
+ * @param name the string to trim
+ * @return the trimmed name
+ */
+ public static String getTrimmedName(String name) {
+ if (name == null || name.length() == 0) {
+ return name;
+ }
+
+ name = name.trim().toLowerCase();
+ if (name.startsWith("the ")) {
+ name = name.substring(4);
+ }
+ if (name.startsWith("an ")) {
+ name = name.substring(3);
+ }
+ if (name.startsWith("a ")) {
+ name = name.substring(2);
+ }
+ if (name.endsWith(", the") || name.endsWith(",the") ||
+ name.endsWith(", an") || name.endsWith(",an") ||
+ name.endsWith(", a") || name.endsWith(",a")) {
+ name = name.substring(0, name.lastIndexOf(','));
+ }
+ name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
+
+ return name;
+ }
+
+ /**
+ * A snippet is taken from MediaStore.Audio.keyFor method
+ * This will take a name, removes things like "the", "an", etc
* as well as special characters, then find the localized label
* @param name Name to get the label of
* @param trimName boolean flag to run the trimmer on the name
}
if (trimName) {
- name = name.trim().toLowerCase();
- if (name.startsWith("the ")) {
- name = name.substring(4);
- }
- if (name.startsWith("an ")) {
- name = name.substring(3);
- }
- if (name.startsWith("a ")) {
- name = name.substring(2);
- }
- if (name.endsWith(", the") || name.endsWith(",the") ||
- name.endsWith(", an") || name.endsWith(",an") ||
- name.endsWith(", a") || name.endsWith(",a")) {
- name = name.substring(0, name.lastIndexOf(','));
- }
- name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
+ name = getTrimmedName(name);
}
if (name.length() > 0) {
}
/**
+ * Opens the playlist view
+ *
+ * @param context The {@link Activity} to use.
+ * @param playlistId the id of the playlist
+ * @param mimeType the mimetype if specified, or the default if null passed in
+ * @param playlistName the playlist name
+ */
+ public static void openPlaylist(final Activity context, final long playlistId,
+ final String mimeType, final String playlistName) {
+ final Bundle bundle = new Bundle();
+ bundle.putLong(Config.ID, playlistId);
+ if (mimeType != null) {
+ bundle.putString(Config.MIME_TYPE, mimeType);
+ } else {
+ bundle.putString(Config.MIME_TYPE, MediaStore.Audio.Playlists.CONTENT_TYPE);
+ }
+ bundle.putString(Config.NAME, playlistName);
+
+ // Create the intent to launch the profile activity
+ final Intent intent = new Intent(context, ProfileActivity.class);
+ intent.putExtras(bundle);
+ context.startActivity(intent);
+ }
+
+ /**
* Opens the sound effects panel or DSP manager in CM
*
* @param context The {@link Activity} to use.
}
/**
- * Opens to {@link SearchActivity}.
+ * Opens to {@link com.cyngn.eleven.ui.activities.SearchActivity}.
*
* @param activity The {@link Activity} to use.
* @param query The search query.
import com.cyngn.eleven.R;
import com.cyngn.eleven.model.Album;
import com.cyngn.eleven.model.Artist;
+import com.cyngn.eleven.model.SearchResult;
import com.cyngn.eleven.model.Song;
import java.util.List;
return sectionCreator;
}
+
+ /**
+ * Returns an song comparison based on the current sort
+ * @param context Context for string generation
+ * @return the song comparison method
+ */
+ public static IItemCompare<SearchResult> createSearchResultComparison(final Context context) {
+ return new IItemCompare<SearchResult>() {
+
+ @Override
+ public String createSectionSeparator(SearchResult first, SearchResult second) {
+ if (first == null || first.mType != second.mType) {
+ return createLabel(second);
+ }
+
+ return null;
+ }
+
+ @Override
+ public String createLabel(SearchResult item) {
+ switch (item.mType) {
+ case Artist:
+ return context.getString(R.string.page_artists);
+ case Album:
+ return context.getString(R.string.page_albums);
+ case Song:
+ return context.getString(R.string.page_songs);
+ case Playlist:
+ return context.getString(R.string.page_playlists);
+ }
+
+ return null;
+ }
+ };
+ }
}