Wednesday, June 29, 2011

Android Touch


Lars Vogel

Version 0.1
03.06.2011
Revision History
Revision 0.103.06.2011Lars Vogel
Created
Touch in Android
This tutorial describes how to use touch in Android applications with Eclipse.

1. Android Touch

1.1. Android Basics

One of the primary inputs for Android is the touch interface. The standard UI components (View) already support touch without the need of the developer to interfere. This article describes how to implement a touch interface in your Android application.
The base class for touch support is the class "MotionEvent". MotionEvent contains the touch related information. To react to touch events in your view or your activity you override the method "onTouchEvent()". This method must return a boolean value which indicates if the touch event has been consumed (true) or if the framework should still react to the touch event (false). If single input is used you can use the methods getX() and getY() to get the current position. Via getAction() you receive the action which was performed.
Table 1. Sample Table
EventDescription
MotionEvent.ACTION_DOWNNew touch started
MotionEvent.ACTION_MOVEFinger is moving
MotionEvent.ACTION_UPFinger went up
MotionEvent.ACTION_OUTSIDEFinger is leaving the UI component

Multitouch was introduced in Android 2.0 and has been improved in each Android version. This tutorial focus currently on SingleTouch and it is planned to extend this for multitouch.

1.2. Android Basics

The following assumes that you have already basic knowledge in Android development .

2. Singletouch Example

We will demonstrate Singletouch via an example with an own view. Create the Android project "de.vogella.android.touch.single" with the activity "SingleTouchView".
Create the following view class "SingleTouchEventView".

package de.vogella.android.touch.single;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class SingleTouchEventView extends View {
 private Paint paint = new Paint();
 private Path path = new Path();

 public SingleTouchEventView(Context context, AttributeSet attrs) {
  super(context, attrs);

  paint.setAntiAlias(true);
  paint.setStrokeWidth(6f);
  paint.setColor(Color.WHITE);
  paint.setStyle(Paint.Style.STROKE);
  paint.setStrokeJoin(Paint.Join.ROUND);
 }

 @Override
 protected void onDraw(Canvas canvas) {
  canvas.drawPath(path, paint);
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  float eventX = event.getX();
  float eventY = event.getY();

  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
   path.moveTo(eventX, eventY);
   return true;
  case MotionEvent.ACTION_MOVE:
   path.lineTo(eventX, eventY);
   break;
  case MotionEvent.ACTION_UP:
   // nothing to do
   break;
  default:
   return false;
  }

  // Schedules a repaint.
  invalidate();
  return true;
 }
}

  

Add this view to your activity.

package de.vogella.android.touch.single;

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

public class SingleTouchActivity extends Activity {
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(new SingleTouchEventView(this, null));
 }
}
  

If you run your application you will be able to draw on the screen with your finger (or with the mouse in the emulator).

3. Thank you

Android Animations - Tutorial


Lars Vogel

Version 0.4
10.03.2011
Revision History
Revision 0.105.01.2011Lars Vogel
Created
Revision 0.2 - 0.408.02.2011 - 10.03.2011Lars Vogel
bug fixes and enhancements
Android Animations
This tutorial describes how to use Animations in Android. The tutorial is based on Eclipse 3.6, Java 1.6 and Android 2.3 (Gingerbread).

1. Android Animations

1.1. Overview

Android supports two types of animations: Frame-by-frame animations and Tweened Animations. Frame-by-frame animations shows different drawables in a View. Tweened animations are applied to Views and transform them in size, position or opacity. Both animations are limited to the original size of the view.

1.2. Tweened Animations

Tweened animations are descripted via resource files and are relatively unexpensive operations. You can use AlphaAnimation, RotateAnimation, ScaleAnimation and TranslateAnimation.
You can group animations in "AnimationSet" in which you define the start time, duration and offsets of each animation.
You apply the animation to a view via view.startAnimation() passing the AnimationSet as a parameter. Per default a AnimationSet will run once, unless you use setRepeatModel() and setRepeastCount().
Register an "AnimationListener" to react to the animation. This listener is notified when an animation starts or ends.
Animations can be defined via XMl resource files in the directory "res/anim" or via code. You can load the resource file via AnimationUtils.loadAnimiation(this,R.anim.Animation).

1.3. Android Basics

The following assumes that you have already basic knowledge in Android development . Please check the Android development tutorial to learn the basics.

2. Example

We will create a simple example which rotates a TextView. We will also add an "AnimationListener" which listeners to the animations events. Create a new Android project "de.vogella.android.animation" with the activity "AnimationExample". Change "main.xml" to the following.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Animate" android:onClick="startAnimation"></Button>
<TextView android:id="@+id/textview" android:layout_width="fill_parent"
 android:layout_height="wrap_content" android:text="@string/hello" />
