Android send command to Arduino in USB Host mode

This example show how to send command from Android to Arduino Esplora board, in USB Host Mode, to control the LED and Screen of Arduino Esplora. When user toggle the LED by clicking on the button, or change the screen color by slideing the bars, the commands will be add in a command queue, and then send to Arduino in background.


In Android Side:

MainActivity.java.
package com.example.androidusbhost;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

public class MainActivity extends Activity {

ToggleButton btnLED;
SeekBar barR, barG, barB;
TextView textRx;

TextView textInfo;
TextView textSearchedEndpoint;

TextView textDeviceName;
TextView textStatus;

private static final int targetVendorID= 9025;
private static final int targetProductID = 32828;
UsbDevice deviceFound = null;
UsbInterface usbInterfaceFound = null;
UsbEndpoint endpointIn = null;
UsbEndpoint endpointOut = null;

private static final String ACTION_USB_PERMISSION =
"com.android.example.USB_PERMISSION";
PendingIntent mPermissionIntent;

UsbInterface usbInterface;
UsbDeviceConnection usbDeviceConnection;

ThreadUsbTx threadUsbTx;

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

btnLED = (ToggleButton)findViewById(R.id.ledbutton);
btnLED.setOnClickListener(btnLEDOnClickListener);

barR = (SeekBar)findViewById(R.id.rbar);
barG = (SeekBar)findViewById(R.id.gbar);
barB = (SeekBar)findViewById(R.id.bbar);
barR.setOnSeekBarChangeListener(colorOnSeekBarChangeListener);
barG.setOnSeekBarChangeListener(colorOnSeekBarChangeListener);
barB.setOnSeekBarChangeListener(colorOnSeekBarChangeListener);

textRx = (TextView)findViewById(R.id.textrx);

textStatus = (TextView)findViewById(R.id.textstatus);

textDeviceName = (TextView)findViewById(R.id.textdevicename);
textInfo = (TextView) findViewById(R.id.info);
textSearchedEndpoint = (TextView)findViewById(R.id.searchedendpoint);

//register the broadcast receiver
mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);

registerReceiver(mUsbDeviceReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED));
registerReceiver(mUsbDeviceReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));

connectUsb();
}

@Override
protected void onDestroy() {
releaseUsb();
unregisterReceiver(mUsbReceiver);
unregisterReceiver(mUsbDeviceReceiver);
super.onDestroy();
}

OnClickListener btnLEDOnClickListener =
new OnClickListener(){

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

int usbResult;
byte[] cmdLED;
if(((ToggleButton)v).isChecked()){
cmdLED = new byte[] {(byte)'L', 'E', 'D', 'O', 'N', '\n'};
}else{
cmdLED = new byte[] {(byte)'L', 'E', 'D', 'O', 'F', 'F', '\n'};
}
threadUsbTx.insertCmd(cmdLED);
}

};

OnSeekBarChangeListener colorOnSeekBarChangeListener =
new OnSeekBarChangeListener(){

byte[] toAscii = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {

byte[] cmdCol = new byte[] {(byte)'C', 'O', 'L', '#',
'0', '0', '0', '0', '0', '0', '\n'};

cmdCol[4] = toAscii[(barR.getProgress()>>4) & 0x0f];
cmdCol[5] = toAscii[barR.getProgress() & 0x0f];
cmdCol[6] = toAscii[(barG.getProgress()>>4) & 0x0f];
cmdCol[7] = toAscii[barG.getProgress() & 0x0f];
cmdCol[8] = toAscii[(barB.getProgress()>>4) & 0x0f];
cmdCol[9] = toAscii[barB.getProgress() & 0x0f];
threadUsbTx.insertCmd(cmdCol.clone());
}

};

private void connectUsb(){

btnLED.setEnabled(false);
barR.setEnabled(false);
barG.setEnabled(false);
barB.setEnabled(false);

Toast.makeText(MainActivity.this,
"connectUsb()",
Toast.LENGTH_LONG).show();
textStatus.setText("connectUsb()");

searchEndPoint();

if(usbInterfaceFound != null){
setupUsbComm();

threadUsbTx = new ThreadUsbTx(usbDeviceConnection, endpointOut);
threadUsbTx.start();

btnLED.setEnabled(true);
barR.setEnabled(true);
barG.setEnabled(true);
barB.setEnabled(true);
//Turn On LED once connected
btnLED.setChecked(true);
barR.setProgress(0x80);
barG.setProgress(0x80);
barB.setProgress(0x80);

threadUsbTx.insertCmd(
new byte[] {(byte)'L', 'E', 'D', 'O', 'N', '\n'});
threadUsbTx.insertCmd(
new byte[] {(byte)'C', 'O', 'L', '#',
'8', '0', '8', '0', '8', '0', '\n'});

}

}

private void releaseUsb(){

Toast.makeText(MainActivity.this,
"releaseUsb()",
Toast.LENGTH_LONG).show();
textStatus.setText("releaseUsb()");

if(usbDeviceConnection != null){
if(usbInterface != null){
usbDeviceConnection.releaseInterface(usbInterface);
usbInterface = null;
}
usbDeviceConnection.close();
usbDeviceConnection = null;
}

deviceFound = null;
usbInterfaceFound = null;
endpointIn = null;
endpointOut = null;

btnLED.setEnabled(false);
barR.setEnabled(false);
barG.setEnabled(false);
barB.setEnabled(false);

if(threadUsbTx!=null){
threadUsbTx.setRunning(false);
}
}

