[Android] [PATCH 01/11] First Android TV UI draft

Geoffrey Métais geoffrey.metais at gmail.com
Mon Nov 17 17:53:38 CET 2014


Basic video browsing with only one list for now.
Thumbnailer is plugged.

Audio browsing should work (but not play)
---
 vlc-android/AndroidManifest.xml                    |  19 +-
 vlc-android/project.properties                     |   2 +-
 vlc-android/res/layout/tv_details.xml              |   6 +
 vlc-android/res/layout/tv_main.xml                 |   7 +
 vlc-android/res/layout/tv_main_fragment.xml        |   7 +
 vlc-android/res/values/dimens.xml                  |   2 +
 vlc-android/src/org/videolan/vlc/Thumbnailer.java  |  16 +-
 .../src/org/videolan/vlc/gui/tv/CardPresenter.java |  98 +++++++++
 .../org/videolan/vlc/gui/tv/DetailsActivity.java   |  14 ++
 .../vlc/gui/tv/DetailsDescriptionPresenter.java    |  24 +++
 .../src/org/videolan/vlc/gui/tv/MainFragment.java  |  11 +
 .../org/videolan/vlc/gui/tv/MainTvActivity.java    | 232 +++++++++++++++++++++
 .../org/videolan/vlc/gui/tv/MediaItemDetails.java  |  24 +++
 .../vlc/gui/tv/MediaItemDetailsFragment.java       |  63 ++++++
 .../org/videolan/vlc/gui/tv/StringPresenter.java   |  29 +++
 .../src/org/videolan/vlc/gui/tv/TvMedia.java       | 136 ++++++++++++
 .../vlc/gui/video/VideoBrowserInterface.java       |  15 ++
 .../videolan/vlc/gui/video/VideoGridFragment.java  |   7 +-
 18 files changed, 700 insertions(+), 12 deletions(-)
 create mode 100644 vlc-android/res/layout/tv_details.xml
 create mode 100644 vlc-android/res/layout/tv_main.xml
 create mode 100644 vlc-android/res/layout/tv_main_fragment.xml
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/CardPresenter.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/DetailsActivity.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/DetailsDescriptionPresenter.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/MainFragment.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetails.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/StringPresenter.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/tv/TvMedia.java
 create mode 100644 vlc-android/src/org/videolan/vlc/gui/video/VideoBrowserInterface.java

diff --git a/vlc-android/AndroidManifest.xml b/vlc-android/AndroidManifest.xml
index cc26409..c5a2c60 100644
--- a/vlc-android/AndroidManifest.xml
+++ b/vlc-android/AndroidManifest.xml
@@ -27,6 +27,16 @@
         android:logo="@drawable/ic_logo_w"
         android:theme="@style/Theme.VLC.NoTitleBar" >
         <activity
+		    android:name="org.videolan.vlc.gui.tv.MainTvActivity"
+		    android:label="@string/app_name"
+		    android:theme="@style/Theme.Leanback">
+
+		    <intent-filter>
+		      <action android:name="android.intent.action.MAIN" />
+		      <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+		    </intent-filter>
+	  </activity>
+        <activity
             android:name=".gui.MainActivity"
             android:configChanges="orientation|screenSize"
             android:icon="@drawable/icon"
@@ -39,6 +49,11 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+      <activity android:name="org.videolan.vlc.gui.tv.DetailsActivity"
+        android:exported="true"
+        android:theme="@style/Theme.Leanback"/>
+
         <activity android:name=".gui.CompatErrorActivity" />
         <activity android:name=".gui.PreferencesActivity" />
         <activity
@@ -388,12 +403,12 @@
                 android:name="android.appwidget.provider"
                 android:resource="@xml/vlcwidget" />
         </receiver>
-        <receiver android:name=".RemoteControlClientReceiver" >
+        <!--receiver android:name=".RemoteControlClientReceiver" >
             <intent-filter>
                 <action android:name="android.intent.action.MEDIA_BUTTON" />
                 <action android:name="org.videolan.vlc.remote.PlayPause" />
             </intent-filter>
