Play HTML5 fullscreen Video on Android WebView

HTML5 introduced the video element for the purpose of playing videos or movies.  HTML5 video is intended to become the new standard way to show video on the web without plugins.  Almost all desktop and mobile browsers support the video element. Visit http://caniuse.com/video to check the HTML5 video support on different browsers.

In this post I’m going to explain how to play a HTML5 video in a Android WebView.

This is the test HTML page which has a video element.

<!DOCTYPE html>
<html>
<body>
<video id="video" width="320" height="240" controls>
 <source src="http://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
</video>
</body>
</html>

Save this file in the assets folder. In the Android activity load the url into the WebView.

WebView mWebView = (WebView) findViewById(R.id.webView1);
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.loadUrl("file:///android_asset/test.html");

You should get the Internet permission in the manifest file.

<uses-permission android:name="android.permission.INTERNET"/>

Run the application and you can view the HTML5 video in the WebView.

html5 video

 

The video plays correctly. But the if we click the full screen button it won’t work. We should handle it manually. First change the layout like this.

<FrameLayout
 android:id="@+id/main_content"
 android:layout_width="match_parent"
 android:layout_height="300dip" >

<WebView
 android:id="@+id/webView1"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent" />
 </FrameLayout>

 <FrameLayout
 android:id="@+id/target_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:visibility="gone">
 </FrameLayout>

Then extend the WebChromeClient class and we should override the onShowCustomView and onHideCustomView methods.

	class MyChromeClient extends WebChromeClient {

		@Override
		public void onShowCustomView(View view, CustomViewCallback callback) {
		    mCustomViewCallback = callback;
			mTargetView.addView(view);
			mCustomView = view;
			mContentView.setVisibility(View.GONE);
			mTargetView.setVisibility(View.VISIBLE);
			mTargetView.bringToFront();
		}

		@Override
		public void onHideCustomView() {
			if (mCustomView == null)
				return;

			mCustomView.setVisibility(View.GONE);
			mTargetView.removeView(mCustomView);
			mCustomView = null;
			mTargetView.setVisibility(View.GONE);
			mCustomViewCallback.onCustomViewHidden();
			mContentView.setVisibility(View.VISIBLE);
		}
	}

In the onCreate method, set the custom WebChromeClient to the WebView.

	private FrameLayout mTargetView;
	private FrameLayout mContentView;
	private CustomViewCallback mCustomViewCallback;
	private View mCustomView;
	private MyChromeClient mClient;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		WebView mWebView = (WebView) findViewById(R.id.webView1);
		mClient = new MyChromeClient();
		mWebView.setWebChromeClient(mClient);
		mWebView.loadUrl("file:///android_asset/test.html");

		mContentView = (FrameLayout) findViewById(R.id.main_content);
		mTargetView = (FrameLayout)findViewById(R.id.target_view);
	}

Finally override the onBackPressed method of the Activity and exit full screen when back is pressed.

	@Override
	public void onBackPressed(){
		if (mCustomView != null){
			mClient.onHideCustomView();
		}else{
			finish();
		}
	}

Now when we run the application, we can go full screen using the button.

Complete Android project can be found here http://www.sendspace.com/file/u1ty03

Android Jelly Bean notifications with actions

Recently I had to work with Android JB style notifications. Unlike previous versions, we can add custom actions to these new notifications. What I had to do was upload some files to a server using a foreground service and add an option to cancel the uploading. So I added the Cancel Upload action to my notification.

Image

To add this action I used addAction (int icon, CharSequence title, PendingIntent intent) method.  When the notification is tapped I needed to open the app but I didn’t want to open the app when the Cancel action is tapped.

After some research I was managed to achieve this using a broadcast receiver.

This is my foreground service with the notification.

public class UploadService extends IntentService{

	private NotificationCompat.Builder mBuilder;