private void searchEndPoint(){

textInfo.setText("");
textSearchedEndpoint.setText("");

usbInterfaceFound = null;
endpointOut = null;
endpointIn = null;

//Search device for targetVendorID and targetProductID
if(deviceFound == null){
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();

while (deviceIterator.hasNext()) {
UsbDevice device = deviceIterator.next();

if(device.getVendorId()==targetVendorID){
if(device.getProductId()==targetProductID){
deviceFound = device;
}
}
}
}

if(deviceFound==null){
textStatus.setText("device not found");
}else{
String s = deviceFound.toString() + "\n" +
"DeviceID: " + deviceFound.getDeviceId() + "\n" +
"DeviceName: " + deviceFound.getDeviceName() + "\n" +
"DeviceClass: " + deviceFound.getDeviceClass() + "\n" +
"DeviceSubClass: " + deviceFound.getDeviceSubclass() + "\n" +
"VendorID: " + deviceFound.getVendorId() + "\n" +
"ProductID: " + deviceFound.getProductId() + "\n" +
"InterfaceCount: " + deviceFound.getInterfaceCount();
textInfo.setText(s);

//Search for UsbInterface with Endpoint of USB_ENDPOINT_XFER_BULK,
//and direction USB_DIR_OUT and USB_DIR_IN

for(int i=0; i<deviceFound.getInterfaceCount(); i++){
UsbInterface usbif = deviceFound.getInterface(i);

UsbEndpoint tOut = null;
UsbEndpoint tIn = null;

int tEndpointCnt = usbif.getEndpointCount();
if(tEndpointCnt>=2){
for(int j=0; j<tEndpointCnt; j++){
if(usbif.getEndpoint(j).getType() ==
UsbConstants.USB_ENDPOINT_XFER_BULK){
if(usbif.getEndpoint(j).getDirection() ==
UsbConstants.USB_DIR_OUT){
tOut = usbif.getEndpoint(j);
}else if(usbif.getEndpoint(j).getDirection() ==
UsbConstants.USB_DIR_IN){
tIn = usbif.getEndpoint(j);
}
}
}

if(tOut!=null && tIn!=null){
//This interface have both USB_DIR_OUT
//and USB_DIR_IN of USB_ENDPOINT_XFER_BULK
usbInterfaceFound = usbif;
endpointOut = tOut;
endpointIn = tIn;
}
}

}

if(usbInterfaceFound==null){
textSearchedEndpoint.setText("No suitable interface found!");
}else{
textSearchedEndpoint.setText(
"UsbInterface found: " + usbInterfaceFound.toString() + "\n\n" +
"Endpoint OUT: " + endpointOut.toString() + "\n\n" +
"Endpoint IN: " + endpointIn.toString());
}
}
}

private boolean setupUsbComm(){

//for more info, search SET_LINE_CODING and
//SET_CONTROL_LINE_STATE in the document:
//"Universal Serial Bus Class Definitions for Communication Devices"
//at http://adf.ly/dppFt
final int RQSID_SET_LINE_CODING = 0x20;
final int RQSID_SET_CONTROL_LINE_STATE = 0x22;

boolean success = false;

UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
Boolean permitToRead = manager.hasPermission(deviceFound);

if(permitToRead){
usbDeviceConnection = manager.openDevice(deviceFound);
if(usbDeviceConnection != null){
usbDeviceConnection.claimInterface(usbInterfaceFound, true);

showRawDescriptors(); //skip it if you no need show RawDescriptors

int usbResult;

/*
* D7: 0 - Host to Device
* 1 = Device to Host
* D6..5 Type
* 0 = Standard
* 1 = Class
* 2 = Vendor
* 3 = Reserved
* D4..0 Recipient
* 0 = Device
* 1 = Interface
* 2 = Endpoint
* 3 = Other
*/

//int requestType = 0x21;
int requestType = 0x42;

usbResult = usbDeviceConnection.controlTransfer(
requestType,
RQSID_SET_CONTROL_LINE_STATE, //SET_CONTROL_LINE_STATE
0, //value
0, //index
null, //buffer
0, //length
0); //timeout

//baud rate = 9600
//8 data bit
//1 stop bit
byte[] encodingSetting =
new byte[] {(byte)0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
usbResult = usbDeviceConnection.controlTransfer(
requestType,
RQSID_SET_LINE_CODING, //SET_LINE_CODING
0, //value
0, //index
encodingSetting, //buffer
7, //length
0); //timeout

}

}else{
manager.requestPermission(deviceFound, mPermissionIntent);
Toast.makeText(MainActivity.this,
"Permission: " + permitToRead,
Toast.LENGTH_LONG).show();
textStatus.setText("Permission: " + permitToRead);
}

return success;
}

private void showRawDescriptors(){
final int STD_USB_REQUEST_GET_DESCRIPTOR = 0x06;
final int LIBUSB_DT_STRING = 0x03;

byte[] buffer = new byte[255];
int indexManufacturer = 14;
int indexProduct = 15;
String stringManufacturer = "";
String stringProduct = "";

byte[] rawDescriptors = usbDeviceConnection.getRawDescriptors();

int lengthManufacturer = usbDeviceConnection.controlTransfer(
UsbConstants.USB_DIR_IN|UsbConstants.USB_TYPE_STANDARD, //requestType
STD_USB_REQUEST_GET_DESCRIPTOR, //request ID for this transaction
(LIBUSB_DT_STRING << 8) | rawDescriptors[indexManufacturer], //value
0, //index
buffer, //buffer
0xFF, //length
0); //timeout
try {
stringManufacturer = new String(buffer, 2, lengthManufacturer-2, "UTF-16LE");
} catch (UnsupportedEncodingException e) {
textStatus.setText(e.toString());
}

int lengthProduct = usbDeviceConnection.controlTransfer(
UsbConstants.USB_DIR_IN|UsbConstants.USB_TYPE_STANDARD,
STD_USB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8) | rawDescriptors[indexProduct],
0,
buffer,
0xFF,
0);
try {
stringProduct = new String(buffer, 2, lengthProduct-2, "UTF-16LE");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

textStatus.setText("Manufacturer: " + stringManufacturer + "\n" +
"Product: " + stringProduct);
}

private final BroadcastReceiver mUsbReceiver =
new BroadcastReceiver() {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {

textStatus.setText("ACTION_USB_PERMISSION");

synchronized (this) {
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if(device != null){
connectUsb();
}
}
else {
Toast.makeText(MainActivity.this,
"permission denied for device " + device,
Toast.LENGTH_LONG).show();
textStatus.setText("permission denied for device " + device);
}
}
}
}
};