</LinearLayout>

  

Create a new resource file "myanimation.xml" of type animation.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="true">
 <rotate android:fromDegrees="0" android:toDegrees="360"
  android:duration="4000" android:pivotX="50%" android:pivotY="50%"
  android:startOffset="0">
 </rotate>
</set>


  

Change your activity to the following.

package de.vogella.android.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.Toast;

public class AnimationExample extends Activity implements AnimationListener {
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

 }

 public void startAnimation(View view) {
  Animation animation = AnimationUtils.loadAnimation(this,
    R.anim.myanimation);
  animation.setAnimationListener(this);
  View animatedView = findViewById(R.id.textview);

  animatedView.startAnimation(animation);

 }

 @Override
 public void onAnimationStart(Animation animation) {
  Toast.makeText(this, "Animation started", Toast.LENGTH_SHORT).show();
 }

 @Override
 public void onAnimationEnd(Animation animation) {
  Toast.makeText(this, "Animation ended", Toast.LENGTH_SHORT).show();
 }

 @Override
 public void onAnimationRepeat(Animation animation) {
  Toast.makeText(this, "Animation rep", Toast.LENGTH_SHORT).show();
 }
}
  

If you run this example and press the button, the animation should start.

3. Thank you

Android Cloud to Device Messaging (C2DM) - Tutorial


Lars Vogel

Version 0.8
11.03.2011
Revision History
Revision 0.108.01.2011Lars Vogel
Created
Revision 0.2 - 0.809.01.2011 - 11.03.2011Lars Vogel
updated and bugfixes
Android C2DMs
This tutorial describes how to push information from a server to the Google device. It is based on Eclipse 3.6, Java 1.6 and Android 2.3 (Gingerbread)).

1. Cloud to device messaging

1.1. Cloud to device messaging

As of Android 2.2 the Android platform allows to push information from the Internet to an Android phone. This service is called "Cloud to Device messaging" or short C2DM.
The standard mechanism for an application which requires data from the internet is to poll periodically for the data. This uses unnecessary network bandwidth and consumes the battery. Therefore pushing data over the internet to the phone might be a preferable solution.
C2DM is intented to notify the mobile phone that new data is available. It is not intended to send lots of data, as the size of the message is limited to 1024 characters. The push service would notify the mobile device and the Android application would fetch the data from somewhere else.
C2DM uses existing connections the Google servers which is highly optimize to minimize bandwidth and battery consumption.

1.2. Requirements

C2MD only works as of Android 2.2 and requires that Android Market application is installed on the device.
To use C2DM on the Android simulator you also need to use a Google device with API 8 or higher and to register with a Google account on the emulator via the Settings.
To use C2DM on your Android device you have to write an Android application. This application will register at a Google server to be able to receive push messages from the Google server. Your server registers with the Google server and authenticates himself. The server can then push messages to the Google server which in return pushes the information to the device. For this authentication you require a Google user.

1.3. Information flow

In a C2DM you have three involved parties. The server which wants to push messages to the Android device (application server), the Google C2DM servers and the Android application.
To enable C2DM for an Android application, this application must registers with the Google C2DM servers. After registration it receives a registration ID from the Google C2DM servers.
The Android application must then send the registration ID to its application server. The application server stores the registration ID. This registration ID is required to send the information later to this specific device.
When the application server needs to push a message to the Android applicatoin, it sends the message via HTTP GET to Google’s C2DM servers.
The C2DM servers route the message to the device once the device is available, and an Intent broadcast is sent to the Android application. The app is started and processes the message in its defined Intent Receiver.

1.4. More details

To register your application for the C2DM service your application needs to fire an registration intent "com.google.android.c2dm.intent.REGISTER" to the C2DM server. This intent includes a Google mail address and the application ID.
Upon successful registration the C2DM server broadcast a REGISTRATION Intent to the application which includes a registration ID. Each registration ID represents a particular device, e.g. every Android phone will receive its own registration code.
The C2DM may refresh this registration ID periodically but until a refreshment your application should store this ID for later use.
Google provides a small set of classes which makes the registration as smooth as possible.
After the Android application received its unique registration ID it has to send this information to the application server.The application server can use this registration ID to send messages to the Android application on the specific device.

1.5. Send messages

With your registration ID the application server can send messages to your device. This requires in addition an authorization token which needs to be generated for each per Android application and registration ID.
The authorization token is generated by sending a post HTTPS request to the C2DM servers.
To send a message to the Android application on a specific device the application server sends a HTTP GET request to the Google C2DM servers. This HTTP GET request contain the registration ID (to address a specific device) and well as the authentication token (to tell Google that this server is allowed to send messages).
Once your server sends the message to the C2DM server this server will queue the message until the device is available. The message will be send to the device as a broadcast. Your application needs to register for this broadcast event to receive the message.

