Tuesday, May 28, 2013

Get video size for MediaPlayer by implementing onVideoSizeChanged()

Further work on the exercise "Simple example to play stream video from internet using MediaPlayer" and "Release MediaPlayer in onDestroy() and onPause()", to get video size we can implement onVideoSizeChanged().

Get video size for MediaPlayer by implementing onVideoSizeChanged()


  • Define your Activity with implements OnVideoSizeChangedListener
  • Implement your OnVideoSizeChangedListener(), update SurfaceHolder accordingly by calling its setFixedSize() method
  • Register your OnVideoSizeChangedListener by calling mediaPlayer.setOnVideoSizeChangedListener(this)

Modify MainActivity.java
package com.example.androidmediaplayer;

import java.io.IOException;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnVideoSizeChangedListener;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;
import android.app.Activity;

public class MainActivity extends Activity 
 implements SurfaceHolder.Callback, 
 OnPreparedListener, OnVideoSizeChangedListener{
 
 MediaPlayer mediaPlayer;
 SurfaceHolder surfaceHolder;
 SurfaceView playerSurfaceView;
 String videoSrc = "rtsp://v6.cache1.c.youtube.com/CjYLENy73wIaLQkDsLHya4-Z9hMYDSANFEIJbXYtZ29vZ2xlSARSBXdhdGNoYKX4k4uBjbOiUQw=/0/0/0/video.3gp";

 int videoWidth, videoHeight;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  playerSurfaceView = (SurfaceView)findViewById(R.id.playersurface);

        surfaceHolder = playerSurfaceView.getHolder();
        surfaceHolder.addCallback(this);
 }

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0) {

        try {
         mediaPlayer = new MediaPlayer();
            mediaPlayer.setDisplay(surfaceHolder);
   mediaPlayer.setDataSource(videoSrc);
   mediaPlayer.prepare();
   mediaPlayer.setOnPreparedListener(this);
   mediaPlayer.setOnVideoSizeChangedListener(this);
   mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  } catch (IllegalArgumentException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SecurityException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IllegalStateException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder arg0) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void onPrepared(MediaPlayer mp) {
  surfaceHolder.setFixedSize(videoWidth, videoHeight);
  mediaPlayer.start();
 }

 @Override
 protected void onDestroy() {
  // TODO Auto-generated method stub
  super.onDestroy();
  if (mediaPlayer != null) {
   mediaPlayer.release();
   mediaPlayer = null; 
  }
 }

 @Override
 protected void onPause() {
  // TODO Auto-generated method stub
  super.onPause();
  if (mediaPlayer != null) {
   mediaPlayer.release();
   mediaPlayer = null; 
  }
 }

 @Override
 public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
  videoWidth = width;
  videoHeight = height;
  Toast.makeText(getApplicationContext(), 
    String.valueOf(videoWidth) + "x" + String.valueOf(videoHeight), 
    Toast.LENGTH_SHORT).show();
  
  if (mediaPlayer.isPlaying()){
   surfaceHolder.setFixedSize(videoWidth, videoHeight);
  }
 }

}


download filesDownload the files.

Monday, May 27, 2013

Processing + Android

Processing is an open source programming language and environment for people who want to create images, animations, and interactions. Initially developed to serve as a software sketchbook and to teach fundamentals of computer programming within a visual context, Processing also has evolved into a tool for generating finished professional work. Today, there are tens of thousands of students, artists, designers, researchers, and hobbyists who use Processing for learning, prototyping, and production.
  • Free to download and open source
  • Interactive programs using 2D, 3D or PDF output
  • OpenGL integration for accelerated 3D
  • For GNU/Linux, Mac OS X, and Windows
  • Projects run online or as double-clickable applications
  • Over 100 libraries extend the software into sound, video, computer vision, and more...
  • Well documented, with many books available

Processing for Android project is aim to make it foolishly easy to create Android apps using the Processing API. Once you have Processing on your machine (and the Android developer tools), you can simply write a line of code, hit Run (Ctrl-R or Cmd-R), and have your sketch show up in the emulator as a working Android app. Select Run on Device (Ctrl-Shift-R or Cmd-Shift-R) to have it run on an Android device that you have plugged into your machine. That's good stuff!

Read Tutorial to develop Android App with Processing version 2.0+.





Next:
- Download and install Processing software
- Setup Processing for Android development
- Hello World of Processing for Android


Release MediaPlayer in onDestroy() and onPause()

If you try run the last exercise of "Simple example of using MediaPlayer", you can notice that if you exit the app, the sound will still playing, and then switch back to the app, both old sound and new sound will overlap.

Actually, you have to release the MediaPlayer in onDestroy() and onPause(), when the app exit. Or any other handling method, depends on your own case.

 @Override
 protected void onDestroy() {
  // TODO Auto-generated method stub
  super.onDestroy();
  if (mediaPlayer != null) {
   mediaPlayer.release();
   mediaPlayer = null; 
  }
 }

 @Override
 protected void onPause() {
  // TODO Auto-generated method stub
  super.onPause();
  if (mediaPlayer != null) {
   mediaPlayer.release();
   mediaPlayer = null; 
  }
 }

Simple example of using MediaPlayer


Next:
- Get video size for MediaPlayer by implementing onVideoSizeChanged()


Sunday, May 26, 2013

Simple example to play stream video from internet using MediaPlayer

Please notice that it's a very simple and incompleted example to play stream video of 3gp format from internet using MediaPlayer.

MediaPlayer play stream video from internet


Modify layout to have a SurfaceView for the MediaPlayer.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <SurfaceView 
        android:id="@+id/playersurface"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>


MainActivity.java
package com.example.androidmediaplayer;

import java.io.IOException;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.app.Activity;

public class MainActivity extends Activity implements SurfaceHolder.Callback, OnPreparedListener{
 
 MediaPlayer mediaPlayer;
 SurfaceHolder surfaceHolder;
 SurfaceView playerSurfaceView;
 String videoSrc = "rtsp://v6.cache1.c.youtube.com/CjYLENy73wIaLQkDsLHya4-Z9hMYDSANFEIJbXYtZ29vZ2xlSARSBXdhdGNoYKX4k4uBjbOiUQw=/0/0/0/video.3gp";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  playerSurfaceView = (SurfaceView)findViewById(R.id.playersurface);