-        </receiver>
+        </receiver-->
     </application>
 
 </manifest>
diff --git a/vlc-android/project.properties b/vlc-android/project.properties
index 4ac9888..880b0ad 100644
--- a/vlc-android/project.properties
+++ b/vlc-android/project.properties
@@ -10,7 +10,7 @@
 #split.density=true
 
 # Project target.
-target=android-21
+target=android-L
 android.library.reference.1=../java-libs/appcompat
 android.library.reference.2=../java-libs/WheelView
 android.library.reference.3=../java-libs/cardview
diff --git a/vlc-android/res/layout/tv_details.xml b/vlc-android/res/layout/tv_details.xml
new file mode 100644
index 0000000..f000fbe
--- /dev/null
+++ b/vlc-android/res/layout/tv_details.xml
@@ -0,0 +1,6 @@
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="org.videolan.vlc.gui.tv.MediaItemDetailsFragment"
+    android:id="@+id/details_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/>
\ No newline at end of file
diff --git a/vlc-android/res/layout/tv_main.xml b/vlc-android/res/layout/tv_main.xml
new file mode 100644
index 0000000..4568719
--- /dev/null
+++ b/vlc-android/res/layout/tv_main.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="org.videolan.vlc.gui.tv.MainFragment"
+    android:id="@+id/main_browse_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+/>
diff --git a/vlc-android/res/layout/tv_main_fragment.xml b/vlc-android/res/layout/tv_main_fragment.xml
new file mode 100644
index 0000000..c220b43
--- /dev/null
+++ b/vlc-android/res/layout/tv_main_fragment.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+      android:name="android.support.v17.leanback.app.BrowseFragment"
+      android:id="@+id/browse_fragment"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      />
\ No newline at end of file
diff --git a/vlc-android/res/values/dimens.xml b/vlc-android/res/values/dimens.xml
index 1400716..fdbd4a8 100644
--- a/vlc-android/res/values/dimens.xml
+++ b/vlc-android/res/values/dimens.xml
@@ -10,4 +10,6 @@
     <dimen name="grid_card_title_text_size">12sp</dimen>
     <dimen name="grid_card_subtitle_text_size">10sp</dimen>
     <dimen name="grid_card_vertical_spacing">0dp</dimen>
+    <dimen name="tv_card_width">192dp</dimen>
+    <dimen name="tv_card_height">108dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/vlc-android/src/org/videolan/vlc/Thumbnailer.java b/vlc-android/src/org/videolan/vlc/Thumbnailer.java
index 90c789d..7e92819 100644
--- a/vlc-android/src/org/videolan/vlc/Thumbnailer.java
+++ b/vlc-android/src/org/videolan/vlc/Thumbnailer.java
@@ -33,7 +33,7 @@ import org.videolan.libvlc.LibVLC;
 import org.videolan.libvlc.LibVlcException;
 import org.videolan.libvlc.Media;
 import org.videolan.vlc.gui.MainActivity;
-import org.videolan.vlc.gui.video.VideoGridFragment;
+import org.videolan.vlc.gui.video.VideoBrowserInterface;
 import org.videolan.vlc.util.BitmapUtil;
 import org.videolan.vlc.util.VLCInstance;
 