private final BroadcastReceiver mUsbDeviceReceiver =
new BroadcastReceiver() {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {

deviceFound = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Toast.makeText(MainActivity.this,
"ACTION_USB_DEVICE_ATTACHED: \n" +
deviceFound.toString(),
Toast.LENGTH_LONG).show();
textStatus.setText("ACTION_USB_DEVICE_ATTACHED: \n" +
deviceFound.toString());

connectUsb();
}else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {

UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);

Toast.makeText(MainActivity.this,
"ACTION_USB_DEVICE_DETACHED: \n" +
device.toString(),
Toast.LENGTH_LONG).show();
textStatus.setText("ACTION_USB_DEVICE_DETACHED: \n" +
device.toString());

if(device!=null){
if(device == deviceFound){
releaseUsb();
}
}

textInfo.setText("");
}
}

};

class ThreadUsbTx extends Thread{
boolean running;

UsbDeviceConnection txConnection;
UsbEndpoint txEndpoint;
Queue<byte[]> cmdQueue;
byte[] cmdToSent;

ThreadUsbTx(UsbDeviceConnection conn, UsbEndpoint endpoint){
txConnection = conn;
txEndpoint = endpoint;
cmdQueue = new LinkedList<byte[]>();
cmdToSent = null;
running = true;
}

public void setRunning(boolean r){
running = r;
}

public void insertCmd(byte[] cmd){
synchronized(cmdQueue){
cmdQueue.add(cmd);
}
}

@Override
public void run() {

while(running){

synchronized(cmdQueue){
if(cmdQueue.size()>0){
cmdToSent = cmdQueue.remove();
}
}

if(cmdToSent!=null){
int usbResult = usbDeviceConnection.bulkTransfer(
endpointOut,
cmdToSent,
cmdToSent.length,
0);

final String s = new String(cmdToSent);

runOnUiThread(new Runnable(){

@Override
public void run() {
Toast.makeText(MainActivity.this,
s,
Toast.LENGTH_SHORT).show();
}});

cmdToSent = null;
}
}
}
}
}

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:orientation="vertical"
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" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:autoLink="web"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold" />

<ToggleButton
android:id="@+id/ledbutton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textOn="LED ON"
android:textOff="LED OFF" />
<SeekBar
android:id="@+id/rbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:progress="0"/>
<SeekBar
android:id="@+id/gbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:progress="0"/>
<SeekBar
android:id="@+id/bbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="255"
android:progress="0"/>
<TextView
android:id="@+id/textrx"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#ff0000" />

<TextView
android:id="@+id/textstatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold" />

<TextView
android:id="@+id/textdevicename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold|italic" />

<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" >

<TextView
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/searchedendpoint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold" />

</LinearLayout>
</ScrollView>

</LinearLayout>

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

<uses-feature android:name="android.hardware.usb.host" />
<uses-sdk
android:minSdkVersion="13"
android:targetSdkVersion="18" />

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.androidusbhost.MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|orientation" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />

<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
</intent-filter>

</activity>
</application>

</manifest>

download filesDownload the files.

In Arduino Side: re-use the code in my another blog Node.js + Arduino, running on PC.
#include <Esplora.h>
#include <TFT.h>
#include <SPI.h>

int MAX_CMD_LENGTH = 10;
char cmd[10];
int cmdIndex;
char incomingByte;

int prevSlider = 0;

void setup() {

EsploraTFT.begin();
EsploraTFT.background(0,0,0);
EsploraTFT.stroke(255,255,255); //preset stroke color

//Setup Serial Port with baud rate of 9600
Serial.begin(9600);

//indicate start
Esplora.writeRGB(255, 255, 255);
delay(250);
Esplora.writeRGB(0, 0, 0);

cmdIndex = 0;

}

void loop() {

if (incomingByte=Serial.available()>0) {

char byteIn = Serial.read();
cmd[cmdIndex] = byteIn;

if(byteIn=='\n'){
//command finished
cmd[cmdIndex] = '\0';
//Serial.println(cmd);
cmdIndex = 0;

String stringCmd = String(cmd);

if(strcmp(cmd, "LEDON") == 0){
//Serial.println("Command received: LEDON");
Esplora.writeRGB(255, 255, 255);
}else if (strcmp(cmd, "LEDOFF") == 0) {
//Serial.println("Command received: LEDOFF");
Esplora.writeRGB(0, 0, 0);
}else if(stringCmd.substring(0,4)="COL#"){
//Serial.println("Command received: COL#");
if(stringCmd.length()==10){
char * pEnd;
long int rgb = strtol(&cmd[4], &pEnd, 16);
int r = (rgb & 0xff0000) >> 16;
int g = (rgb & 0xff00) >> 8;
int b = rgb & 0xff;
//Serial.println(r);
//Serial.println(g);
//Serial.println(b);
EsploraTFT.background(b,g,r);
}
}else{
//Serial.println("Command received: unknown!");
}

}else{
if(cmdIndex++ >= MAX_CMD_LENGTH){
cmdIndex = 0;
}
}
}

//Read Slider
int slider = Esplora.readSlider();
//convert slider value from [0-1023] to [0x00-0xFF]
slider = slider>>2 & 0xff;

if(slider!=prevSlider){
prevSlider = slider;

String stringSlider = String(slider, HEX);
Serial.println("SLD#" + stringSlider +"\n");
}

}

Remark:
The device-to-host (Arduino-to-Android) communication have NOT been implemented on Android side in this example.


Step-by-step: Android USB Host Mode programming

Watch the Java 8 Launch videos

Java 8 Launch videos are online now.  Include great tutorials in sessions of
- Java SE 8
- Java SE Embedded 8
- Java ME 8
- Internet of Things and The Enterprise

Watch it at http://www.oracle.com/events/us/en/java8/index.html.

PopupWindow with AnalogClock and DigitalClock

Example to display AnalogClock and DigitalClock on PopupWindow.

PopupWindow with AnalogClock and DigitalClock
PopupWindow with AnalogClock and DigitalClock