        surfaceHolder = playerSurfaceView.getHolder();
        surfaceHolder.addCallback(this);
 }

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0) {

        try {
         mediaPlayer = new MediaPlayer();
            mediaPlayer.setDisplay(surfaceHolder);
   mediaPlayer.setDataSource(videoSrc);
   mediaPlayer.prepare();
   mediaPlayer.setOnPreparedListener(this);
   mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  } catch (IllegalArgumentException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SecurityException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IllegalStateException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder arg0) {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void onPrepared(MediaPlayer mp) {
  mediaPlayer.start();
 }

}


Note: permission of "android.permission.INTERNET" is needed in AndroidManifest.xml.

download filesDownload the files.

Next:
- Release MediaPlayer in onDestroy() and onPause()


Saturday, May 25, 2013

Get the current frame in VideoView using MediaMetadataRetriever

Last exercise show a simple example to "Get image frame in video using MediaMetadataRetriever". In this exercise, we will play the video in a VideoView. Then capture the frame at the position when user click on the button.

Get the current frame in VideoView using MediaMetadataRetriever


layout file.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="capture video image" />
    <VideoView
        android:id="@+id/videoview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>


MainActivity.java
package com.example.androidvideoview;

import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.Toast;
import android.widget.VideoView;

public class MainActivity extends Activity {
 
 MediaMetadataRetriever mediaMetadataRetriever;
 MediaController myMediaController;
 VideoView myVideoView;
 String viewSource = "/storage/sdcard0/DCIM/100MEDIA/VIDEO0009.mp4";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  mediaMetadataRetriever = new MediaMetadataRetriever();
  mediaMetadataRetriever.setDataSource(viewSource);

  myVideoView = (VideoView) findViewById(R.id.videoview);
  myVideoView.setVideoURI(Uri.parse(viewSource));  
  myMediaController = new MediaController(this);
  myVideoView.setMediaController(myMediaController);

  myVideoView.setOnCompletionListener(myVideoViewCompletionListener);
  myVideoView.setOnPreparedListener(MyVideoViewPreparedListener);
  myVideoView.setOnErrorListener(myVideoViewErrorListener);

  myVideoView.requestFocus();
  myVideoView.start();
  
  Button buttonCapture = (Button)findViewById(R.id.capture);
  buttonCapture.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    int currentPosition = myVideoView.getCurrentPosition(); //in millisecond
    Toast.makeText(MainActivity.this,
      "Current Position: " + currentPosition + " (ms)", 
      Toast.LENGTH_LONG).show();
    
    Bitmap bmFrame = mediaMetadataRetriever
      .getFrameAtTime(currentPosition * 1000); //unit in microsecond
    
    if(bmFrame == null){
     Toast.makeText(MainActivity.this, 
       "bmFrame == null!", 
       Toast.LENGTH_LONG).show();
    }else{
     AlertDialog.Builder myCaptureDialog = 
       new AlertDialog.Builder(MainActivity.this);
     ImageView capturedImageView = new ImageView(MainActivity.this);
     capturedImageView.setImageBitmap(bmFrame);
     LayoutParams capturedImageViewLayoutParams = 
       new LayoutParams(LayoutParams.WRAP_CONTENT, 
         LayoutParams.WRAP_CONTENT);
     capturedImageView.setLayoutParams(capturedImageViewLayoutParams);
     
     myCaptureDialog.setView(capturedImageView);
     myCaptureDialog.show();
    }
    
   }});
 }

 MediaPlayer.OnCompletionListener myVideoViewCompletionListener = 
   new MediaPlayer.OnCompletionListener() {

  @Override
  public void onCompletion(MediaPlayer arg0) {
   Toast.makeText(MainActivity.this, "End of Video",
     Toast.LENGTH_LONG).show();
  }
 };

 MediaPlayer.OnPreparedListener MyVideoViewPreparedListener = 
   new MediaPlayer.OnPreparedListener() {

  @Override
  public void onPrepared(MediaPlayer mp) {
   
   long duration = myVideoView.getDuration(); //in millisecond
   Toast.makeText(MainActivity.this,
     "Duration: " + duration + " (ms)", 
     Toast.LENGTH_LONG).show();

  }
 };

 MediaPlayer.OnErrorListener myVideoViewErrorListener = 
   new MediaPlayer.OnErrorListener() {

  @Override
  public boolean onError(MediaPlayer mp, int what, int extra) {
   
   Toast.makeText(MainActivity.this, 
     "Error!!!",
     Toast.LENGTH_LONG).show();
   return true;
  }
 };

}


Note: To use MediaMetadataRetriever, minSdkVersion="10" have to be defined in AndroidManifest.xml.

Get image frame in video using MediaMetadataRetriever

MediaMetadataRetriever, for API level 10 or higher, provides a unified interface for retrieving frame and meta data from an input media file.

Example to retrieve frame in video:

retrieve frame in video


package com.example.androidmediametadataretriever;

import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.widget.ImageView;
import android.app.Activity;

public class MainActivity extends Activity {
 
 String uri = "/storage/sdcard0/DCIM/100MEDIA/VIDEO0007.mp4";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  ImageView capturedImageView = (ImageView)findViewById(R.id.capturedimage);
  
  MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
  
  mediaMetadataRetriever.setDataSource(uri);
  Bitmap bmFrame = mediaMetadataRetriever.getFrameAtTime(5000000); //unit in microsecond
  capturedImageView.setImageBitmap(bmFrame);
 }

}


Next:
- Get the current frame in VideoView using MediaMetadataRetriever


Updated@2014-12-29:
Permission of "android.permission.READ_EXTERNAL_STORAGE" is needed, otherwise MediaMetadataRetriever.setDataSource() will throw IllegalArgumentException.

Read: MediaMetadataRetriever: IllegalArgumentException thrown from setDataSource()


Wednesday, May 22, 2013

"Type R cannot be resolved to a variable" after Android SDK updated

If you get the error of "Type R cannot be resolved to a variable" after Android SDK and ADT updated. Try to find and install any available update in Android SDK.

In my case, after update Android SDK Tools rev 22, and also ADT. I get error of "Type R cannot be resolved to a variable" in my projects, and any new project!

- Start Android SDK Manager again. Two more update available, Android SDK Platform-tools and Android SDK Build-tools. It was not available in the previous update.


- Install the update packages.

- Close and restart Eclipse.

- Problem solved.


Tegra Android Development Pack

The Tegra Android Development Pack 2.0 installs all software tools required to develop for Android on NVIDIA’s Tegra platform. This suite of developer tools is targeted at Tegra devices, but will configure a development environment that will work with almost any Android device. This cure for the common cold is available on Windows, OSX, Ubuntu Linux 32-bit and Ubuntu Linux 64-bit.



