[Android] [PATCH] Search module for Android TV UI
Geoffrey Métais
geoffrey.metais at gmail.com
Thu Oct 2 18:41:54 CEST 2014
---
vlc-android/AndroidManifest.xml | 6 ++
vlc-android/res/layout/tv_search | 8 ++
vlc-android/res/values/strings.xml | 3 +
.../org/videolan/vlc/gui/tv/MainTvActivity.java | 34 +++---
.../org/videolan/vlc/gui/tv/SearchActivity.java | 15 +++
.../org/videolan/vlc/gui/tv/SearchFragment.java | 114 +++++++++++++++++++++
.../src/org/videolan/vlc/gui/tv/TvUtil.java | 29 ++++++
7 files changed, 192 insertions(+), 17 deletions(-)
create mode 100644 vlc-android/res/layout/tv_search
create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/SearchActivity.java
create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/SearchFragment.java
create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java
diff --git a/vlc-android/AndroidManifest.xml b/vlc-android/AndroidManifest.xml
index b44325f..5234b8e 100644
--- a/vlc-android/AndroidManifest.xml
+++ b/vlc-android/AndroidManifest.xml
@@ -17,6 +17,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
+ <!-- Needed for audio search on TV -->
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:name="org.videolan.vlc.VLCApplication"
@@ -58,6 +60,10 @@
android:exported="true"
android:theme="@style/Theme.Leanback"/>
+ <activity android:name="org.videolan.vlc.gui.tv.SearchActivity"
+ android:exported="true"
+ android:theme="@style/Theme.Leanback"/>
+
<activity android:name=".gui.CompatErrorActivity" />
<activity android:name=".gui.PreferencesActivity" />
<activity
diff --git a/vlc-android/res/layout/tv_search b/vlc-android/res/layout/tv_search
new file mode 100644
index 0000000..c324a89
--- /dev/null
+++ b/vlc-android/res/layout/tv_search
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="org.videolan.vlc.gui.tv.SearchFragment"
+ android:id="@+id/search_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
\ No newline at end of file
diff --git a/vlc-android/res/values/strings.xml b/vlc-android/res/values/strings.xml
index f5f7180..20cea00 100644
--- a/vlc-android/res/values/strings.xml
+++ b/vlc-android/res/values/strings.xml
@@ -297,6 +297,9 @@
<string name="drawer_open">Open navigation drawer</string>
<string name="drawer_close">Close navigation drawer</string>
+ <!-- Android TV -->
+ <string name="search_results">Search results</string>
+
<string-array name="hardware_acceleration_list">
<item>@string/automatic</item>
<item>@string/hardware_acceleration_disabled</item>
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java b/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
index 9c1abbd..8ecac4e 100644
--- a/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
@@ -33,6 +33,8 @@ import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnItemClickedListener;
import android.support.v17.leanback.widget.Row;
import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
public class MainTvActivity extends Activity implements VideoBrowserInterface {
@@ -55,24 +57,18 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
OnItemClickedListener mItemClickListener = new OnItemClickedListener() {
@Override
public void onItemClicked(Object item, Row row) {
- Media media = (Media)item;
- if (media.getType() == Media.TYPE_VIDEO){
- VideoPlayerActivity.start(mContext, media.getLocation(), false);
- } else if (media.getType() == Media.TYPE_AUDIO){
-
- Intent intent = new Intent(MainTvActivity.this,
- DetailsActivity.class);
- // pass the item information
- intent.putExtra("item", (Parcelable)new MediaItemDetails(media.getTitle(), media.getArtist(), media.getAlbum()+"\n"+media.getLocation(), media.getLocation()));
- startActivity(intent);
- } else if (media.getType() == Media.TYPE_GROUP){
- Intent intent = new Intent(mContext, VerticalGridActivity.class);
- intent.putExtra("id", row.getId());
- startActivity(intent);
- }
+ TvUtil.openMedia(mContext, (Media)item, row);
}
};
+ OnClickListener mSearchClickedListenernew = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(mContext, SearchActivity.class);
+ startActivity(intent);
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -89,9 +85,13 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
mBrowseFragment.setTitle(getString(R.string.app_name));
mBrowseFragment.setBadgeDrawable(getResources().getDrawable(R.drawable.cone));
+ // set search icon color
+ mBrowseFragment.setSearchAffordanceColor(getResources().getColor(R.color.darkorange));
// add a listener for selected items
mBrowseFragment.setOnItemClickedListener(mItemClickListener);
+
+ mBrowseFragment.setOnSearchClickedListener(mSearchClickedListenernew);
mMediaLibrary.loadMediaItems(this, true);
mThumbnailer = new Thumbnailer(this, getWindowManager().getDefaultDisplay());
BackgroundManager.getInstance(this).attach(getWindow());
@@ -175,7 +175,7 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
// Empty item to launch grid activity
videoAdapter.add(new Media(null, 0, 0, Media.TYPE_GROUP, null, "Browse more", null, null, null, 0, 0, null, 0, 0));
- HeaderItem header = new HeaderItem(HEADER_VIDEO, "Videos", null);
+ HeaderItem header = new HeaderItem(HEADER_VIDEO, getString(R.string.video), null);
mRowsAdapter.add(new ListRow(header, videoAdapter));
}
@@ -188,7 +188,7 @@ public class MainTvActivity extends Activity implements VideoBrowserInterface {
// Empty item to launch grid activity
audioAdapter.add(new Media(null, 0, 0, Media.TYPE_GROUP, null, "Browse more", null, null, null, 0, 0, null, 0, 0));
- HeaderItem header = new HeaderItem(HEADER_MUSIC, "Music", null);
+ HeaderItem header = new HeaderItem(HEADER_MUSIC, getString(R.string.audio), null);
mRowsAdapter.add(new ListRow(header, audioAdapter));
}
mBrowseFragment.setAdapter(mRowsAdapter);
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/SearchActivity.java b/vlc-android/src/org/videolan/vlc/gui/tv/SearchActivity.java
new file mode 100644
index 0000000..be02217
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/SearchActivity.java
@@ -0,0 +1,15 @@
+package org.videolan.vlc.gui.tv;
+
+import org.videolan.vlc.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SearchActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.tv_search);
+ }
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/SearchFragment.java b/vlc-android/src/org/videolan/vlc/gui/tv/SearchFragment.java
new file mode 100644
index 0000000..e55dd64
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/SearchFragment.java
@@ -0,0 +1,114 @@
+package org.videolan.vlc.gui.tv;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.videolan.libvlc.Media;
+import org.videolan.vlc.MediaLibrary;
+import org.videolan.vlc.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.Row;
+import android.text.TextUtils;
+
+public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
+implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {
+
+ private static final String TAG = "SearchFragment";
+ private static final int SEARCH_DELAY_MS = 300;
+
+ private ArrayObjectAdapter mRowsAdapter;
+ private Handler mHandler = new Handler();
+ private SearchRunnable mDelayedLoad;
+ protected Activity mActivity;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+ setSearchResultProvider(this);
+ setOnItemClickedListener(getDefaultItemClickedListener());
+ mDelayedLoad = new SearchRunnable();
+ mActivity = getActivity();
+ }
+
+ @Override
+ public ObjectAdapter getResultsAdapter() {
+ return mRowsAdapter;
+ }
+
+ private void queryByWords(String words) {
+ mRowsAdapter.clear();
+ if (!TextUtils.isEmpty(words)) {
+ mDelayedLoad.setSearchQuery(words);
+ mHandler.removeCallbacks(mDelayedLoad);
+ mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+ }
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newQuery) {
+ queryByWords(newQuery);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ queryByWords(query);
+ return true;
+ }
+
+ private void loadRows(String query) {
+ ArrayList<Media> mediaList = MediaLibrary.getInstance().getMediaItems();
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+ for (Media media : mediaList) {
+ if (media.getTitle().toLowerCase().indexOf(query.toLowerCase()) >= 0
+ || media.getLocation().toLowerCase().indexOf(query.toLowerCase()) >= 0) {
+ listRowAdapter.add(media);
+ }
+ }
+ HeaderItem header = new HeaderItem(0, getResources().getString(R.string.search_results),
+ null);
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+ }
+
+ protected OnItemClickedListener getDefaultItemClickedListener() {
+ return new OnItemClickedListener() {
+ @Override
+ public void onItemClicked(Object item, Row row) {
+ if (item instanceof Media) {
+ TvUtil.openMedia(mActivity, (Media) item, row);
+ }
+ }
+ };
+ }
+
+ private class SearchRunnable implements Runnable {
+
+ private volatile String searchQuery;
+
+ public SearchRunnable() {}
+
+ public void run() {
+ loadRows(searchQuery);
+ }
+
+ public void setSearchQuery(String value) {
+ this.searchQuery = value;
+ }
+ }
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java b/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java
new file mode 100644
index 0000000..b9d6b20
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/TvUtil.java
@@ -0,0 +1,29 @@
+package org.videolan.vlc.gui.tv;
+
+import org.videolan.libvlc.Media;
+import org.videolan.vlc.gui.video.VideoPlayerActivity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.support.v17.leanback.widget.Row;
+
+public class TvUtil {
+
+
+ public static void openMedia(Activity activity, Media media, Row row){
+ if (media.getType() == Media.TYPE_VIDEO){
+ VideoPlayerActivity.start(activity, media.getLocation(), false);
+ } else if (media.getType() == Media.TYPE_AUDIO){
+ Intent intent = new Intent(activity,
+ DetailsActivity.class);
+ // pass the item information
+ intent.putExtra("item", (Parcelable)new MediaItemDetails(media.getTitle(), media.getArtist(), media.getAlbum()+"\n"+media.getLocation(), media.getLocation()));
+ activity.startActivity(intent);
+ } else if (media.getType() == Media.TYPE_GROUP){
+ Intent intent = new Intent(activity, VerticalGridActivity.class);
+ intent.putExtra("id", row.getId());
+ activity.startActivity(intent);
+ }
+ }
+}
--
1.9.1
More information about the Android
mailing list