/res/layout/popup.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/customborder"
android:orientation="vertical" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:orientation="vertical" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical" >

<TextView
android:id="@+id/textout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="It is a PopupWindow" />

<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />

<AnalogClock
android:id="@+id/tabAnalogClock"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<DigitalClock
android:id="@+id/tabDigitalClock"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<Button
android:id="@+id/dismiss"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Dismiss" />
</LinearLayout>
</LinearLayout>

</LinearLayout>

/res/drawable/customborder.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="0dp"
android:topRightRadius="30dp"
android:bottomRightRadius="30dp"
android:bottomLeftRadius="30dp" />
<stroke
android:width="3dp"
android:color="@android:color/background_dark" />
<solid
android:color="#20303000"/>
</shape>

/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:orientation="vertical"
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" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:autoLink="web" />

<Button
android:id="@+id/openpopup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Open Popup Window" />

<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/ic_launcher" />

</LinearLayout>

package com.example.androidpopupwindow;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.PopupWindow;
import android.app.Activity;

public class MainActivity extends Activity {

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

final Button btnOpenPopup = (Button) findViewById(R.id.openpopup);
btnOpenPopup.setOnClickListener(new Button.OnClickListener() {

@Override
public void onClick(View arg0) {
LayoutInflater layoutInflater =
(LayoutInflater)getBaseContext()
.getSystemService(LAYOUT_INFLATER_SERVICE);
View popupView = layoutInflater.inflate(R.layout.popup, null);
final PopupWindow popupWindow = new PopupWindow(
popupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

Button btnDismiss = (Button)popupView.findViewById(R.id.dismiss);

btnDismiss.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
popupWindow.dismiss();
}});

popupWindow.setFocusable(true);
popupWindow.showAsDropDown(btnOpenPopup, 50, -30);


}

});
}

}


download filesDownload the files.



More examples of using PopupWindow

Popup Window with dynamic content

This example show how to update content of popup window using Java code at run-time, instead of hard-coded in XML. The TextView (textOut) of Popup Window will be set according to another EditText (textIn) on main layout, before the Popup Window shown.

Popup Window with dynamic content
Popup Window with dynamic content

MainActivity.java
package com.example.androidpopupwindow;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.PopupWindow;
import android.widget.Spinner;
import android.widget.TextView;
import android.app.Activity;

public class MainActivity extends Activity {

String[] DayOfWeek = {"Sunday", "Monday", "Tuesday",
"Wednesday", "Thursday", "Friday", "Saturday"};

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

final EditText textIn = (EditText)findViewById(R.id.textin);

final Button btnOpenPopup = (Button) findViewById(R.id.openpopup);
btnOpenPopup.setOnClickListener(new Button.OnClickListener() {

@Override
public void onClick(View arg0) {
LayoutInflater layoutInflater =
(LayoutInflater)getBaseContext()
.getSystemService(LAYOUT_INFLATER_SERVICE);
View popupView = layoutInflater.inflate(R.layout.popup, null);
final PopupWindow popupWindow = new PopupWindow(
popupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

//Update TextView in PopupWindow dynamically
TextView textOut = (TextView)popupView.findViewById(R.id.textout);
String stringOut = textIn.getText().toString();
if(!stringOut.equals("")){
textOut.setText(stringOut);
}

Button btnDismiss = (Button)popupView.findViewById(R.id.dismiss);

Spinner popupSpinner = (Spinner)popupView.findViewById(R.id.popupspinner);

ArrayAdapter<String> adapter =
new ArrayAdapter<String>(MainActivity.this,
android.R.layout.simple_spinner_item, DayOfWeek);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
popupSpinner.setAdapter(adapter);

btnDismiss.setOnClickListener(new Button.OnClickListener(){

@Override
public void onClick(View v) {
popupWindow.dismiss();
}});

popupWindow.showAsDropDown(btnOpenPopup, 50, -30);


}

});
}

}

/res/layout/activity_main.xml, main layout.
<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:orientation="vertical"
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" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold"
android:layout_gravity="center_horizontal"
android:autoLink="web" />

<EditText
android:id="@+id/textin"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

<Button
android:id="@+id/openpopup"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Open Popup Window" />

<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/ic_launcher" />

</LinearLayout>

/res/layout/popup.xml, layout of the Popup Window.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/customborder"
android:orientation="vertical" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:orientation="vertical" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical" >

<TextView
android:id="@+id/textout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="It is a PopupWindow" />

<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />

<Spinner
android:id="@+id/popupspinner"
android:spinnerMode="dialog"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />

<Button
android:id="@+id/dismiss"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Dismiss" />
</LinearLayout>
</LinearLayout>

</LinearLayout>

/res/drawable/customborder.xml, define the custom border of the Popup Window.
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="0dp"
android:topRightRadius="30dp"
android:bottomRightRadius="30dp"
android:bottomLeftRadius="30dp" />
<stroke
android:width="3dp"
android:color="@android:color/background_dark" />
<solid
android:color="#40300030"/>
</shape>



download filesDownload the files.

Animated GIF: load attribute resource of android:src in XML

Previous examples show how to diaply animated GIF using decodeStream(InputStream) and decodeByteArray(InputStream) loaded with a preset resource, /res/drawable/android_er.gif. This example show how to define resource with android:src in XML, and retrieve in custom view.

load attribute resource of android:src in XML
load attribute resource of android:src in XML

Copy the GIFs to /res/drawable folder.
android_er.gif

android_er_rev.gif

Modify activity_main.xml to define android:src in <com.example.androidgif.GifView>.
<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:orientation="vertical"
tools:context="com.example.androidgif.MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:autoLink="web"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/android_er" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff000090"
android:orientation="vertical"
android:padding="5dp" >

<com.example.androidgif.GifView
android:id="@+id/gifview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff000090"
android:orientation="vertical"
android:padding="5dp" >

<com.example.androidgif.GifView
android:id="@+id/gifview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/android_er" />
</LinearLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff000090"
android:orientation="vertical"
android:padding="5dp" >