To Download The Files Below, you must be a member of the Tegra Registered Developer Program. To join simply create a new account (it's free and easy).

Monday, May 20, 2013

Apply animations on layout changes using LayoutTransition

LayoutTransition (Added in API level 11) enables automatic animations on layout changes in ViewGroup objects.

example:

LayoutTransition transition = new LayoutTransition();
container.setLayoutTransition(transition);


It's the default animations effect apply on the dynamic view in last exercise "Add and Remove view dynamically".


Modify MainActivity.java from last exercise.
- Apply LayoutTransition on container of LinearLayout.
- To made the effect noticeable, insert dynamic view in front by calling container.addView(addView, 0) instead of container.addView(addView).
- Modify AndroidManifest.xml to have minSdkVersion="11".

package com.example.androiddynamicview;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.animation.LayoutTransition;
import android.app.Activity;
import android.content.Context;

public class MainActivity extends Activity {
 
 EditText textIn;
 Button buttonAdd;
 LinearLayout container;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textIn = (EditText)findViewById(R.id.textin);
  buttonAdd = (Button)findViewById(R.id.add);
  container = (LinearLayout)findViewById(R.id.container);
  
  buttonAdd.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    LayoutInflater layoutInflater = 
      (LayoutInflater) getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    final View addView = layoutInflater.inflate(R.layout.row, null);
    TextView textOut = (TextView)addView.findViewById(R.id.textout);
    textOut.setText(textIn.getText().toString());
    Button buttonRemove = (Button)addView.findViewById(R.id.remove);
    buttonRemove.setOnClickListener(new OnClickListener(){

     @Override
     public void onClick(View v) {
      ((LinearLayout)addView.getParent()).removeView(addView);
     }});
    
    container.addView(addView, 0);
   }});
  
  LayoutTransition transition = new LayoutTransition();
  container.setLayoutTransition(transition);
  
 }

}


download filesDownload the files.

Related:
To apply Android 3.0 animation API on old devices, NineOldAndroids is an alternative.

Add and Remove view dynamically

This exercise demonstrate how to add and remove view dynamically at run-time. When user click on the Add button, new view will be added. When user click on Remove button, the corresponding view will be removed.


MainActivity.java
package com.example.androiddynamicview;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.app.Activity;
import android.content.Context;

public class MainActivity extends Activity {
 
 EditText textIn;
 Button buttonAdd;
 LinearLayout container;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textIn = (EditText)findViewById(R.id.textin);
  buttonAdd = (Button)findViewById(R.id.add);
  container = (LinearLayout)findViewById(R.id.container);
  
  buttonAdd.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    LayoutInflater layoutInflater = 
      (LayoutInflater) getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    final View addView = layoutInflater.inflate(R.layout.row, null);
    TextView textOut = (TextView)addView.findViewById(R.id.textout);
    textOut.setText(textIn.getText().toString());
    Button buttonRemove = (Button)addView.findViewById(R.id.remove);
    buttonRemove.setOnClickListener(new OnClickListener(){

     @Override
     public void onClick(View v) {
      ((LinearLayout)addView.getParent()).removeView(addView);
     }});
    
    container.addView(addView);
   }});
  
 }

}


activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/add"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="Add"/>
        <EditText
            android:id="@+id/textin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_toLeftOf="@id/add"/>
    </RelativeLayout>
    <LinearLayout 
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    </LinearLayout>

</LinearLayout>


row.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <Button
        android:id="@+id/remove"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="Remove"/>
    <TextView
        android:id="@+id/textout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_toLeftOf="@id/remove"/>
</RelativeLayout>



download filesDownload the files.

Related:
- Apply animations on layout changes using LayoutTransition
Get text from dynamically added view
Add and Remove view dynamically, keep views after orientation changed

In more details to show how to keep track of the child views:
Add and Remove view dynamically, details of keeping track of child views
Another approach to share a common OnClickListener between all buttonRemove.

Sunday, May 19, 2013

Intel introduce Beacon Mountain, a Development Environment for Android Apps



Learn about Beacon Mountain - Intel's development environment for creating applications for Intel Atom and ARM*-based devices running Android* operating systems.

Install Android Studio on Ubuntu with error of "Plugin com.intellij failed to initialize and will be disabled"

Just try to install the Early Access Preview of Android Studio on Ubuntu 13.04. But error of "Plugin com.intellij failed to initialize and will be disabled:null" happen...!



It's due to IntelliJ IDEA need fixed installation of JDK, it have to be one of the following location:
  • /usr/java
  • /opt/java
  • /usr/lib/jvm
Android Studio installed successful


Remark: After you installed JDK in the location, you have to set environment parameter point to the installed location. In my practice, I use the following commands in Terminal to update alternatives:

$sudo update-alternatives --install /usr/bin/javac javac /opt/java/bin/javac 1
$sudo update-alternatives --install /usr/bin/java java /opt/java/bin/java 1

where /opt/java/ is the location of my installed JDK.

Saturday, May 18, 2013

New Features in Android Location

Google I/O 2013 - Beyond the Blue Dot: New Features in Android Location




What's New in Android Developer Tools - Google I/O 2013



An in-depth tour of the Android development tools, with a closer look at everything new - along with tips and tricks for getting the most out of them.

Thursday, May 16, 2013

Android SDK Tools update rev.22

Android SDK Tools rev.22 is available. To update your tools in Eclipse, select Window -> Android SDK Manager.

Android SDK Tools update rev.22


This Android SDK requires Android Developer Toolkit version 22.0.0 or above.


(To manually update Android Developer Toolkit, select Help -> check for updates.)


Save instance state for Menu Items in orientation changed

Last exercise demonstrate how to "Show and hide menu item programatically". But the setting will lose in orientation changed. This exercise show how to save the states in Bundle.

Save instance state for Menu Items in orientation changed


To save instance state, override onSaveInstanceState() method to put it in outState. Then retrieve it in onCreate(). And override onPrepareOptionsMenu() method to set Visible of menu items accordingly.

Modify MainActivity.java in last exercise.
package com.example.androidmenu;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
import android.widget.ToggleButton;

public class MainActivity extends Activity {
 
 Menu menu;
 ToggleButton toggleButtonUp, toggleButtonDown;
 
 Boolean savedMenuUpIsVisible, savedMenuDownIsVisible;
 final static String KEY_Menu_Up = "KEY_MENU_UP";
 final static String KEY_Menu_Down = "KEY_MENU_DOWN";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  toggleButtonUp = (ToggleButton)findViewById(R.id.buttonup);
  toggleButtonDown = (ToggleButton)findViewById(R.id.buttondown);
  