	public UploadService() {
		super("UploadService");
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		Intent deleteIntent = new Intent(this, CancelUploadReceiver.class);
		PendingIntent pendingIntentCancel = PendingIntent.getBroadcast(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT);

		//building the notification
		mBuilder = new NotificationCompat.Builder(this)
		.setSmallIcon(android.R.drawable.ic_menu_upload)
		.setContentTitle("Uploading Media...")
		.setTicker("Starting uploads")
		.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Cancel Upload", pendingIntentCancel);

		Intent notificationIntent = new Intent(this, MainActivity.class);
		notificationIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
		PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
		mBuilder.setContentIntent(pendingIntent);
		mBuilder.setProgress(100, 0, true);

		startForeground(12345, mBuilder.build());

		for(int i=0;i<10;i++){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

	}

}

You need to register the CancelUploadReceiver in the manifest file.

<receiver android:name=".CancelUploadReceiver"/>

And when the “Cancel Upload” is tapped it will receive the broadcast. Then we can simply stop the service.

public class CancelUploadReceiver extends BroadcastReceiver{

@Override
 public void onReceive(Context context, Intent intent) {
   Intent service = new Intent();
   service.setComponent(new ComponentName(context,UploadService.class));
   context.stopService(service);
 }

}

clone() method and Singleton pattern in Java

When we are writing a class using the Singleton pattern, there should be only one instance of that class at a time. We implement this by making the constructor private and adding a getInstance() method.

But what happens if we clone an instance of this class? You might think it won’t work because the constructor is private. But it works!! So we can actually make two instances of that class. Java does NOT handle this and we should do that by ourselves.

So how can we stop this? We need to override the clone() method like this,

@Override
protected Object clone() throws CloneNotSupportedException{
	throw new CloneNotSupportedException();
}

Now whenever we try to make a clone it will throw this CloneNotSupportedException.

Android ViewPager with Multiple Views

Recently I had to customize the standard ViewPager in order to make multiple Views visible at the same time. Here’s how I achieved this.

First let’s add a standard ViewPager to our Activity.

In the layout XML file,

<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF">

<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="200dip"
android:layout_height="200dip"
android:layout_gravity="center_horizontal|center_vertical"/>
</FrameLayout>

In the Activity we need to set a PagerAdapter which will supply views to the ViewPager.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
	viewPager.setAdapter(new ViewAdapter());
}

private class ViewAdapter extends PagerAdapter{

@Override
public int getCount() {
	return 4;
}

@Override
public boolean isViewFromObject(View view, Object object) {
	return (view == object);
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
	TextView textView = new TextView(MainActivity.this);
	textView.setText(“Item “+position);
	textView.setBackgroundResource(R.drawable.ic_launcher);
	((ViewPager)container).addView(textView, 0);
	return textView;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
	((ViewPager) container).removeView((TextView)object);
}

}

When we Run the application the output looks like this.

viewpager1

We can swipe left and right to navigate through Views. But we can see only one View at a time! 

We can modify this code and make the hidden views visible all the time.

First we need to create a custom container for the ViewPager by extending FrameLayout. Let’s call it ViewPagerContainer. And call this init method in its constructor.

private void init() {
	setClipChildren(false);
	setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

We have to use this as the container instead of the FrameLayout in the layout XML file.

<com.example.viewpagerexample.ViewPagerContainer

android:id=”@+id/container”
android:layout_width=”match_parent”
android:layout_height=”fill_parent”
android:background=”#FFFFFF”>

<android.support.v4.view.ViewPager
android:id=”@+id/viewpager”
android:layout_width=”200dip”
android:layout_height=”200dip”
android:layout_gravity=”center_horizontal|center_vertical”/>

</com.example.viewpagerexample.ViewPagerContainer>

Finally in the Activity add these lines.

ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(new ViewAdapter());
viewPager.setOffscreenPageLimit(5);
viewPager.setPageMargin(15);
viewPager.setClipChildren(false);

Now when we run the application it should look like this. It will not clip the child views now.

viewpager2

But there’s a problem here. We can swipe only the middle view. This is because the touch events occur outside the ViewPagers bounds. We can override the onTouchEvent of the ViewPagerContainer and dispatch the events to the ViewPager to solve this.

private ViewPager mPager;

@Override
protected void onFinishInflate() {
	mPager = (ViewPager) getChildAt(0);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
	return mPager.dispatchTouchEvent(ev);
}

How to mark a range in Android ProgressBar/SeekBar

There’s no straight forward way to mark a range in Android ProgressBar or SeekBar. They only have the setProgress method. But I needed to fill the ProgressBar from 35 to 70. I managed to do it using ClipDrawables.

In the xml file,

<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dip"
android:max="100" />

In the code,

int left = 35;
int right = 70;

ProgressBar progressBar = (ProgressBar)findViewById(R.id.progressBar1);
Drawable drawable = progressBar.getProgressDrawable();
ClipDrawable clipDrawable = new ClipDrawable(drawable, 
Gravity.LEFT, ClipDrawable.HORIZONTAL);
ClipDrawable clipDrawable2 = new ClipDrawable(clipDrawable, 
Gravity.RIGHT, ClipDrawable.HORIZONTAL);
progressBar.setProgressDrawable(clipDrawable2);
clipDrawable2.setLevel((100-left)*100);
clipDrawable.setLevel(right*100);