<com.example.androidgif.GifView
android:id="@+id/gifview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/android_er_rev" />
</LinearLayout>

</LinearLayout>

<TextView
android:id="@+id/textinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="info..." />

</LinearLayout>

GifView.java
package com.example.androidgif;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Toast;

public class GifView extends View {

//Set true to use decodeStream
//Set false to use decodeByteArray
private static final boolean DECODE_STREAM = true;

private InputStream gifInputStream;
private Movie gifMovie;
private int movieWidth, movieHeight;
private long movieDuration;
private long mMovieStart;

public GifView(Context context) {
super(context);
init(context, null);
}

public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

public GifView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}

private void init(final Context context, AttributeSet attrs){
setFocusable(true);

if(attrs == null){
Toast.makeText(getContext(),
"gifResource: null",
Toast.LENGTH_LONG).show();

gifMovie = null;
movieWidth = 0;
movieHeight = 0;
movieDuration = 0;
}else{

int gifResource = attrs.getAttributeResourceValue(
"http://schemas.android.com/apk/res/android",
"src",
0);

if(gifResource == 0){
Toast.makeText(getContext(),
"gifResource: 0",
Toast.LENGTH_LONG).show();

gifMovie = null;
movieWidth = 0;
movieHeight = 0;
movieDuration = 0;
}else{
Toast.makeText(getContext(),
"gifResource: " + gifResource,
Toast.LENGTH_LONG).show();

gifInputStream = context.getResources().openRawResource(gifResource);

if(DECODE_STREAM){
gifMovie = Movie.decodeStream(gifInputStream);
}else{
byte[] array = streamToBytes(gifInputStream);
gifMovie = Movie.decodeByteArray(array, 0, array.length);
}

movieWidth = gifMovie.width();
movieHeight = gifMovie.height();
movieDuration = gifMovie.duration();
}
}
}

private static byte[] streamToBytes(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int len;
try {
while ((len = is.read(buffer)) >= 0) {
os.write(buffer, 0, len);
}
} catch (java.io.IOException e) {
}
return os.toByteArray();
}

@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension(movieWidth, movieHeight);
}

public int getMovieWidth(){
return movieWidth;
}

public int getMovieHeight(){
return movieHeight;
}

public long getMovieDuration(){
return movieDuration;
}

@Override
protected void onDraw(Canvas canvas) {

long now = android.os.SystemClock.uptimeMillis();
if (mMovieStart == 0) { // first time
mMovieStart = now;
}

if (gifMovie != null) {

int dur = gifMovie.duration();
if (dur == 0) {
dur = 1000;
}

int relTime = (int)((now - mMovieStart) % dur);

gifMovie.setTime(relTime);

gifMovie.draw(canvas, 0, 0);
invalidate();

}

}

}

Other files, MainActivity.java and AndroidManifest.xml to turn OFF hardwareAccelerated, refer to the post "Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)".

download filesDownload the files.


Load animated GIF from Internet

Previous examples show how to diaply animated GIF using decodeStream(InputStream) and decodeByteArray(InputStream) loaded from  /res/drawable/ folder. This example show how to load from Internet.



  • We cannot access Internet in main thread, such that we have to implement a background thread to load the gif from Internet.
  • Once loaded, we have to ask Android system to re-layout with updated graph.
  • In the example code, dummy delay is added to simulate network delay.
  • Both decodeStream(InputStream) and decodeByteArray(InputStream) are implemented, you can choice it by setting DECODE_STREAM true or false.
  • It can be noted that textViewInfo is filled with 0, 0 x 0; because the GIF is not loaded when onCreate() called.
  • uses-permission android:name="android.permission.INTERNET" is needed in AndroidManifest.xml
Modify activity_main.xml to added a LinearLayout to show how the system layout at run-time.
<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:orientation="vertical"
tools:context="com.example.androidgif.MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:autoLink="web"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/android_er" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff000090"
android:orientation="vertical"
android:padding="5dp" >

<com.example.androidgif.GifView
android:id="@+id/gifview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

<TextView
android:id="@+id/textinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="info..." />

</LinearLayout>

GifView.java
package com.example.androidgif;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Toast;

public class GifView extends View {

//Set true to use decodeStream
//Set false to use decodeByteArray
private static final boolean DECODE_STREAM = true;

private InputStream gifInputStream;
private Movie gifMovie;
private int movieWidth, movieHeight;
private long movieDuration;
private long mMovieStart;

final static String gifAddr = "http://3.bp.blogspot.com/-aO7sI_xlx-0/Uyhbv6BB4DI/AAAAAAAAKzg/RfKWKzg7O-M/s1600/android_er.gif";

public GifView(Context context) {
super(context);
init(context);
}

public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public GifView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(final Context context){
setFocusable(true);

gifMovie = null;
movieWidth = 0;
movieHeight = 0;
movieDuration = 0;

Thread threadLoadGif = new Thread(new Runnable(){

@Override
public void run() {
try {
URL gifURL = new URL(gifAddr);

HttpURLConnection connection = (HttpURLConnection)gifURL.openConnection();
gifInputStream = connection.getInputStream();

//Insert dummy sleep
//to simulate network delay
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

if(DECODE_STREAM){
gifMovie = Movie.decodeStream(gifInputStream);
}else{
byte[] array = streamToBytes(gifInputStream);
gifMovie = Movie.decodeByteArray(array, 0, array.length);
}

movieWidth = gifMovie.width();
movieHeight = gifMovie.height();
movieDuration = gifMovie.duration();

((MainActivity)context).runOnUiThread(new Runnable(){

@Override
public void run() {
//request re-draw layout
invalidate();
requestLayout();
Toast.makeText(context,
movieWidth + " x " + movieHeight + "\n"
+ movieDuration,
Toast.LENGTH_LONG).show();
}});

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}});

threadLoadGif.start();

}

private static byte[] streamToBytes(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int len;
try {
while ((len = is.read(buffer)) >= 0) {
os.write(buffer, 0, len);
}
} catch (java.io.IOException e) {
}
return os.toByteArray();
}

@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension(movieWidth, movieHeight);
}

public int getMovieWidth(){
return movieWidth;
}

public int getMovieHeight(){
return movieHeight;
}

public long getMovieDuration(){
return movieDuration;
}

@Override
protected void onDraw(Canvas canvas) {

long now = android.os.SystemClock.uptimeMillis();
if (mMovieStart == 0) { // first time
mMovieStart = now;
}

if (gifMovie != null) {

int dur = gifMovie.duration();
if (dur == 0) {
dur = 1000;
}

int relTime = (int)((now - mMovieStart) % dur);

gifMovie.setTime(relTime);

gifMovie.draw(canvas, 0, 0);
invalidate();

}

}

}