  toggleButtonUp.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
    if(menu != null){
     MenuItem item_up = menu.findItem(R.id.action_up);
     item_up.setVisible(isChecked);
    }
    
   }});
  
  toggleButtonDown.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
    if (menu != null){
     MenuItem item_down = menu.findItem(R.id.action_down);
     item_down.setVisible(isChecked);
    }
    
   }});
  
  if(savedInstanceState != null){
   savedMenuUpIsVisible = savedInstanceState.getBoolean(KEY_Menu_Up, true);
   savedMenuDownIsVisible = savedInstanceState.getBoolean(KEY_Menu_Down, true);
  }
  
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  this.menu = menu;
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  
  switch(item.getItemId()){
  case R.id.action_settings:
   Toast.makeText(getApplicationContext(), 
     "Setting...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_up:
   Toast.makeText(getApplicationContext(), 
     "Up...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_down:
   Toast.makeText(getApplicationContext(), 
     "Down...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_other:
   Toast.makeText(getApplicationContext(), 
     "Other...", 
     Toast.LENGTH_SHORT).show();
   break;
  default:
   Toast.makeText(getApplicationContext(), 
     "Unknown...", 
     Toast.LENGTH_SHORT).show();
   break;
  }
  
  //Return false to allow normal menu processing to proceed, 
  //true to consume it here.
  return false;
 }

 @Override
 public boolean onPrepareOptionsMenu(Menu menu) {
  
  if(savedMenuUpIsVisible != null){
   MenuItem item_up = menu.findItem(R.id.action_up);
   item_up.setVisible(savedMenuUpIsVisible);
  }
  
  if(savedMenuDownIsVisible != null){
   MenuItem item_down = menu.findItem(R.id.action_down);
   item_down.setVisible(savedMenuDownIsVisible);
  }

  return super.onPrepareOptionsMenu(menu);
 }

 @Override
 protected void onSaveInstanceState(Bundle outState) {
  super.onSaveInstanceState(outState);
  
  if (menu != null){
   MenuItem item_up = menu.findItem(R.id.action_up);
   outState.putBoolean(KEY_Menu_Up, item_up.isVisible());
   MenuItem item_down = menu.findItem(R.id.action_down);
   outState.putBoolean(KEY_Menu_Down, item_down.isVisible());
  }
  
 }

}


download filesDownload the files.

Wednesday, May 15, 2013

Early Access Preview of Android Studio is available now

Android Studio is a new Android development environment based on IntelliJ IDEA. Similar to Eclipse with the ADT Plugin, Android Studio provides integrated Android developer tools for development and debugging. On top of the capabilities you expect from IntelliJ, Android Studio offers:
  • Gradle-based build support.
  • Android-specific refactoring and quick fixes.
  • Lint tools to catch performance, usability, version compatibility and other problems.
  • ProGuard and app-signing capabilities.
  • Template-based wizards to create common Android designs and components.
  • A rich layout editor that allows you to drag-and-drop UI components, preview layouts on multiple screen configurations, and much more.
visit: http://developer.android.com/sdk/installing/studio.html



Tor Norbye gives a quick a preview of Android Studio. It's a brand new fully featured IDE based on the community edition of IntelliJ.

Show and hide menu item programatically

Last post show how to "create Menu". To show and hide individual menu item at run-time using Java code, you can call:

MenuItem item = menu.findItem(<id>);
item.setVisible(<true/false>);

where menu is the Menu parameter passed to onCreateOptionsMenu(Menu menu) callback method.

Show and hide menu item programatically


Modify from last post. Add two ToggleButtons in layout to toggle Menu Item Up and Down.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <ToggleButton 
        android:id="@+id/buttonup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:checked="true"
        android:textOn="Menu item Up - ON"
        android:textOff="Menu item Up - OFF"/>
    <ToggleButton 
        android:id="@+id/buttondown"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:checked="true"
        android:textOn="Menu item Down - ON"
        android:textOff="Menu item Down - OFF"/>

</LinearLayout>


MainActivity.java
package com.example.androidmenu;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
import android.widget.ToggleButton;

public class MainActivity extends Activity {
 
 Menu menu;
 ToggleButton toggleButtonUp, toggleButtonDown;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  toggleButtonUp = (ToggleButton)findViewById(R.id.buttonup);
  toggleButtonDown = (ToggleButton)findViewById(R.id.buttondown);
  
  toggleButtonUp.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
    if(menu != null){
     MenuItem item_up = menu.findItem(R.id.action_up);
     item_up.setVisible(isChecked);
    }
    
   }});
  
  toggleButtonDown.setOnCheckedChangeListener(new OnCheckedChangeListener(){

   @Override
   public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    
    if (menu != null){
     MenuItem item_down = menu.findItem(R.id.action_down);
     item_down.setVisible(isChecked);
    }
    
   }});
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  this.menu = menu;
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  
  switch(item.getItemId()){
  case R.id.action_settings:
   Toast.makeText(getApplicationContext(), 
     "Setting...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_up:
   Toast.makeText(getApplicationContext(), 
     "Up...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_down:
   Toast.makeText(getApplicationContext(), 
     "Down...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_other:
   Toast.makeText(getApplicationContext(), 
     "Other...", 
     Toast.LENGTH_SHORT).show();
   break;
  default:
   Toast.makeText(getApplicationContext(), 
     "Unknown...", 
     Toast.LENGTH_SHORT).show();
   break;
  }
  
  //Return false to allow normal menu processing to proceed, 
  //true to consume it here.
  return false;
 }
 
}


Please notice that the null checking on menu in onCheckedChanged() is prevent the application from crash dur to menu become null after orientation changed. Actually, in this simple example, the system will keep status of the ToggleButtons after orientation changed, but not the visibility of Menu items. Such that both the Menu item will return to ON state, but the ToggleButton may be OFF.


The menu items return to ON after orientation changed!


To save instance state for Menu Items in orientation changed, read the next article.

Tuesday, May 14, 2013

Example to create Menu

Create Menu


Modify /res/menu/main.xml to define menu items.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:showAsAction="never"
        android:title="@string/action_settings"/>
    
    <item
        android:id="@+id/action_up"
        android:orderInCategory="100"
        android:showAsAction="ifRoom|withText"
        android:icon="@android:drawable/arrow_up_float"
        android:title="Up"/>
    <item
        android:id="@+id/action_down"
        android:orderInCategory="100"
        android:showAsAction="ifRoom|withText"
        android:icon="@android:drawable/arrow_down_float"
        android:title="Down"/>
    <item
        android:id="@+id/action_other"
        android:orderInCategory="100"
        android:showAsAction="ifRoom"
        android:icon="@drawable/ic_launcher"
        android:title="Other"/>

</menu>


To load menu, override onCreateOptionsMenu() method to inflate R.menu.main. It's loaded in default generated code.

To handle menu item selected, override onOptionsItemSelected() method.

MainActivity.java
package com.example.androidmenu;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity {

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

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  
  switch(item.getItemId()){
  case R.id.action_settings:
   Toast.makeText(getApplicationContext(), 
     "Setting...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_up:
   Toast.makeText(getApplicationContext(), 
     "Up...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_down:
   Toast.makeText(getApplicationContext(), 
     "Down...", 
     Toast.LENGTH_SHORT).show();
   break;
  case R.id.action_other:
   Toast.makeText(getApplicationContext(), 
     "Other...", 
     Toast.LENGTH_SHORT).show();
   break;
  default:
   Toast.makeText(getApplicationContext(), 
     "Unknown...", 
     Toast.LENGTH_SHORT).show();
   break;
  }
  
  //Return false to allow normal menu processing to proceed, 
  //true to consume it here.
  return false;
 }
 
}


Next:
- Show and hide menu item programatically


Google Play Developers Can Now Reply to User Reviews

Google announced that all developers on Google Play can now reply to user reviews. You can reply to user reviews in the Google Play Developer Console, and your replies are shown publicly below the corresponding user review on Google Play. Users receive an email notification when you reply and can either reply to you directly by email, or update their review if they choose to do so.

source: Android Developers Blog

Monday, May 13, 2013

Save state of ActionBar visibility

Last exercise demonstrate how to "Show and hide ActionBar using Java code". But, once configuration changed (such as orientation change), ActionBar will return to setting in AndroidManifest.xml. To resume the user set visibility of ActionBar, we can save it in onSaveInstanceState() and restore it in onCreate().

Please notice that it can save state for configuration change only: if the app is killed or finished, then re-started, it will return to AndroidManifest.xml set visibility.

Save state of ActionBar visibility


package com.example.androidactionbar;

import android.os.Bundle;
import android.app.ActionBar;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Button buttonToggleActionBar = (Button)findViewById(R.id.toggleactionbar);
  buttonToggleActionBar.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    ActionBar myActionBar = getActionBar();
    boolean myActionBarisShowing = myActionBar.isShowing();
    if(myActionBarisShowing){
     myActionBar.hide();
     Toast.makeText(getApplicationContext(), 
       "Hide ActionBar", 
       Toast.LENGTH_LONG).show();
    }else{
     myActionBar.show();
     Toast.makeText(getApplicationContext(), 
       "Show ActionBar", 
       Toast.LENGTH_LONG).show();
    }
   }});
  
  Button buttonFinish = (Button)findViewById(R.id.finish);
  buttonFinish.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    finish();
   }});
  
  if (savedInstanceState != null) {
   Boolean savedActionBarisShowing = 
     savedInstanceState.getBoolean("KEY_ACTIONBAR_ISSHOWING", true);
   if(savedActionBarisShowing){
    getActionBar().show();
   }else{
    getActionBar().hide();
   }
        }
 }

 @Override
 protected void onSaveInstanceState(Bundle outState) {
  super.onSaveInstanceState(outState);
  outState.putBoolean("KEY_ACTIONBAR_ISSHOWING", getActionBar().isShowing());
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/toggleactionbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Toggle ActionBar" />
    <Button
        android:id="@+id/finish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="finish()" />

</LinearLayout>


Sunday, May 12, 2013

Show and hide ActionBar using Java code

With android:theme="@android:style/Theme.Holo" is using, Java code can detect if ActionBar is showing currently by calling ActionBar.isShowing() method, and show/hide it by calling ActionBar.show()/ActionBar.hide().

Show and hide ActionBar using Java code


package com.example.androidactionbar;

import android.os.Bundle;
import android.app.ActionBar;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Button buttonToggleActionBar = (Button)findViewById(R.id.toggleactionbar);
  buttonToggleActionBar.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    ActionBar myActionBar = getActionBar();
    boolean myActionBarisShowing = myActionBar.isShowing();
    if(myActionBarisShowing){
     myActionBar.hide();
     Toast.makeText(getApplicationContext(), 
       "Hide ActionBar", 
       Toast.LENGTH_LONG).show();
    }else{
     myActionBar.show();
     Toast.makeText(getApplicationContext(), 
       "Show ActionBar", 
       Toast.LENGTH_LONG).show();
    }
   }});
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/toggleactionbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Toggle ActionBar" />

