[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