Other files, MainActivity.java and AndroidManifest.xml to turn OFF hardwareAccelerated, refer to the post "Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)".

download filesDownload the files.


Play animated GIF using android.graphics.Movie, with Movie.decodeByteArray(InputStream)

Implement a custom view, GifView, to display animated GIF using android.graphics.Movie, load movie with Movie.decodeByteArray(), instead of loading with Movie.decodeStream().

Play animated GIF using android.graphics.Movie, with Movie.decodeByteArray(InputStream)
Modify GifView.java from previous example of loading with Movie.decodeStream(), to load with Movie.decodeByteArray().

package com.example.androidgif;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;

public class GifView extends View {

private InputStream gifInputStream;
private Movie gifMovie;
private int movieWidth, movieHeight;
private long movieDuration;
private long mMovieStart;

public GifView(Context context) {
super(context);
init(context);
}

public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public GifView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context){
setFocusable(true);
gifInputStream = context.getResources()
.openRawResource(R.drawable.android_er);

//gifMovie = Movie.decodeStream(gifInputStream);
byte[] array = streamToBytes(gifInputStream);
gifMovie = Movie.decodeByteArray(array, 0, array.length);

movieWidth = gifMovie.width();
movieHeight = gifMovie.height();
movieDuration = gifMovie.duration();
}

private static byte[] streamToBytes(InputStream is) {
ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
byte[] buffer = new byte[1024];
int len;
try {
while ((len = is.read(buffer)) >= 0) {
os.write(buffer, 0, len);
}
} catch (java.io.IOException e) {
}
return os.toByteArray();
}

@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension(movieWidth, movieHeight);
}

public int getMovieWidth(){
return movieWidth;
}

public int getMovieHeight(){
return movieHeight;
}

public long getMovieDuration(){
return movieDuration;
}

@Override
protected void onDraw(Canvas canvas) {

long now = android.os.SystemClock.uptimeMillis();
if (mMovieStart == 0) { // first time
mMovieStart = now;
}

if (gifMovie != null) {

int dur = gifMovie.duration();
if (dur == 0) {
dur = 1000;
}

int relTime = (int)((now - mMovieStart) % dur);

gifMovie.setTime(relTime);

gifMovie.draw(canvas, 0, 0);
invalidate();

}

}

}

All other files, include the animated GIF, activity_main.xml, MainActivity.java and AndroidManifest.xml; refer to previous post Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)

download filesDownload the files.

Next:
Load animated GIF from Internet
- Load attribute resource of android:src in XML


JDK 8 released, with new Java document page

Java SE 8 released! Download it from the Java SE Downloads page.


And the Java Documentation page updated with new nice look.


Android Wear Developer Preview is available now

Android Wear extends the Android platform to a new generation of wearable devices. The Android Wear Developer Preview is available now, to let you create wearable experiences for your existing Android apps and see how they will appear on square and round Android wearables. Later this year, the Android Wear SDK will be launched, enabling even more customized experiences.

In the coming months we’ll be launching new APIs and features for Android wearables to create even more unique experiences for the wrist:
  • Build Custom UI
    - Create custom card layouts and run activities directly on wearables.
  • Send Data
    - Send data and actions between a phone and a wearable with a data replication APIs and RPCs.
  • Control Sensors
    - Gather sensor data and display it in real-time on Android wearables.
  • Voice Actions
    - Register your app to handle voice actions, like "Ok Google, take a note."

Get Started with the New Android Wear Developer Preview NOW.

The Android Wear Developer Preview provides tools and APIs that allow you to enhance your app notifications to provide an optimized user experience on Android Wear.

With the Android Wear Developer Preview, you can:

  • Run the Android Wear platform in the Android emulator.
  • Connect your Android device to the emulator and view notifications from the device as cards on Android Wear.
  • Try new APIs in the preview support library that enhance your app's notifications with features such as voice replies and notification pages.
Caution: The current Android Wear Developer Preview is intended for development and testing purposes only, not for production apps. Google may change this Developer Preview significantly prior to the official release of the Android Wear SDK. You may not publicly distribute or ship any application using this Developer Preview, as this Developer Preview will no longer be supported after the official SDK is released (which will cause applications based only on the Developer Preview to break).




Upgrade to the latest Google Mobile Ads SDK

A number of enhancements have been added to Android and iOS SDKs to help developers save time, increase flexibility, and capture new opportunities.

Full support for device identifiers
In order to give users better control and to provide you with a simple, standard system to continue to monetize your apps, full support for anonymous device identifiers is introduced within the new iOS and Android SDKs. These identifiers help marketers better reach and engage with the types of users most valuable to them while helping developers improve monetization through higher CPMs and fill rates.

Seamless auto-updates for Android
The Google Mobile Ads SDK is now fully integrated with Google Play Services so you can take full advantage of features and capabilities in each new release of Google Play services, without needing to update your APK. 

Monetize your apps
The Google Mobile Ads SDK fully supports AdMob and DoubleClick for Publishers. Connect with even more advertisers with ad network optimization - now fully supporting JavaScript based ad network tags via the iOS and Android SDKs. 

To get started with our upgraded iOS or Android SDKs, simply visit the Google Developers site and follow the instructions listed.

The standalone AdMob SDK will be deprecated on 1 August 2014

Now that Google Mobile Ads is supported in Google Play services.  The standalone AdMob SDK will be deprecated on 1 August, 2014. Play services supports Advertising ID, and provides seamless updates to Android users.