</LinearLayout>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidactionbar"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Holo" >
        <activity
            android:name="com.example.androidactionbar.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


Related:
- Display and hide ActionBar in XML

Next:
- Save state of ActionBar visibility

Display and hide ActionBar in XML

To use Theme.Holo (or its descendants) for Android 3.0 or higher to show ActionBar.

android:theme="@android:style/Theme.Holo"

@android:style/Theme.Holo

To hide Action Bar use Theme.Holo.NoActionBar or Theme.Holo.NoActionBar.Fullscreen.

android:theme="@android:style/Theme.Holo.NoActionBar"


@android:style/Theme.Holo.NoActionBar

android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"

@android:style/Theme.Holo.NoActionBar.Fullscreen



Related:
- Show and hide ActionBar using Java code


Thursday, May 9, 2013

Lifecycle of dual fragment activity

It's part of the articles of lifecycle: start reading from Understand lifecycle of Activity and Fragment, Introduction.

In this exercise, we have two fragements; the first one (MyFragment0) have a button to switch to the second fragment (MyFragment1) using FragmentTransaction, the second fragment is same as the exercise of "UI elements lost after Activity/Fragment re-created" have images displayed. The lifecycle behavior is similar to "UI elements lost after Activity/Fragment re-created" or "How setRetainInstance(true) affect lifecycle of Fragment" depends on setRetainInstance(); except that if the activity's onCreate() method called with (savedInstanceState == null), it will reload the first fragment (MyFragment0), otherwise the second fragment (MyFragment1) will be kept displayed.

dual fragment activity


MainActivity.java
package com.example.androidfragmenttest;