@@ -47,7 +47,7 @@ import android.view.Display;
 public class Thumbnailer implements Runnable {
     public final static String TAG = "VLC/Thumbnailer";
 
-    private VideoGridFragment mVideoGridFragment;
+    private VideoBrowserInterface mVideoBrowser;
 
     private final Queue<Media> mItems = new LinkedList<Media>();
 
@@ -68,7 +68,7 @@ public class Thumbnailer implements Runnable {
         mPrefix = context.getResources().getString(R.string.thumbnail);
     }
 
-    public void start(VideoGridFragment videoGridFragment) {
+    public void start(VideoBrowserInterface videoBrowser) {
         if (mLibVlc == null) {
             try {
                 mLibVlc = VLCInstance.getLibVlcInstance();
@@ -81,7 +81,7 @@ public class Thumbnailer implements Runnable {
 
         isStopping = false;
         if (mThread == null || mThread.getState() == State.TERMINATED) {
-            mVideoGridFragment = videoGridFragment;
+            mVideoBrowser = videoBrowser;
             mThread = new Thread(this);
             mThread.start();
         }
@@ -129,7 +129,7 @@ public class Thumbnailer implements Runnable {
         Log.d(TAG, "Thumbnailer started");
 
         while (!isStopping) {
-            mVideoGridFragment.resetBarrier();
+            mVideoBrowser.resetBarrier();
             lock.lock();
             // Get the id of the file browser item to create its thumbnail.
             boolean interrupted = false;
@@ -177,11 +177,11 @@ public class Thumbnailer implements Runnable {
 
             MediaDatabase.setPicture(item, thumbnail);
             // Post to the file browser the new item.
-            mVideoGridFragment.setItemToUpdate(item);
+            mVideoBrowser.setItemToUpdate(item);
 
             // Wait for the file browser to process the change.
             try {
-                mVideoGridFragment.await();
+                mVideoBrowser.await();
             } catch (InterruptedException e) {
                 Log.i(TAG, "interruption probably requested by stop()");
                 break;
@@ -194,7 +194,7 @@ public class Thumbnailer implements Runnable {
         /* cleanup */
         MainActivity.hideProgressBar();
         MainActivity.clearTextInfo();
-        mVideoGridFragment = null;
+        mVideoBrowser = null;
         Log.d(TAG, "Thumbnailer stopped");
     }
 }
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/CardPresenter.java b/vlc-android/src/org/videolan/vlc/gui/tv/CardPresenter.java
new file mode 100644
index 0000000..5da55d5
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/CardPresenter.java
@@ -0,0 +1,98 @@
+package org.videolan.vlc.gui.tv;
+
+import org.videolan.libvlc.Media;
+import org.videolan.vlc.MediaDatabase;
+import org.videolan.vlc.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.Presenter;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class CardPresenter extends Presenter {
+
+	private static final String TAG = "CardPresenter";
+
+    private static Context sContext;
+    private static int CARD_WIDTH = 0;
+    private static int CARD_HEIGHT = 0;
+    private static Resources sResources;
+    private static MediaDatabase sMediaDatabase = MediaDatabase.getInstance();
+    private static Drawable sDefaultCardImage;
+
+    static class ViewHolder extends Presenter.ViewHolder {
+        private Media mMedia;
+        private ImageCardView mCardView;
+
+        public ViewHolder(View view) {
+            super(view);
+            mCardView = (ImageCardView) view;
+        }
+
+        public void setMovie(Media m) {
+            mMedia = m;
+        }
+
+        public Media getMovie() {
+            return mMedia;
+        }
+
+        public ImageCardView getCardView() {
+            return mCardView;
+        }
+
+        protected void updateCardViewImage(String mediaLocation) {
+        	mCardView.setMainImage(new BitmapDrawable(sResources, sMediaDatabase.getPicture(sContext, mediaLocation)));
+        }
+
+        protected void updateCardViewImage(Drawable image) {
+        	mCardView.setMainImage(image);
+        }
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        sContext = parent.getContext();
+        sResources = sContext.getResources();
+        sDefaultCardImage = sContext.getResources().getDrawable(R.drawable.cone);
+        if (CARD_WIDTH == 0) {
+			CARD_WIDTH = sResources.getDimensionPixelSize(
+					R.dimen.tv_card_width);
+			CARD_HEIGHT = sResources.getDimensionPixelSize(
+					R.dimen.tv_card_height);
+		}
+
+        ImageCardView cardView = new ImageCardView(sContext);
+        cardView.setFocusable(true);
+        cardView.setFocusableInTouchMode(true);
+        cardView.setBackgroundColor(sContext.getResources().getColor(R.color.lb_details_overview_bg_color));
+        return new ViewHolder(cardView);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+    	Media media = (Media) item;
+        ((ViewHolder) viewHolder).setMovie(media);
+
+        ((ViewHolder) viewHolder).mCardView.setTitleText(media.getTitle());
+        ((ViewHolder) viewHolder).mCardView.setContentText(media.getDescription());
+        ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
+        if (media.isPictureParsed())
+			((ViewHolder) viewHolder).updateCardViewImage(media.getLocation());
+		else
+			((ViewHolder) viewHolder).updateCardViewImage(sDefaultCardImage);
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+    }
+
+    @Override
+    public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
+        // TODO?
+    }
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/DetailsActivity.java b/vlc-android/src/org/videolan/vlc/gui/tv/DetailsActivity.java
new file mode 100644
index 0000000..9b4bc1d
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/DetailsActivity.java
@@ -0,0 +1,14 @@
+package org.videolan.vlc.gui.tv;
+
+import org.videolan.vlc.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class DetailsActivity extends Activity {
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.tv_details);
+	}
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/DetailsDescriptionPresenter.java b/vlc-android/src/org/videolan/vlc/gui/tv/DetailsDescriptionPresenter.java
new file mode 100644
index 0000000..22f456a
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/DetailsDescriptionPresenter.java
@@ -0,0 +1,24 @@
+package org.videolan.vlc.gui.tv;
+
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.util.Log;
+
+public class DetailsDescriptionPresenter extends
+		AbstractDetailsDescriptionPresenter {
+	public static final String TAG ="DetailsDescriptionPresenter";
+
+	protected void onBindDescription(ViewHolder viewHolder, Object itemData) {
+		Log.d(TAG, "itemData "+itemData);
+		MediaItemDetails details = (MediaItemDetails) itemData;
+		// In a production app, the itemData object contains the information
+		// needed to display details for the media item:
+		// viewHolder.getTitle().setText(details.getShortTitle());
+
+		// Here we provide static data for testing purposes:
+		viewHolder.getTitle().setText(details.getTitle());
+		viewHolder.getSubtitle().setText(details.getSubTitle());
+//		viewHolder.getBody().setText(details.getBody());
+	}
+
+	
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MainFragment.java b/vlc-android/src/org/videolan/vlc/gui/tv/MainFragment.java
new file mode 100644
index 0000000..ae5cc55
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MainFragment.java
@@ -0,0 +1,11 @@
+package org.videolan.vlc.gui.tv;
+
+import android.annotation.TargetApi;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+
+ at TargetApi(20)
+public class MainFragment extends Fragment {
+
+	
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java b/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
new file mode 100644
index 0000000..7650cf1
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MainTvActivity.java
@@ -0,0 +1,232 @@
+package org.videolan.vlc.gui.tv;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+
+import org.videolan.libvlc.Media;
+import org.videolan.vlc.MediaDatabase;
+import org.videolan.vlc.MediaLibrary;
+import org.videolan.vlc.R;
+import org.videolan.vlc.Thumbnailer;
+import org.videolan.vlc.VLCApplication;
+import org.videolan.vlc.gui.video.VideoBrowserInterface;
+import org.videolan.vlc.gui.video.VideoPlayerActivity;
+import org.videolan.vlc.util.WeakHandler;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.BrowseFragment;
+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.OnItemClickedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v4.app.FragmentActivity;
+
+public class MainTvActivity extends FragmentActivity implements VideoBrowserInterface {
+
+	public static final String TAG = "BrowseActivity";
+
+	protected BrowseFragment mBrowseFragment;
+	protected final CyclicBarrier mBarrier = new CyclicBarrier(2);
+	private MediaLibrary mMediaLibrary;
+	private Thumbnailer mThumbnailer;
+	protected Media mItemToUpdate;
+	ArrayObjectAdapter mRowsAdapter;
+	ArrayObjectAdapter videoAdapter;
+	ArrayObjectAdapter audioAdapter;
+	HashMap<String, Integer> mVideoIndex;
+	Drawable mDefaultBackground;
+	Activity mContext;
+
+	OnItemClickedListener mItemClickListener = new OnItemClickedListener() {
+		@Override
+		public void onItemClicked(Object item, Row row) {
+			VideoPlayerActivity.start(mContext, ((Media)item).getLocation(), false);
+			//code to launch a DetailActivity
+			//                    Intent intent = new Intent(MainTvActivity.this,
+			//                            DetailsActivity.class);
+			//                    // pass the item information
+			//                    intent.putExtra("id", row.getId());
+			//                    intent.putExtra("item", (Parcelable)item);
+			//                    startActivity(intent);
+		}
+	};
+
+	@Override
+	protected void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		mContext = this;
+		setContentView(R.layout.tv_main_fragment);
+
+		mMediaLibrary = MediaLibrary.getInstance();
+		mDefaultBackground = getResources().getDrawable(R.drawable.background);
+		final FragmentManager fragmentManager = getFragmentManager();
+		mBrowseFragment = (BrowseFragment) fragmentManager.findFragmentById(
+				R.id.browse_fragment);
+
+		// Set display parameters for the BrowseFragment
+		mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
+		mBrowseFragment.setTitle(getString(R.string.app_name));
+		mBrowseFragment.setBadgeDrawable(getResources().getDrawable(R.drawable.cone));
+
+		// add a listener for selected items
+		mBrowseFragment.setOnItemClickedListener(mItemClickListener);
+		mMediaLibrary.loadMediaItems(this, true);
+		mThumbnailer = new Thumbnailer(this, getWindowManager().getDefaultDisplay());
+		BackgroundManager.getInstance(this).attach(getWindow());
+	}
+
+	public void onResume() {
+		super.onResume();
+		mMediaLibrary.addUpdateHandler(mHandler);
+		if (mMediaLibrary.isWorking()) {
+			actionScanStart();
+		}
+
+		/* Start the thumbnailer */
+		if (mThumbnailer != null)
+			mThumbnailer.start(this);
+	}
+
+	public void onPause() {
+		super.onPause();
+		mMediaLibrary.removeUpdateHandler(mHandler);
+
+		/* Stop the thumbnailer */
+		if (mThumbnailer != null)
+			mThumbnailer.stop();
+	}
+
+	@Override
+	public void onDestroy() {
+		super.onDestroy();
+		if (mThumbnailer != null)
+			mThumbnailer.clearJobs();
+		mBarrier.reset();
+	}
+
+    protected void updateBackground(Drawable drawable) {
+        BackgroundManager.getInstance(this).setDrawable(drawable);
+    }
+
+    protected void clearBackground() {
+        BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
+    }
+
+
+	public static void actionScanStart() {
+		Intent intent = new Intent();
+		intent.setAction(ACTION_SCAN_START);
+		VLCApplication.getAppContext().sendBroadcast(intent);
+	}
+
+	public static void actionScanStop() {
+		Intent intent = new Intent();
+		intent.setAction(ACTION_SCAN_STOP);
+		VLCApplication.getAppContext().sendBroadcast(intent);
+	}
+
+	public void await() throws InterruptedException, BrokenBarrierException {
+		mBarrier.await();
+	}
+
+	public void resetBarrier() {
+		mBarrier.reset();
+	}
+
+	private void updateList() {
+		MediaDatabase mediaDatabase = MediaDatabase.getInstance();
+		ArrayList<Media> videoList = mMediaLibrary.getVideoItems();
+		ArrayList<Media> audioList = mMediaLibrary.getAudioItems();
+		int size;
+		Media item;
+		Bitmap picture;
+		mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+
+		// Update video section
+		if (!videoList.isEmpty()) {
+			size = videoList.size();
+			mVideoIndex = new HashMap<String, Integer>(size);
+			videoAdapter = new ArrayObjectAdapter(
+					new CardPresenter());
+			for (int i = 0 ; i < videoList.size() ; ++i) {
+				item = videoList.get(i);
+				picture = mediaDatabase.getPicture(this, item.getLocation());
+
+				videoAdapter.add(item);
+				mVideoIndex.put(item.getLocation(), i);
+				if (mThumbnailer != null){
+					if (picture== null) {
+						mThumbnailer.addJob(item);
+					} else {
+						MediaDatabase.setPicture(item, picture);
+						picture = null;
+					}
+				}
+			}
+			HeaderItem header = new HeaderItem(0, "Videos", null);
+			mRowsAdapter.add(new ListRow(header, videoAdapter));
+		}
+		
+		// update audio section
+		if (!audioList.isEmpty()) {
+			audioAdapter = new ArrayObjectAdapter(new CardPresenter());
+			for (Media music : audioList) {
+				audioAdapter.add(music);
+			}
+			HeaderItem header = new HeaderItem(1, "Music", null);
+			mRowsAdapter.add(new ListRow(header, audioAdapter));
+		}
+		mBrowseFragment.setAdapter(mRowsAdapter);
+	}
+
+	@Override
+	public void setItemToUpdate(Media item) {
+		mItemToUpdate = item;
+		mHandler.sendEmptyMessage(UPDATE_ITEM);
+	}
+
+	private void updateItem() {
+		videoAdapter.notifyArrayItemRangeChanged(mVideoIndex.get(mItemToUpdate.getLocation()), 1);
+		try {
+			mBarrier.await();
+		} catch (InterruptedException e) {
+		} catch (BrokenBarrierException e) {}
+	}
+
+	private Handler mHandler = new VideoListHandler(this);
+
+	private static class VideoListHandler extends WeakHandler<MainTvActivity> {
+		public VideoListHandler(MainTvActivity owner) {
+			super(owner);
+		}
+
+		@Override
+		public void handleMessage(Message msg) {
+			MainTvActivity owner = getOwner();
+			if(owner == null) return;
+
+			switch (msg.what) {
+			case UPDATE_ITEM:
+				owner.updateItem();
+				break;
+			case MediaLibrary.MEDIA_ITEMS_UPDATED:
+				owner.updateList();
+			}
+		}
+	};
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetails.java b/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetails.java
new file mode 100644
index 0000000..a3eb779
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetails.java
@@ -0,0 +1,24 @@
+package org.videolan.vlc.gui.tv;
+
+public class MediaItemDetails {
+
+	private String title, subTitle, body;
+
+	public MediaItemDetails(String title, String subTitle, String body) {
+		this.title = title;
+		this.subTitle = subTitle;
+		this.body = body;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public String getSubTitle() {
+		return subTitle;
+	}
+
+	public String getBody() {
+		return body;
+	}
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java b/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
new file mode 100644
index 0000000..d02025a
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/MediaItemDetailsFragment.java
@@ -0,0 +1,63 @@
+package org.videolan.vlc.gui.tv;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.v17.leanback.app.DetailsFragment;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.util.Log;
+
+public class MediaItemDetailsFragment extends DetailsFragment {
+	private static final String TAG = "MediaItemDetailsFragment";
+	private ArrayObjectAdapter mRowsAdapter;
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		Log.i(TAG, "onCreate");
+		super.onCreate(savedInstanceState);
+
+		buildDetails();
+	}
+
+	private void buildDetails() {
+		Bundle extras = getActivity().getIntent().getExtras();
+		Log.d(TAG, "id "+extras.getLong("id"));
+		TvMedia media = extras.getParcelable("item");
+		ClassPresenterSelector selector = new ClassPresenterSelector();
+		// Attach your media item details presenter to the row presenter:
+		DetailsOverviewRowPresenter rowPresenter =
+				new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+
+		selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
+		selector.addClassPresenter(ListRow.class,
+				new ListRowPresenter());
+		mRowsAdapter = new ArrayObjectAdapter(selector);
+
+		Resources res = getActivity().getResources();
+		DetailsOverviewRow detailsOverview = new DetailsOverviewRow(new MediaItemDetails(media.getTitle(), media.getDescription(), "Big body"));
+
+		// Add images and action buttons to the details view
+		detailsOverview.setImageDrawable(res.getDrawable(media.getCardImageId()));
+		detailsOverview.addAction(new Action(1, "Play"));
+		detailsOverview.addAction(new Action(2, "Delete"));
+		mRowsAdapter.add(detailsOverview);
+
+		// Add a Related items row
+		ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
+				new StringPresenter());
+		listRowAdapter.add("Media Item 1");
+		listRowAdapter.add("Media Item 2");
+		listRowAdapter.add("Media Item 3");
+		HeaderItem header = new HeaderItem(0, "Related Items", null);
+		mRowsAdapter.add(new ListRow(header, listRowAdapter));
+
+		setAdapter(mRowsAdapter);
+	}
+
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/StringPresenter.java b/vlc-android/src/org/videolan/vlc/gui/tv/StringPresenter.java
new file mode 100644
index 0000000..4eff8bc
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/StringPresenter.java
@@ -0,0 +1,29 @@
+package org.videolan.vlc.gui.tv;
+
+import org.videolan.vlc.R;
+
+import android.support.v17.leanback.widget.Presenter;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class StringPresenter extends Presenter {
+	private static final String TAG = "StringPresenter";
+
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        TextView textView = new TextView(parent.getContext());
+        textView.setFocusable(true);
+        textView.setFocusableInTouchMode(true);
+        textView.setBackground(
+                parent.getContext().getResources().getDrawable(R.drawable.background_cone));
+        return new ViewHolder(textView);
+    }
+
+    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+        ((TextView) viewHolder.view).setText(item.toString());
+    }
+
+    public void onUnbindViewHolder(ViewHolder viewHolder) {
+        // no op
+    }
+
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/tv/TvMedia.java b/vlc-android/src/org/videolan/vlc/gui/tv/TvMedia.java
new file mode 100644
index 0000000..cc7381b
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/tv/TvMedia.java
@@ -0,0 +1,136 @@
+package org.videolan.vlc.gui.tv;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class TvMedia implements Parcelable {
+	private long id;
+    private String mediaUrl;
+    private String title;
+    private String description;
+    private int bgImageId;
+    private int cardImageId;
+    private String bgImageUrl;
+    private String cardImageUrl;
+
+    public TvMedia(long id, String title, String description, String bgImageUrl, String cardImageUrl, String mediaUrl) {
+    	this.id = id;
+    	this.title = title;
+    	this.description = description; 
+    	this.bgImageUrl = bgImageUrl;
+    	this.cardImageUrl = cardImageUrl;
+    	this.mediaUrl = mediaUrl;
+    }
+
+    public TvMedia(long id, String title, String description, int bgImageId, int cardImageId, String mediaUrl) {
+    	this.id = id;
+    	this.title = title;
+    	this.description = description;
+    	this.bgImageId = bgImageId;
+    	this.cardImageId = cardImageId;
+    	this.mediaUrl = mediaUrl;
+    }
+
+    public long getId(){
+    	return id;
+    }
+    
+    public String getDescription(){
+    	return description;
+    }
+    
+    public String getBgImageUrl(){
+    	return bgImageUrl;
+    }
+    
+    public String getCardImageUrl(){
+    	return cardImageUrl;
+    }
+    
+    public String getVideoUrl(){
+    	return mediaUrl;
+    }
+    
+    public String getTitle(){
+    	return title;
+    }
+
+    public int getBackgroundImageId() {
+        return bgImageId;
+    }
+
+//    public URI getBackgroundImageURI() {
+//        try {
+//            Log.d("BACK MEDIA: ", bgImageUrl);
+//            return new URI(getBgImageUrl());
+//        } catch (URISyntaxException e) {
+//            Log.d("URI exception: ", bgImageUrl);
+//            return null;
+//        }
+//    }
+
+    public int getCardImageId() {
+    	return cardImageId;
+    }
+
+//    public URI getCardImageURI() {
+//        try {
+//            return new URI(getCardImageUrl());
+//        } catch (URISyntaxException e) {
+//            return null;
+//        }
+//    }
+
+    @Override
+    public String toString() {
+        return "Movie{" +
+                "id=" + id +
+                ", title='" + title + '\'' +
+                ", mediaUrl='" + mediaUrl + '\'' +
+                ", backgroundImageId='" + bgImageId + '\'' +
+//                ", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'' +
+                ", cardImageUrl='" + cardImageUrl + '\'' +
+                '}';
+    }
+
+	@Override
+	public int describeContents() {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+	@Override
+	public void writeToParcel(Parcel dest, int flags) {
+		dest.writeLong(id);
+		dest.writeString(mediaUrl);
+		dest.writeString(title);
+		dest.writeString(description);
+		dest.writeInt(bgImageId);
+		dest.writeInt(cardImageId);
+		dest.writeString(bgImageUrl);
+		dest.writeString(cardImageUrl);
+	}
+	
+	public static final Parcelable.Creator<TvMedia> CREATOR
+	= new Parcelable.Creator<TvMedia>() {
+		public TvMedia createFromParcel(Parcel in) {
+			return new TvMedia(in);
+		}
+
+		public TvMedia[] newArray(int size) {
+			return new TvMedia[size];
+		}
+	};
+
+	private TvMedia(Parcel in) {
+		id = in.readLong();
+		mediaUrl = in.readString();
+		title = in.readString();
+		description = in.readString();
+		bgImageId = in.readInt();
+		cardImageId = in.readInt();
+		bgImageUrl = in.readString();
+		cardImageUrl = in.readString();
+	}
+
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/video/VideoBrowserInterface.java b/vlc-android/src/org/videolan/vlc/gui/video/VideoBrowserInterface.java
new file mode 100644
index 0000000..12e5f1d
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/gui/video/VideoBrowserInterface.java
@@ -0,0 +1,15 @@
+package org.videolan.vlc.gui.video;
+
+import java.util.concurrent.BrokenBarrierException;
+
+import org.videolan.libvlc.Media;
+
+public interface VideoBrowserInterface {
+    static final String ACTION_SCAN_START = "org.videolan.vlc.gui.ScanStart";
+    static final String ACTION_SCAN_STOP = "org.videolan.vlc.gui.ScanStop";
+    static final int UPDATE_ITEM = 0;
+
+	public void resetBarrier();
+	public void setItemToUpdate(Media item);
+	public void await() throws InterruptedException, BrokenBarrierException;
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java b/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java
index 8de4cbe..ffa12d1 100644
--- a/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java
+++ b/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.java
@@ -74,7 +74,7 @@ import android.widget.PopupMenu;
 import android.widget.PopupMenu.OnMenuItemClickListener;
 import android.widget.TextView;
 
-public class VideoGridFragment extends SherlockGridFragment implements ISortable {
+public class VideoGridFragment extends SherlockGridFragment implements ISortable, VideoBrowserInterface {
 
     public final static String TAG = "VLC/VideoListFragment";
 
@@ -82,6 +82,11 @@ public class VideoGridFragment extends SherlockGridFragment implements ISortable
     protected static final String ACTION_SCAN_STOP = "org.videolan.vlc.gui.ScanStop";
     protected static final int UPDATE_ITEM = 0;
 
+    /* Constants used to switch from Grid to List and vice versa */
+    //FIXME If you know a way to do this in pure XML please do it!
+    private static final int GRID_ITEM_WIDTH_DP = 156;
+    private static final int GRID_HORIZONTAL_SPACING_DP = 20;
+    private static final int GRID_VERTICAL_SPACING_DP = 20;
     private static final int GRID_STRETCH_MODE = GridView.STRETCH_COLUMN_WIDTH;
     private static final int LIST_STRETCH_MODE = GridView.STRETCH_COLUMN_WIDTH;
 
-- 
1.9.1



More information about the Android mailing list