On 1 August, the Play Store will stop accepting new or updated apps that use the standalone Google AdMob SDK. The standalone SDK does not use the Advertising ID, and will therefore be affected by the Google Play Ad Policy. Google Play services still supports devices that don't have the Google Play store installed on it. The only difference is that devices without the Play Store will not receive automatic updates.

read more: Google Ads Developer Blog - Announcing Deprecation of the Standalone Android Google AdMob SDK

Related: Upgrade to the latest Google Mobile Ads SDK

Play animated GIF using android.graphics.Movie, with Movie.decodeStream(InputStream)

This example implement a custom view, GifView, to display animated GIF using android.graphics.Movie, load movie with with Movie.decodeStream(InputStream).

Download and copy the GIF file (android_er.gif) to /res/drawable/ folder.

android_er.gif
android_er.gif

Create GifView.java extends View
package com.example.androidgif;

import java.io.InputStream;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;

public class GifView extends View {

private InputStream gifInputStream;
private Movie gifMovie;
private int movieWidth, movieHeight;
private long movieDuration;
private long mMovieStart;

public GifView(Context context) {
super(context);
init(context);
}

public GifView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public GifView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

private void init(Context context){
setFocusable(true);
gifInputStream = context.getResources()
.openRawResource(R.drawable.android_er);

gifMovie = Movie.decodeStream(gifInputStream);
movieWidth = gifMovie.width();
movieHeight = gifMovie.height();
movieDuration = gifMovie.duration();
}

@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension(movieWidth, movieHeight);
}

public int getMovieWidth(){
return movieWidth;
}

public int getMovieHeight(){
return movieHeight;
}

public long getMovieDuration(){
return movieDuration;
}

@Override
protected void onDraw(Canvas canvas) {

long now = android.os.SystemClock.uptimeMillis();
if (mMovieStart == 0) { // first time
mMovieStart = now;
}

if (gifMovie != null) {

int dur = gifMovie.duration();
if (dur == 0) {
dur = 1000;
}

int relTime = (int)((now - mMovieStart) % dur);

gifMovie.setTime(relTime);

gifMovie.draw(canvas, 0, 0);
invalidate();

}

}

}

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:orientation="vertical"
tools:context="com.example.androidgif.MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:autoLink="web"
android:text="http://arteluzevida.blogspot.com/"
android:textStyle="bold" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/android_er"
/>

<com.example.androidgif.GifView
android:id="@+id/gifview"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

<TextView
android:id="@+id/textinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="info..." />

</LinearLayout>

MainActivity.java
package com.example.androidgif;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

TextView textViewInfo;
GifView gifView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gifView = (GifView)findViewById(R.id.gifview);
textViewInfo = (TextView)findViewById(R.id.textinfo);

String stringInfo = "";
stringInfo += "Duration: " + gifView.getMovieDuration() + "\n";
stringInfo += "W x H: "
+ gifView.getMovieWidth() + " x "
+ gifView.getMovieHeight() + "\n";

textViewInfo.setText(stringInfo);

}

}

IMPORTANT!
Modify AndroidManifest.xml to turn OFF hardwareAccelerated. Read remark on the bottom.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.androidgif"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />

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

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

</manifest>



download filesDownload the files.

Related:
Play animated GIF using android.graphics.Movie, with Movie.decodeByteArray(InputStream)
Load animated GIF from Internet
Load attribute resource of android:src in XML



Remark: if android:hardwareAccelerated haven't been set "false" in AndroidManifest.xml, something will go wrong:

- Can't display the animated GIF at HTC One X:

- App stopped at Nexus 7, with error of:
Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1), thread 26494 (mple.androidgif)

Android Wear Developer Preview Now Available

By Austin Robison, Android Wear team



Android Wear extends the Android platform to wearables. These small, powerful devices give users useful information just when they need it. Watches powered by Android Wear respond to spoken questions and commands to provide info and get stuff done. These new devices can help users reach their fitness goals and be their key to a multiscreen world.



We designed Android Wear to bring a common user experience and a consistent developer platform to this new generation of devices. We can’t wait to see what you will build.





Getting started



Your app’s notifications will already appear on Android wearables and starting today, you can sign up for the Android Wear Developer Preview. You can use the emulator provided to preview how your notifications will appear on both square and round Android wearables. The Developer Preview also includes new Android Wear APIs which will let you customize and extend your notifications to accept voice replies, feature additional pages, and stack with similar notifications. Head on over to developer.android.com/wear to sign up and learn more.





For a brief introduction to the developer features of Android Wear, check out these DevBytes videos. They include demos and a discussion about the code snippets driving them.





What’s next?



We’re just getting started with the Android Wear Developer Preview. In the coming months we’ll be launching new APIs and features for Android Wear devices to create even more unique experiences for the wrist.



Join the Android Wear Developers community on Google+ to discuss the Preview and ask questions.



We’re excited to see what you build!



Sharing what’s up our sleeve: Android coming to wearables

Most of us are rarely without our smartphones in hand. These powerful supercomputers keep us connected to the world and the people we love. But we're only at the beginning; we’ve barely scratched the surface of what’s possible with mobile technology. That’s why we’re so excited about wearables—they understand the context of the world around you, and you can interact with them simply and efficiently, with just a glance or a spoken word.

Android Wear: Information that moves with you 
Today we’re announcing Android Wear, a project that extends Android to wearables. And we’re starting with the most familiar wearable—watches. Going well beyond the mere act of just telling you the time, a range of new devices along with an expansive catalogue of apps will give you:
  • Useful information when you need it most. Android Wear shows you info and suggestions you need, right when you need them. The wide variety of Android applications means you’ll receive the latest posts and updates from your favorite social apps, chats from your preferred messaging apps, notifications from shopping, news and photography apps, and more. 
  • Straight answers to spoken questions. Just say “Ok Google” to ask questions, like how many calories are in an avocado, what time your flight leaves, and the score of the game. Or say “Ok Google” to get stuff done, like calling a taxi, sending a text, making a restaurant reservation or setting an alarm. 
  • The ability to better monitor your health and fitness. Hit your exercise goals with reminders and fitness summaries from Android Wear. Your favorite fitness apps can give you real-time speed, distance and time information on your wrist for your run, cycle or walk. 
  • Your key to a multiscreen world. Android Wear lets you access and control other devices from your wrist. Just say “Ok Google” to fire up a music playlist on your phone, or cast your favorite movie to your TV. There’s a lot of possibilities here so we’re eager to see what developers build. 