import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends MyFragmentActivity {
 
 static public class MyFragment0 extends MyFragment {

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   super.onCreateView(inflater, container, savedInstanceState);
   View view = inflater.inflate(R.layout.fragment_layout0, null);
   
   Button buttonSwFragment1 = (Button)view.findViewById(R.id.swfragment);
   buttonSwFragment1.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View arg0) {
     MyFragment1 myFragment1 = new MyFragment1();
     FragmentManager supportFragmentManager = getActivity().getSupportFragmentManager();
     FragmentTransaction fragmentTransaction = supportFragmentManager
       .beginTransaction();
     fragmentTransaction.replace(R.id.container, myFragment1);
     fragmentTransaction.addToBackStack(null);
     fragmentTransaction.commit();
    }});
   
   return view;
  }
  
 }

 static public class MyFragment1 extends MyFragment {
  
  ImageView image_b, image_c, image_d;
  TextView textState;
  Button buttonLoadImage_d;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   super.onCreateView(inflater, container, savedInstanceState);
   View view = inflater.inflate(R.layout.fragment_layout1, null);
   
   image_b = (ImageView)view.findViewById(R.id.image_b);
   image_c = (ImageView)view.findViewById(R.id.image_c);
   image_d = (ImageView)view.findViewById(R.id.image_d);
   textState = (TextView)view.findViewById(R.id.textstate);
   buttonLoadImage_d = (Button)view.findViewById(R.id.loadimage_d);
   
   image_b.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
   
   if(savedInstanceState == null){
    image_c.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
    textState.setText("MyFragment1.savedInstanceState == null");
   }else{
    textState.setText("MyFragment1.savedInstanceState != null");
   }
   
   buttonLoadImage_d.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View arg0) {
     image_d.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
    }});
   
   return view;
  }

 }

 FrameLayout fragmentContainer;
 Button buttonFinish;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  buttonFinish = (Button)findViewById(R.id.finish);
  buttonFinish.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    finish();
   }});

  fragmentContainer = (FrameLayout) findViewById(R.id.container);
  if (savedInstanceState == null) {
   // if's the first time created
   
   MyFragment0 myFragment0 = new MyFragment0();
   FragmentManager supportFragmentManager = getSupportFragmentManager();
   FragmentTransaction fragmentTransaction = supportFragmentManager
     .beginTransaction();
   fragmentTransaction.add(R.id.container, myFragment0);
   fragmentTransaction.commit(); 
  }
 }

}


/res/layout/fragment_layout0.xml, layout of MyFragment0.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Fragment 0"/>
    
    <Button
        android:id="@+id/swfragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Switch to Fragment 1"/>
    
</LinearLayout>


For MyFragmentActivity.java, MyFragment.java, /res/layout/fragment_layout1.xml and /res/layout/activity_main.xml, refer to the exercise "UI elements lost after Activity/Fragment re-created".

download filesDownload the files.

Wednesday, May 8, 2013

The official Google I/O 2013 conference companion app


The official Google I/O 2013 conference companion app supports devices running Android 2.3+, and is optimized for phones and tablets of all shapes and sizes.
With the app you can:
- View the conference agenda and edit your personal schedule
- Sync your schedule between all of your devices and the I/O website
- View detailed session, code lab, officed hours, and speaker information, including speaker bios, photos, and Google+ profiles
- +1 sessions right from the app
- Participate in public #io13 conversations on Google+
- Guide yourself using the vector-based conference map
- Get a reminder a few minutes before sessions in your schedule are due to start
- View information about companies in the Developer Sandbox
- Keep up with announcements from the I/O team during the conference
- View your schedule on your Android 3.0+ device's home and lock screens using widget
- Play "I/O Live" session video streams on your Android 3.0+ devices
- Beam session details from your Android 4.0, NFC-enabled device to another using Android Beam
- Scan attendee badges on your NFC-enabled device
Source code for the app will be available soon at http://code.google.com/p/iosched


Watch Google I/O live

You can stream Google I/O May 15-16 live. Brought to you by Google Developers Live (GDL), the Google I/O homepage will become the GDL at I/O live streaming hub starting on May 15th at 9:00 AM PT (16:00 UTC). From this page, you can:
  • Stream 4 channels of technical content on your computer, tablet, or phone. You’ll feel like you’re right there in the keynote and session rooms, listening to product announcements straight from the source. Live streaming will run on developers.google.com/io from 9 AM PT (16:00 UTC) to 7 PM PT (2:00 UTC) on May 15 and 16.
  • Watch exclusive interviews with the Googlers behind the latest product announcements.This year, GDL will be on site, broadcasting one-on-one product deep dives, executive interviews, and Sandbox walkthroughs from the GDL stage.
  • Get the latest news in real-time. We’ll be posting official announcements during I/O. You’ll be able to see the feed on the Google I/O homepage, in the I/O mobile app (coming soon), and on +Google Developers.
  • Never miss a session. All Google I/O technical sessions will be recorded and posted toGDL and the Google Developers YouTube channel. Subscribe to our YouTube channel for archived session updates.


Source: Google Developers Blog - We’re going live from Google I/O


Tuesday, May 7, 2013

Set user-agent string of WebView

To set user-agent string of WebView, call its getSettings().setUserAgentString(ua); where ua is is your user-agent string.

package com.example.androidwebview;

import android.os.Bundle;
import android.app.Activity;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends Activity {
 
 WebView myWebView;
 //final static String myAddr = "http://android-er.blogspot.com";
 //final static String myAddr = "http://developer.android.com/";
 final static String myAddr = "http://whatsmyuseragent.com/";
 
 //Chrome run on Linux Desktop
 //String ua = "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.63 Safari/537.31";
 //Chrome run on Android 4.1.1 HTC One X
 //String ua = "Mozilla/5.0 (Linux; Android 4.1.1; HTC One X Build/JRO03C) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.58 Mobile Safari/537.31";
 //Firefox run on Android 3.2.1 HTC Flyer 7" Tablet
 String ua = "Mozilla/5.0 (Android; Tablet; rv:20.0) Gecko/20.0 Firefox/20.0";
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  //setContentView(R.layout.activity_main);
  myWebView = new WebView(this);
  
  myWebView.getSettings().setUserAgentString(ua);
  myWebView.getSettings().setUseWideViewPort(true);
  myWebView.getSettings().setLoadWithOverviewMode(true);
  
  myWebView.getSettings().setJavaScriptEnabled(true);                
  myWebView.setWebViewClient(new MyWebViewClient());
  myWebView.loadUrl(myAddr);
  setContentView(myWebView);
  
 }
 
 private class MyWebViewClient extends WebViewClient {

  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
   view.loadUrl(url);
   return true;
  }
  
 }

}