1.6. Receiving a Message

If a message is received on a device via a broadcast event. The system extracts the raw key/value pairs from the message. It then passes the key/value pairs to the Android application in a "com.google.android.c2dm.intent.RECEIVE" Intent. The data can be received from the Intent via getExtras(). The available keys are "payload", "from", "collapse_key". THe actual data is included in "payload">. The Android application extracts this data and can react to it.

1.7. Permissions

To use C2DM in your application to have to register for the permission "com.google.android.c2dm.permission.RECEIVE" and in most cases also "android.permission.INTERNET" permission. Your application should also declare the permission "applicationPackage + ".permission.C2D_MESSAGE" with the "android:protectionLevel" of "signature" so that other applications cannot register and receive message for the application. android:protectionLevel="signature". ensures that applications with request a permission must be signed with same certificate as the application that declared the permission.
Your application must also register as an intent receiver for the two intents "com.google.android.c2dm.intent.RECEIVE" and "com.google.android.c2dm.intent.REGISTRATION". "com.google.android.c2dm.SEND"

2. Device and Registration

Currently C2DM is under beta testing. You need to ask for access. Here is the link to the signup form .
If you test the following example on the emulator make sure that you use Google API 8 or later. You also have to register a Google user on the virtual device under "Settings"-> "Accounts Sync".
If you test the following example on a real device make sure that the Android Market is installed.

3. Example

3.1. Project

Create the Android project "de.vogella.android.c2dm" with the activity "RegisterActivity". Create the following layout "main.xml" which will allow to enter your registered user id for C2DM and send a registration request to the C2DM servers.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <EditText android:id="@+id/editText1" android:layout_height="wrap_content"
  android:layout_width="match_parent" android:hint="Enter C2DM registered User"
  ></EditText>
 <LinearLayout android:layout_height="match_parent"
  android:layout_width="match_parent" android:id="@+id/linearLayout1"
  android:gravity="center">
  <Button android:id="@+id/button1" android:text="Register for Push Notification"
   android:layout_height="wrap_content" android:onClick="register"
   android:layout_width="wrap_content"></Button>
 </LinearLayout>

</LinearLayout>

   

3.2. Create the Google utility classes

Create the following three classes from Google. Unfortunately I was not able to find a downloadable package of these classes at Google therefore I reproduce them.

/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.c2dm;

import java.io.IOException;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;

/**
 * Base class for C2D message receiver. Includes constants for the strings used
 * in the protocol.
 */
public abstract class C2DMBaseReceiver extends IntentService {
 private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";

 public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
 private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";

 // Logging tag
 private static final String TAG = "C2DM";

 // Extras in the registration callback intents.
 public static final String EXTRA_UNREGISTERED = "unregistered";

 public static final String EXTRA_ERROR = "error";

 public static final String EXTRA_REGISTRATION_ID = "registration_id";

 public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
 public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
 public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
 public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
 public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
 public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
 public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";

 // wakelock
 private static final String WAKELOCK_KEY = "C2DM_LIB";

 private static PowerManager.WakeLock mWakeLock;
 private final String senderId;

 /**
  * The C2DMReceiver class must create a no-arg constructor and pass the
  * sender id to be used for registration.
  */
 public C2DMBaseReceiver(String senderId) {
  // senderId is used as base name for threads, etc.
  super(senderId);
  this.senderId = senderId;
 }

 /**
  * Called when a cloud message has been received.
  */
 protected abstract void onMessage(Context context, Intent intent);

 /**
  * Called on registration error. Override to provide better error messages.
  * 
  * This is called in the context of a Service - no dialog or UI.
  */
 public abstract void onError(Context context, String errorId);

 /**
  * Called when a registration token has been received.
  */
 public void onRegistered(Context context, String registrationId)
   throws IOException {
  // registrationId will also be saved
 }

 /**
  * Called when the device has been unregistered.
  */
 public void onUnregistered(Context context) {
 }