Developer Preview 
If you’re a developer, there’s a new section on developer.android.com/wear focused on wearables. Starting today, you can download a Developer Preview so you can tailor your existing app notifications for watches powered by Android Wear. Because Android for wearables works with Android's rich notification system, many apps will already work well. Look out for more developer resources and APIs coming soon. We’re also already working with several consumer electronics manufacturers, including Asus, HTC, LG, Motorola and Samsung; chip makers Broadcom, Imagination, Intel, Mediatek and Qualcomm; and fashion brands like the Fossil Group to bring you watches powered by Android Wear later this year.


We're always seeking new ways for technology to help people live their lives and this is just another step in that journey. Here’s to getting the most out of the many screens you use every day—whether in your car, in your pocket or, very soon, on your wrist.

Posted by Sundar Pichai, SVP, Android, Chrome & Apps

Google Developer Day at GDC

Day 2 of Game Developers Conference 2014 is getting underway and today Google is hosting a special Developer Day at Moscone Center in San Francisco.



Join us at the sessions



Building on yesterday’s announcements for game developers, we'll be presenting a series of sessions that walk you through the new features, services, and tools, explaining how they work and what they can bring to your games.



We'll also be talking with you about how to reach and engage with hundreds of millions of users on Google Play, build Games that scale in the cloud, grow in-game advertising businesses with AdMob, track revenue with Google Analytics, as well as explore new gaming frontiers, like Glass.



If you’re at the conference, the Google Developer Day sessions are a great opportunity to meet the developer advocates, engineers, and product managers of the Google products that drive users, engagement and retention for your games. If you’re remote, we invite you to sit-in on the sessions by joining the livestream below or on Google Developers channel on YouTube.



The Developer Day sessions (and livestream) kick off at 10:00AM PDT (5:00PM UTC). A complete agenda is available on the GDC Developer Day page.








LiquidFun 1.0



Last December we announced the initial release of LiquidFun, a C++ library that adds particle physics, including realistic fluid dynamics, to the open-source Box2D.



To get Google Developer Day started, we’re releasing LiquidFun 1.0, an update that adds multiple particle systems, new particle behaviors, and other new features.



Check out the video below to see what Liquid Fun 1.0 can do, visit the LiquidFun home page, or join today's LiquidFun session at Google Developer Day to learn how LiquidFun works and how to use particle physics in your games. The session starts at 4:35PM PDT (11:35PM UTC).








Google Play services 4.3

gps

Google Play services 4.3 has now been rolled out to the world, and it contains a number of features you can use to improve your apps. Specifically, this version adds some new members to the Google Play services family: Google Analytics API, Tag Manager, and the Address API. We’ve also made some great enhancements to the existing APIs; everything to make sure you stay on top of the app game out there.



Here are the highlights of the 4.3 release.



Google Analytics and Google Tag Manager

The Analytics API and Google Tag Manager has existed for Android for some time as standalone technologies, but with this release we are incorporating them as first class citizens in Google Play services. Those of you that are used to the API will find it very similar to previous versions, and if you have not used it before we strongly encourage you to take a look at it.

Google Analytics allows you to get detailed statistics on how you app is being used by your users, for example what functionality of your app is being used the most, or which activity triggers users to convert from an advertised version of an app to paid one. Google Tag Manager lets you change characteristics of your app on-the-fly, for example colors, without having to push an update from Google Play.



Google Play Games services Update

The furious speed of innovation in Android mobile gaming has not slowed down and neither have we when it comes to packing the Google Play Game services API with features.

With this release, we are introducing game gifts, which allows players to send virtual in-game requests to anyone in their Google+ circles or through player search. Using this feature, the player can send a 'wish' request to ask another player for an in-game item or benefit, or a 'gift' request to grant an item or benefit to another player.

This is a great way for a game to be more engaging by increasing cross player collaboration and social connections. We are therefore glad to add this functionality as an inherent part of the Games API, it is an much-wanted extension to the multi-player functionality included a couple of releases ago. For more information, see: Unlocking the power of Google for your games.



Drive API

The Google Drive for Android API was just recently added as a member of the Google Play services API family. This release adds a number of important features:

  • Pinning - You can now pin files that should be kept up to date locally, ensuring that it is available when the user is offline. This is great for users that need to use your app with limited or no connectivity


  • App Folders - An app often needs to create files which are not visible to the user, for example to store temporary files in a photo editor. This can now be done using App Folders, a feature is analogous to Application Data Folders in the Google Drive API


  • Change Notifications - You can now register a callback to receive notifications when a file or folder is changed. This mean you no longer need to query Drive continuously to check if the data has changed, just put a change notification on it


In addition to the above, we've also added the ability to access a number of new metadata fields.



Address API

This release will also includes a new Address API, which allows developers to request access to addresses for example to fill out a delivery address form. The kicker is the convenience for the user; a user interface component is presented where they select the desired address, and bang, the entire form is filled out. Developers have been relying on Location data which works very well, but this API shall cater for cases where the Location data is either not accurate or the user actually wants to use a different address than their current physical location. This should sound great to anyone who has done any online shopping during the last decade or so.



That’s it for this time. Now go to work and incorporate these new features to make your apps even better!

And stay tuned for future updates.



For the release video, please see:

DevBytes: Google Play Services 4.3



For details on the APIs, please see:

Google Analytics

Google Tag Manager

Google Play Games services Gifts

Google Drive Android API - Change Events

Google Drive Android API - Pinning

Google Drive Android API - App Folder

Address API