Set user-agent string of WebView


remark: to check what user-agent your browser set, browse to http://whatsmyuseragent.com/, allows you to view details about your user agent.

download filesDownload the files.

Set ViewPort of WebView

According to Android Developer document : The method WebSettings.setUseWideViewPort() tells the WebView to use a wide viewport. The default is false. And the method WebSettings.setLoadWithOverviewMode() sets whether the WebView loads pages in overview mode. The default is false.

It's not too clear for me. As my understanding, setUseWideViewPort(true) will try to make the WebView have a normal viewport as desktop browser, setUseWideViewPort(false) make the WebView have a mobile viewport of mobile for phone. setLoadWithOverviewMode(true) will loads the WebView in full zoomed out. In my testing, it's more or less depends on the target html.


WebView with WebSetting.setUseWideViewPort(true)

WebView with WebSetting.setUseWideViewPort(false)


package com.example.androidwebview;

import android.os.Bundle;
import android.app.Activity;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends Activity {
 
 WebView myWebView;
 //final static String myAddr = "http://android-er.blogspot.com";
 final static String myAddr = "http://developer.android.com/";
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  //setContentView(R.layout.activity_main);
  myWebView = new WebView(this);
  
  myWebView.getSettings().setUseWideViewPort(true);
  myWebView.getSettings().setLoadWithOverviewMode(true);
  
  myWebView.getSettings().setJavaScriptEnabled(true);                
  myWebView.setWebViewClient(new MyWebViewClient());
  myWebView.loadUrl(myAddr);
  setContentView(myWebView);
  
 }
 
 private class MyWebViewClient extends WebViewClient {

  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
   view.loadUrl(url);
   return true;
  }
  
 }

}


remark: uses-permission of "android.permission.INTERNET" is needed.

download filesDownload the files.

Next:
- Set user-agent string of WebView

Sunday, May 5, 2013

How setRetainInstance(true) affect lifecycle of Fragment

It's part of the articles of lifecycle: start reading from Understand lifecycle of Activity and Fragment, Introduction.

The Fragment.setRetainInstance() method control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
  • onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
  • onCreate(Bundle) will not be called since the fragment is not being re-created.
  • onAttach(Activity) and onActivityCreated(Bundle) will still be called.
~ reference: http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

Refer to the last exercise, "UI elements lost after Activity/Fragment re-created". Consider the image_c, it is loaded in onCreateView(), only if savedInstanceState == null. Without setRetainInstance(true), it will not be loaded after orientation changed, because savedInstanceState != null.

image_c lost after orientation changed, without setRetainInstance(true)


MYTAG LogCat listed here:

Orientation changed:
  • MainActivity.onPause
  • MyFragment1.onPause
  • MainActivity.onStop
  • MyFragment1.onStop
  • MainActivity.onDestroy
  • MyFragment1.onDestroyView
  • MyFragment1.onDestroy
  • MyFragment1.onDetach
  • MainActivity.onCreate / savedInstanceState != null
  • MyFragment1.onAttach
  • MyFragment1.onCreate / savedInstanceState != null
  • MainActivity.onStart
  • MyFragment1.onCreateView / savedInstanceState != null
  • MyFragment1.onActivityCreated / savedInstanceState != null
  • MyFragment1.onStart
  • MainActivity.onResume
  • MyFragment1.onResume


To know how setRetainInstance(true) affect lifecycle of Fragment, modify MyFragment.java from last exercise "UI elements lost after Activity/Fragment re-created", override onActivityCreated() method to call setRetainInstance(true).

 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
  recLifeCycle_with_savedInstanceState(savedInstanceState);
  super.onActivityCreated(savedInstanceState);
  
  setRetainInstance(true);
 }

image_c reloaded after orientation changed, with setRetainInstance(true).

LogCat in Orientation Changed:
  • MainActivity.onPause
  • MyFragment1.onPause
  • MainActivity.onStop
  • MyFragment1.onStop
  • MainActivity.onDestroy
  • MyFragment1.onDestroyView
  • MyFragment1.onDetach
  • MainActivity.onCreate / savedInstanceState != null
  • MyFragment1.onAttach
  • MainActivity.onStart
  • MyFragment1.onCreateView / savedInstanceState == null
  • MyFragment1.onActivityCreated / savedInstanceState == null
  • MyFragment1.onStart
  • MainActivity.onResume
  • MyFragment1.onResume



download filesDownload the files.

* the original chart of fragment lifecycle is taken here.

Friday, May 3, 2013

UI elements lost after Activity/Fragment re-created

Last two post "Understand lifecycle of Activity and Fragment" and "Different case in lifecycle of Activity and Fragment", we have introduction about lifecycle. Now we check how it affect on real case.

In the layout of our fragment, we have 4 ImageView.
  • image_a: With android:src defined in XML.
  • image_b: Load image in onCreateView().
  • image_c: Load image in onCreateView(), only if savedInstanceState == null.
  • image_d: Load image in runtime, triggered by button clicked
In case image_c and image_d, the image will lost when orientation changed, or "Don't keep activities".




Code List -

Modify our custom FragmentActivity (MyFragmentActivity.java) and Fragment (MyFragment.java.java) to display savedInstanceState also.

MyFragmentActivity.java
package com.example.androidfragmenttest;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;

public class MyFragmentActivity extends FragmentActivity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  recLifeCycle_with_savedInstanceState(savedInstanceState);
  super.onCreate(savedInstanceState);
 }
 
 @Override
 protected void onStart() {
  recLifeCycle();
  super.onStart();
 }

 @Override
 protected void onRestart() {
  recLifeCycle();
  super.onRestart();
 }

 @Override
 protected void onResume() {
  recLifeCycle();
  super.onResume();
 }

 @Override
 protected void onPause() {
  recLifeCycle();
  super.onPause();
 }
 
 @Override
 protected void onStop() {
  recLifeCycle();
  super.onStop();
 }

 @Override
 protected void onDestroy() {
  recLifeCycle();
  super.onDestroy();
 }

 public void recLifeCycle(){
  
  String className = getClass().getSimpleName();
  StackTraceElement[] s = Thread.currentThread().getStackTrace();
  String methodName = s[3].getMethodName();
  
  Toast.makeText(getApplicationContext(), 
    className + "." + methodName, Toast.LENGTH_SHORT).show();
  Log.i("MYTAG", className + "." + methodName);
  
 }
 
 public void recLifeCycle(String note){
  String className = getClass().getSimpleName();
  StackTraceElement[] s = Thread.currentThread().getStackTrace();
  String methodName = s[3].getMethodName();

  Toast.makeText(getApplicationContext(), 
    className + "." + methodName, Toast.LENGTH_SHORT).show();
  Log.i("MYTAG", className + "." + methodName + " / " + note);
 }
 
 public void recLifeCycle_with_savedInstanceState(Bundle state){
  
  String stateMsg;
  if(state == null){
   stateMsg = "savedInstanceState == null";
  }else{
   stateMsg = "savedInstanceState != null";
  }
  
  String className = getClass().getSimpleName();
  StackTraceElement[] s = Thread.currentThread().getStackTrace();
  String methodName = s[3].getMethodName();
  
  Toast.makeText(getApplicationContext(), 
    className + "." + methodName, Toast.LENGTH_SHORT).show();
  Log.i("MYTAG", className + "." + methodName + " / " + stateMsg);
 }

}