 @Override
 public final void onHandleIntent(Intent intent) {
  try {
   Context context = getApplicationContext();
   if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
    handleRegistration(context, intent);
   } else if (intent.getAction().equals(C2DM_INTENT)) {
    onMessage(context, intent);
   } else if (intent.getAction().equals(C2DM_RETRY)) {
    C2DMessaging.register(context, senderId);
   }
  } finally {
   // Release the power lock, so phone can get back to sleep.
   // The lock is reference counted by default, so multiple
   // messages are ok.

   // If the onMessage() needs to spawn a thread or do something else,
   // it should use it's own lock.
   mWakeLock.release();
  }
 }

 /**
  * Called from the broadcast receiver. Will process the received intent,
  * call handleMessage(), registered(), etc. in background threads, with a
  * wake lock, while keeping the service alive.
  */
 static void runIntentInService(Context context, Intent intent) {
  if (mWakeLock == null) {
   // This is called from BroadcastReceiver, there is no init.
   PowerManager pm = (PowerManager) context
     .getSystemService(Context.POWER_SERVICE);
   mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
     WAKELOCK_KEY);
  }
  mWakeLock.acquire();

  // Use a naming convention, similar with how permissions and intents are
  // used. Alternatives are introspection or an ugly use of statics.
  String receiver = context.getPackageName() + ".C2DMReceiver";
  intent.setClassName(context, receiver);

  context.startService(intent);

 }

 private void handleRegistration(final Context context, Intent intent) {
  final String registrationId = intent
    .getStringExtra(EXTRA_REGISTRATION_ID);
  String error = intent.getStringExtra(EXTRA_ERROR);
  String removed = intent.getStringExtra(EXTRA_UNREGISTERED);

  if (Log.isLoggable(TAG, Log.DEBUG)) {
   Log.d(TAG, "dmControl: registrationId = " + registrationId
     + ", error = " + error + ", removed = " + removed);
  }

  if (removed != null) {
   // Remember we are unregistered
   C2DMessaging.clearRegistrationId(context);
   onUnregistered(context);
   return;
  } else if (error != null) {
   // we are not registered, can try again
   C2DMessaging.clearRegistrationId(context);
   // Registration failed
   Log.e(TAG, "Registration error " + error);
   onError(context, error);
   if ("SERVICE_NOT_AVAILABLE".equals(error)) {
    long backoffTimeMs = C2DMessaging.getBackoff(context);

    Log.d(TAG, "Scheduling registration retry, backoff = "
      + backoffTimeMs);
    Intent retryIntent = new Intent(C2DM_RETRY);
    PendingIntent retryPIntent = PendingIntent
      .getBroadcast(context, 0 /* requestCode */, retryIntent,
        0 /* flags */);

    AlarmManager am = (AlarmManager) context
      .getSystemService(Context.ALARM_SERVICE);
    am.set(AlarmManager.ELAPSED_REALTIME, backoffTimeMs,
      retryPIntent);

    // Next retry should wait longer.
    backoffTimeMs *= 2;
    C2DMessaging.setBackoff(context, backoffTimeMs);
   }
  } else {
   try {
    onRegistered(context, registrationId);
    C2DMessaging.setRegistrationId(context, registrationId);
   } catch (IOException ex) {
    Log.e(TAG, "Registration error " + ex.getMessage());
   }
  }
 }
}

   


package com.google.android.c2dm;
/*
 */


import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

/**
 * Helper class to handle BroadcastReciver behavior.
 * - can only run for a limited amount of time - it must start a real service 
 * for longer activity
 * - must get the power lock, must make sure it's released when all done.
 * 
 */
public class C2DMBroadcastReceiver extends BroadcastReceiver {
    
    @Override
    public final void onReceive(Context context, Intent intent) {
        // To keep things in one place.
        C2DMBaseReceiver.runIntentInService(context, intent);
        setResult(Activity.RESULT_OK, null /* data */, null /* extra */);        
    }
}

   


package com.google.android.c2dm;
/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */



import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

/**
 * Utilities for device registration.
 *
 * Will keep track of the registration token in a private preference.
 */
public class C2DMessaging {
    public static final String EXTRA_SENDER = "sender";
    public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
    public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER";
    public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
    public static final String LAST_REGISTRATION_CHANGE = "last_registration_change";
    public static final String BACKOFF = "backoff";
    public static final String GSF_PACKAGE = "com.google.android.gsf";


    // package
    static final String PREFERENCE = "com.google.android.c2dm";
    
    private static final long DEFAULT_BACKOFF = 30000;