MyFragment.java
package com.example.androidfragmenttest;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

public class MyFragment extends Fragment {

 @Override
 public void onAttach(Activity activity) {
  recLifeCycle();
  super.onAttach(activity);
 }

 @Override
 public void onCreate(Bundle savedInstanceState) {
  recLifeCycle_with_savedInstanceState(savedInstanceState);
  super.onCreate(savedInstanceState);
 }

 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
  
  recLifeCycle_with_savedInstanceState(savedInstanceState);
  return super.onCreateView(inflater, container, savedInstanceState);
 }
 
 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
  recLifeCycle_with_savedInstanceState(savedInstanceState);
  super.onActivityCreated(savedInstanceState);
 }

 @Override
 public void onStart() {
  recLifeCycle();
  super.onStart();
 }

 @Override
 public void onResume() {
  recLifeCycle();
  super.onResume();
 }

 @Override
 public void onPause() {
  recLifeCycle();
  super.onPause();
 }

 @Override
 public void onStop() {
  recLifeCycle();
  super.onStop();
 }

 @Override
 public void onDestroyView() {
  recLifeCycle();
  super.onDestroyView();
 }

 @Override
 public void onDestroy() {
  recLifeCycle();
  super.onDestroy();
 }

 @Override
 public void onDetach() {
  recLifeCycle();
  super.onDetach();
 }
 
 public void recLifeCycle(){
  
  String className = getClass().getSimpleName();
  StackTraceElement[] s = Thread.currentThread().getStackTrace();
  String methodName = s[3].getMethodName();
  
  String msg = className + "." + methodName;
  
  Toast.makeText(getActivity(), 
    msg, Toast.LENGTH_SHORT).show();
  Log.i("MYTAG", msg);
 }
 
 public void recLifeCycle(String note){
  
  String className = getClass().getSimpleName();
  StackTraceElement[] s = Thread.currentThread().getStackTrace();
  String methodName = s[3].getMethodName();
  
  String msg = className + "." + methodName;

  Toast.makeText(getActivity(), 
    msg, Toast.LENGTH_SHORT).show();
  Log.i("MYTAG", msg + " / " + note);
 }
 
 public void recLifeCycle_with_savedInstanceState(Bundle state){
  
  String stateMsg;
  if(state == null){
   stateMsg = "savedInstanceState == null";
  }else{
   stateMsg = "savedInstanceState != null";
  }
  
  String className = getClass().getSimpleName();
  StackTraceElement[] s = Thread.currentThread().getStackTrace();
  String methodName = s[3].getMethodName();
  
  String msg = className + "." + methodName + " / " + stateMsg;
  
  Toast.makeText(getActivity(), 
    msg, Toast.LENGTH_SHORT).show();
  Log.i("MYTAG", msg);
 }
 
}


The fragment layout, /res/layout/fragment_layout1.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Fragment 1"/>
    
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <LinearLayout 
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            
            <LinearLayout 
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="image_a"/>
                <ImageView
                    android:id="@+id/image_a"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/ic_launcher"/>
            </LinearLayout>
            <LinearLayout 
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="image_b"/>
                <ImageView
                    android:id="@+id/image_b"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
            </LinearLayout>
            <LinearLayout 
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="image_c"/>
                <ImageView
                    android:id="@+id/image_c"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
            </LinearLayout>
            <TextView 
                android:id="@+id/textstate"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <LinearLayout 
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <TextView 
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="image_d"/>
                <ImageView
                    android:id="@+id/image_d"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                <Button
                    android:id="@+id/loadimage_d"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Load image_d"/>
            </LinearLayout>

        </LinearLayout>
    </ScrollView>
    
</LinearLayout>


MainActivity.java
package com.example.androidfragmenttest;

import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends MyFragmentActivity {

 static public class MyFragment1 extends MyFragment {
  
  ImageView image_b, image_c, image_d;
  TextView textState;
  Button buttonLoadImage_d;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
   super.onCreateView(inflater, container, savedInstanceState);
   View view = inflater.inflate(R.layout.fragment_layout1, null);
   
   image_b = (ImageView)view.findViewById(R.id.image_b);
   image_c = (ImageView)view.findViewById(R.id.image_c);
   image_d = (ImageView)view.findViewById(R.id.image_d);
   textState = (TextView)view.findViewById(R.id.textstate);
   buttonLoadImage_d = (Button)view.findViewById(R.id.loadimage_d);
   
   image_b.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
   
   if(savedInstanceState == null){
    image_c.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
    textState.setText("MyFragment1.savedInstanceState == null");
   }else{
    textState.setText("MyFragment1.savedInstanceState != null");
   }
   
   buttonLoadImage_d.setOnClickListener(new OnClickListener(){

    @Override
    public void onClick(View arg0) {
     image_d.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
    }});
   
   return view;
  }

 }

 FrameLayout fragmentContainer;
 Button buttonFinish;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  buttonFinish = (Button)findViewById(R.id.finish);
  buttonFinish.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    finish();
   }});

  fragmentContainer = (FrameLayout) findViewById(R.id.container);
  if (savedInstanceState == null) {
   // if's the first time created
   
   MyFragment1 myListFragment1 = new MyFragment1();
   FragmentManager supportFragmentManager = getSupportFragmentManager();
   FragmentTransaction fragmentTransaction = supportFragmentManager
     .beginTransaction();
   fragmentTransaction.add(R.id.container, myListFragment1);
   fragmentTransaction.commit(); 
  }
 }

}


Main layout, /res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/finish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="finish()"/>
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>

</LinearLayout>




download filesDownload the files.