    /**
     * Initiate c2d messaging registration for the current application
     */
    public static void register(Context context,
            String senderId) {
        Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
        registrationIntent.setPackage(GSF_PACKAGE);
        registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
                PendingIntent.getBroadcast(context, 0, new Intent(), 0));
        registrationIntent.putExtra(EXTRA_SENDER, senderId);
        context.startService(registrationIntent);
        // TODO: if intent not found, notification on need to have GSF
    }

    /**
     * Unregister the application. New messages will be blocked by server.
     */
    public static void unregister(Context context) {
        Intent regIntent = new Intent(REQUEST_UNREGISTRATION_INTENT);
        regIntent.setPackage(GSF_PACKAGE);
        regIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT, PendingIntent.getBroadcast(context,
                0, new Intent(), 0));
        context.startService(regIntent);
    }

    /**
     * Return the current registration id.
     *
     * If result is empty, the registration has failed.
     *
     * @return registration id, or empty string if the registration is not complete.
     */
    public static String getRegistrationId(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        String registrationId = prefs.getString("dm_registration", "");
        return registrationId;
    }

    public static long getLastRegistrationChange(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        return prefs.getLong(LAST_REGISTRATION_CHANGE, 0);
    }
    
    static long getBackoff(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        return prefs.getLong(BACKOFF, DEFAULT_BACKOFF);
    }
    
    static void setBackoff(Context context, long backoff) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putLong(BACKOFF, backoff);
        editor.commit();

    }

    // package
    static void clearRegistrationId(Context context) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString("dm_registration", "");
        editor.putLong(LAST_REGISTRATION_CHANGE, System.currentTimeMillis());
        editor.commit();

    }

    // package
    static void setRegistrationId(Context context, String registrationId) {
        final SharedPreferences prefs = context.getSharedPreferences(
                PREFERENCE,
                Context.MODE_PRIVATE);
        Editor editor = prefs.edit();
        editor.putString("dm_registration", registrationId);
        editor.commit();

    }
}

   

3.3. Getting ready for registration

Create the following class "C2DMReceiver" which we will later register as a service.

package de.vogella.android.c2dm;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import com.google.android.c2dm.C2DMBaseReceiver;

public class C2DMReceiver extends C2DMBaseReceiver {
 public C2DMReceiver() {
  // Email address currently not used by the C2DM Messaging framework
  super("dummy@google.com");
 }

 @Override
 public void onRegistered(Context context, String registrationId)
   throws java.io.IOException {
  // The registrationId should be send to your applicatioin server.
  // We just log it to the LogCat view
  // We will copy it from there
  Log.e("C2DM", "Registration ID arrived: Fantastic!!!");
  Log.e("C2DM", registrationId);
 };

 @Override
 protected void onMessage(Context context, Intent intent) {
  Log.e("C2DM", "Message: Fantastic!!!");
  // Extract the payload from the message
  Bundle extras = intent.getExtras();
  if (extras != null) {
   System.out.println(extras.get("payload"));
   // Now do something smart based on the information
  }
 }

 @Override
 public void onError(Context context, String errorId) {
  Log.e("C2DM", "Error occured!!!");
 }

}

   

Create the following "AndroidManifest.xml". That will register the right receivers and requests the right permissions.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="de.vogella.android.c2dm" android:versionCode="1"
 android:versionName="1.0">
 <permission android:name="de.vogella.android.c2dm.permission.C2D_MESSAGE"
  android:protectionLevel="signature" />
 <uses-permission android:name="de.vogella.android.c2dm.permission.C2D_MESSAGE" />
 <!-- Permissions -->
 <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.WAKE_LOCK" />


 <application android:icon="@drawable/icon" android:label="@string/app_name">
  <activity android:name="RegisterActivity" android:label="@string/app_name">
   <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>
  <service android:name=".C2DMReceiver" />


  <!-- Only C2DM servers can send messages for the app. If permission is 
   not set - any other app can generate it -->
  <receiver android:name="com.google.android.c2dm.C2DMBroadcastReceiver"
   android:permission="com.google.android.c2dm.permission.SEND">
   <!-- Receive the actual message -->
   <intent-filter>
    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    <category android:name="de.vogella.android.c2dm" />
   </intent-filter>
   <!-- Receive the registration id -->
   <intent-filter>
    <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
    <category android:name="de.vogella.android.c2dm" />
   </intent-filter>
  </receiver>
 </application>
</manifest>
   

Change your Activity to the following. In this activity you can enter your registered Google C2DM user and ask for a registration key.

package de.vogella.android.c2dm;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.google.android.c2dm.C2DMessaging;

public class RegisterActivity extends Activity {
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 }

 public void register(View view) {
  Log.e("Super", "Starting registration");
  Toast.makeText(this, "Starting", Toast.LENGTH_LONG).show();
  EditText text = (EditText) findViewById(R.id.editText1);

  C2DMessaging.register(this, text.getText().toString());
 }
}
   

3.4. Register your application

Run your application, maintain your registered user and press the button. Check LogCat for the registration ID.
If you see " Unable to start service Intent { act=com.google.android.c2dm.intent.REGISTER pkg=com.google.android.gsf (has extras) }: not found" message make sure you are using a Google device and that you have a Google user registered on the device.

3.5. Using curl

If you run a Linux system you can easily test the service on teh command line. You can request your authentication key via curl on the command line. From the response get the part after "Auth=".

curl https://www.google.com/accounts/ClientLogin -d Email=your_user -d "Passwd=your_password" -d accountType=GOOGLE -d source=Google-cURL-Example -d service=ac2dm
   

This part and the registration code can be used to send a message to your device.

curl --header "Authorization: GoogleLogin auth=your_authenticationid" "https://android.apis.google.com/c2dm/send" -d registration_id=your_registration -d "data.payload=This data will be send to your application as payload" -d collapse_key=0

   

3.6. Create your server ;-) application

As described earlier the application server needs to get an authentication key via HTTPS. Afterwards it can send messages to the device via HTTP by supplying the authentication key and the registration ID.
To avoid that you have to learn about server side Java programming we will "simulate" the server by another Android application. In general you could create a server side application for example with the Google App Engine .
Create a new Android application "de.vogella.android.c2dm.server" with the Activity "ServerSimulator". Assign the permission "android.permission.INTERNET" to this activity.
Change "main.xml" to the following.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <Button android:text="Get Authentication" android:id="@+id/button1"
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:onClick="getAuthentification"></Button>
 <Button android:text="Show Authentication" android:id="@+id/button2"
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:onClick="showAuthentification"></Button>
 <EditText android:layout_width="match_parent" android:id="@+id/registrationId"
  android:hint="Registration Code" android:layout_height="wrap_content"></EditText>
 <Button android:text="Send Message" android:id="@+id/button3"
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:onClick="sendMessage"></Button>
</LinearLayout>

   

Change "ServerSimulator" to the following. Make your to use your registration key, your user and your password.

package de.vogella.android.c2dm.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

public class ServerSimulator extends Activity {
 private SharedPreferences prefManager;
 private final static String AUTH = "authentication";

 private static final String UPDATE_CLIENT_AUTH = "Update-Client-Auth";

 public static final String PARAM_REGISTRATION_ID = "registration_id";

 public static final String PARAM_DELAY_WHILE_IDLE = "delay_while_idle";

 public static final String PARAM_COLLAPSE_KEY = "collapse_key";

 private static final String UTF8 = "UTF-8";

 // Registration is currently hardcoded
 private final static String YOUR_REGISTRATION_STRING = "APA91bFQut1tqA-nIL1ZaV0emnp4Rb0smwCkrMHcoYRXeYVtIebJgrzOHQj0h76qKRzd3bC_JO37uJ0NgTcFO87HS9V7YC-yOP774pm0toppTHFO7Zc_PAw";

 private SharedPreferences prefs;

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  prefManager = PreferenceManager.getDefaultSharedPreferences(this);
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.mymenu, menu);
  return super.onCreateOptionsMenu(menu);
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
  case R.id.menuitem_user:
   Intent intent = new Intent(this, UserPreferences.class);
   startActivity(intent);
   break;
  default:
   break;
  }
  return false;
 }

 public void getAuthentification(View view) {
  SharedPreferences prefs = PreferenceManager
    .getDefaultSharedPreferences(this);

  HttpClient client = new DefaultHttpClient();
  HttpPost post = new HttpPost(
    "https://www.google.com/accounts/ClientLogin");

  try {

   List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
   nameValuePairs.add(new BasicNameValuePair("Email", prefs.getString(
     "user", "undefined")));
   nameValuePairs.add(new BasicNameValuePair("Passwd", prefs
     .getString("password", "undefined")));
   nameValuePairs.add(new BasicNameValuePair("accountType", "GOOGLE"));
   nameValuePairs.add(new BasicNameValuePair("source",
     "Google-cURL-Example"));
   nameValuePairs.add(new BasicNameValuePair("service", "ac2dm"));

   post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
   HttpResponse response = client.execute(post);
   BufferedReader rd = new BufferedReader(new InputStreamReader(
     response.getEntity().getContent()));

   String line = "";
   while ((line = rd.readLine()) != null) {
    Log.e("HttpResponse", line);
    if (line.startsWith("Auth=")) {
     Editor edit = prefManager.edit();
     edit.putString(AUTH, line.substring(5));
     edit.commit();
     String s = prefManager.getString(AUTH, "n/a");
     Toast.makeText(this, s, Toast.LENGTH_LONG).show();
    }

   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 public void showAuthentification(View view) {
  String s = prefManager.getString(AUTH, "n/a");
  Toast.makeText(this, s, Toast.LENGTH_LONG).show();
 }

 public void sendMessage(View view) {
  try {
   Log.e("Tag", "Started");
   String auth_key = prefManager.getString(AUTH, "n/a");
   // Send a sync message to this Android device.
   StringBuilder postDataBuilder = new StringBuilder();
   postDataBuilder.append(PARAM_REGISTRATION_ID).append("=")
     .append(YOUR_REGISTRATION_STRING);

   // if (delayWhileIdle) {
   // postDataBuilder.append("&").append(PARAM_DELAY_WHILE_IDLE)
   // .append("=1");
   // }
   postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=")
     .append("0");

   postDataBuilder.append("&").append("data.payload").append("=")
     .append(URLEncoder.encode("Lars war hier", UTF8));

   byte[] postData = postDataBuilder.toString().getBytes(UTF8);

   // Hit the dm URL.

   URL url = new URL("https://android.clients.google.com/c2dm/send");

   HttpURLConnection conn = (HttpURLConnection) url.openConnection();
   conn.setDoOutput(true);
   conn.setUseCaches(false);
   conn.setRequestMethod("POST");
   conn.setRequestProperty("Content-Type",
     "application/x-www-form-urlencoded;charset=UTF-8");
   conn.setRequestProperty("Content-Length",
     Integer.toString(postData.length));
   conn.setRequestProperty("Authorization", "GoogleLogin auth="
     + auth_key);

   OutputStream out = conn.getOutputStream();
   out.write(postData);
   out.close();

   int responseCode = conn.getResponseCode();

   Log.e("Tag", String.valueOf(responseCode));
   // Validate the response code

   if (responseCode == 401 || responseCode == 403) {
    // The token is too old - return false to retry later, will
    // fetch the token
    // from DB. This happens if the password is changed or token
    // expires. Either admin
    // is updating the token, or Update-Client-Auth was received by
    // another server,
    // and next retry will get the good one from database.
    Log.e("C2DM", "Unauthorized - need token");
   }

   // Check for updated token header
   String updatedAuthToken = conn.getHeaderField(UPDATE_CLIENT_AUTH);
   if (updatedAuthToken != null && !auth_key.equals(updatedAuthToken)) {
    Log.i("C2DM",
      "Got updated auth token from datamessaging servers: "
        + updatedAuthToken);
    Editor edit = prefManager.edit();
    edit.putString(AUTH, updatedAuthToken);
   }

   String responseLine = new BufferedReader(new InputStreamReader(
     conn.getInputStream())).readLine();

   // NOTE: You *MUST* use exponential backoff if you receive a 503
   // response code.
   // Since App Engine's task queue mechanism automatically does this
   // for tasks that
   // return non-success error codes, this is not explicitly
   // implemented here.
   // If we weren't using App Engine, we'd need to manually implement
   // this.
   if (responseLine == null || responseLine.equals("")) {
    Log.i("C2DM", "Got " + responseCode
      + " response from Google AC2DM endpoint.");
    throw new IOException(
      "Got empty response from Google AC2DM endpoint.");
   }

   String[] responseParts = responseLine.split("=", 2);
   if (responseParts.length != 2) {
    Log.e("C2DM", "Invalid message from google: " + responseCode
      + " " + responseLine);
    throw new IOException("Invalid response from Google "
      + responseCode + " " + responseLine);
   }

   if (responseParts[0].equals("id")) {
    Log.i("Tag", "Successfully sent data message to device: "
      + responseLine);
   }

   if (responseParts[0].equals("Error")) {
    String err = responseParts[1];
    Log.w("C2DM",
      "Got error response from Google datamessaging endpoint: "
        + err);
    // No retry.
    throw new IOException(err);
   }
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }
}
   

4. Thank you

Android Internals


Lars Vogel

Version 0.2
16.03.2011
Revision History
Revision 0.112.02.2011Lars Vogel
Created
Revision 0.216.03.2011Lars Vogel
updated and bugfixed
Android Internal
This articles looks at the internals of Androids and describes a little bit the architecture of Android.

1. Android Development

1.1. Android Operation System

Android is an operating system based on a modified Linux 2.6 with a Java programming interface. Also several drivers and libraries have been modified to allow Android to run efficient on mobile devices.
It provides tools, e.g. a compiler, debugger and a device emulator as well as its own Java Virtual machine (Dalvik Virtual Machine - DVM).Android is created by the Open Handset Alliance which is lead by Google.
Android uses a special virtual machine, e.g. the Dalvik Virtual Machine. Dalvik uses special bytecode. Therefore you cannot run standard Java bytecode on Android. Android provides a tool "dx" which allows to convert Java Class files into "dex" (Dalvik Executable) files. Android applications are packed into an .apk (Android Package) file by the program "aapt" (Android Asset Packaging Tool). To simplify development Google provides the Android Development Tools (ADT) for Eclipse . The ADT performs automatically the conversion from class to dex files and creates the apk during deployment.
For an introduction into Android development please see the Android Development Tutorial . The rest of this article tries to look inside the Android runtime system.

2. Internals

During startup of the Android system the Linux kernel first calls the process "init". init reads the files "/init.rc" and "init.device.rc". "init.device.rc" is device specific, on the virtual device this file is called "init.goldfish.rc".
init.rc starts the process "Zygote" via the program "/system/bin/app_process". Zygote loads the core Java classes and performs initial processing of them. These classes can be reused by Android application and therefore this step makes them faster to start. Once the initial work of Zygote is done, the process listens to a socket and waits for requests.
A special driver called "Binder" allow an efficient interprocess communications (IPC)) in which allow references to objects are passed between processes. The real objects are stored in Shared Memory. This way the communication between the processes is optimized as less data must be transferred.
Android does not provide a swap space like other Linux systems therefore the amound of available memory is limited to the memory on the device.
Androids uses a special C library "Bionic" instead of the standard Glibc. This library is not compatible with Glibs but requires less memory. Bionic contains a special thread implementation which optimizes the memory consumption of each thread and reduces the start time of new threads.
As of Android 2.3 the Android system uses Ext4, the standard file system for Linux. Before that it used the YAFFS file system. Some vendors replaces the standard file system by their own one.
As all application have their own Linux user application data can per default only be accesses from the application. To share data you cansave files which are accessible by other applications . Alternatively application can define their own content provider . Applications which are signed with the same certificate can also define in the "AndroidManifest.mf" a "android:sharedUserId". Application with a shared user id are running in the same process and with the same user id and can access files from similar applications.
The .apk file of installed applications can be found in the folder "data/app". Using the file manager or the adb pull / push command you can download the application or install it on a device. Payed applications or application from the market which have the option "Copy Protection" selected can be found in the folder "data/app-private" which the user cannot access per default.
To further protect an application from illegal copies you can use the library "Licence Verification Library" which checks for a paid application if it has been purchased from the Android Market.
The maximum size of Android application is currently typically 16 or 24 Megabyte. How large the application can be is defined at compile time of the Android system.

3. Thank you

Android Sensor - Tutorial


Lars Vogel

Version 0.2
14.06.2011
Revision History
Revision 0.105.01.2011Lars Vogel
Created
Revision 0.214.06.2011Lars Vogel
bugfixes and enhancements
Android Sensor
This tutorial describes how to use the Androids Sensor manager. It currently demonstrates the accelerometer. The tutorial is based on Eclipse 3.6, Java 1.6 and Android 2.3.3 (Gingerbread).

1. Android Sensors

Android supports several sensors via the SensorManager, for example the accelerometer. Unfortunately you cannot test the accelerometer on the Android emulator.
Once you get the ServiceManager via getSystemService(SENSOR_SERVICE) you can register a "SensorEventListener" to it. To avoid the unnecessary usage of battery you register your listener in the onResume method and de-register on the onPause method.

2. Example

Create a new Android project "de.vogella.android.sensor" with the activity "SensorTest".
Change your layout "main.xml" to the following.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <TextView android:id="@+id/textView" android:layout_width="match_parent"
  android:layout_height="match_parent" android:text="Shake to get a toast and to switch color" />
</LinearLayout>

  

Change the coding of your activity Activity.

package de.vogella.android.sensor;

import android.app.Activity;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

public class SensorTest extends Activity implements SensorEventListener {
 private SensorManager sensorManager;
 private boolean color = false; 
 private View view;
 private long lastUpdate;

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  requestWindowFeature(Window.FEATURE_NO_TITLE);
  getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
             WindowManager.LayoutParams.FLAG_FULLSCREEN);
   
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  view = findViewById(R.id.textView);
  view.setBackgroundColor(Color.GREEN);

  sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
  sensorManager.registerListener(this,
    sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
    SensorManager.SENSOR_DELAY_NORMAL);
  lastUpdate = System.currentTimeMillis();
 }

 @Override
 public void onSensorChanged(SensorEvent event) {
  if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
   float[] values = event.values;
   // Movement
   float x = values[0];
   float y = values[1];
   float z = values[2];

   float accelationSquareRoot = (x * x + y * y + z * z)
     / (SensorManager.GRAVITY_EARTH * SensorManager.GRAVITY_EARTH);
   long actualTime = System.currentTimeMillis();
   if (accelationSquareRoot >= 2) //
   {
    if (actualTime - lastUpdate < 200) {
     return;
    }
    lastUpdate = actualTime;
    Toast.makeText(this, "Device was shuffed", Toast.LENGTH_SHORT)
      .show();
    if (color) {
     view.setBackgroundColor(Color.GREEN);
     
    } else {
     view.setBackgroundColor(Color.RED);
    }
    color = !color;
   }

  }

 }

 @Override
 public void onAccuracyChanged(Sensor sensor, int accuracy) {
  // TODO Auto-generated method stub

 }

 @Override
 protected void onResume() {
  super.onResume();
  // register this class as a listener for the orientation and
  // accelerometer sensors
  sensorManager.registerListener(this,
    sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
    SensorManager.SENSOR_DELAY_NORMAL);
 }

 @Override
 protected void onPause() {
  // unregister listener
  sensorManager.unregisterListener(this);
  super.onStop();
 }
}
  

3. Thank you