AndroidC2DMDemo
com.google.android.c2dm
C2DMBroadcastReceiver
/*
*/
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 */);
}
}
C2DMBaseReceiver
/*
* 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());
}
}
}
}
C2DMessaging
/*
* 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 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();
}
}
org.wp.activity
AndroidC2DMDemo
package org.wp.activity;
import com.google.android.c2dm.C2DMessaging;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class AndroidC2DMDemo extends Activity {
private static final String TAG = "AndroidC2DMDemo";
public static final String SENDER_ID = "wp.android.c2dm.demo@gmail.com";
public static final String MESSAGE_KEY = "onewayonelife";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.i(TAG, "start");
C2DMessaging.register(this, SENDER_ID);
}
}
C2DMReceiver
package org.wp.activity;
import java.io.IOException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
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 {
private static final String TAG = "C2DMReceiver";
public C2DMReceiver() {
super(AndroidC2DMDemo.SENDER_ID);
}
@Override
public void onRegistered(Context context, String registrationId)
throws IOException {
Log.i(TAG, "registrationId:" + registrationId);
}
@Override
public void onUnregistered(Context context) {
}
@Override
protected void onMessage(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
String msg = extras.getString(AndroidC2DMDemo.MESSAGE_KEY);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, AndroidC2DMDemo.class), 0);
Notification notification = new Notification(R.drawable.icon, msg, System.currentTimeMillis());
notification.setLatestEventInfo(this, getString(R.string.app_name), msg, contentIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(0, notification);
}
}
@Override
public void onError(Context context, String errorId) {
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.wp.activity" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".AndroidC2DMDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- In order to use the c2dm library, an application must declare a class with the name C2DMReceiver, in its own package, extending com.google.android.c2dm.C2DMBaseReceiver It must also include this section in the manifest. --> <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="org.wp.activity" /> </intent-filter> <!-- Receive the registration id --> <intent-filter> <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> <category android:name="org.wp.activity" /> </intent-filter> </receiver> </application> <uses-sdk android:minSdkVersion="8" /> <!-- Only this application can receive the messages and registration result --> <permission android:name="org.wp.activity.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="org.wp.activity.permission.C2D_MESSAGE" /> <!-- This app has permission to register and receive message --> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <!-- Send the registration id to the server --> <uses-permission android:name="android.permission.INTERNET" /> <!-- App must have this permission to use the library --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" /> </manifest>
AndroidC2DMServerDemo
AndroidC2DMServer
package org.wp.activity;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class AndroidC2DMServer {
private static String clientLoginUrl = "https://www.google.com/accounts/ClientLogin";
// 因为使用https的方式会提示出错,因此使用http的方式
private static String pushUrl = "http://android.apis.google.com/c2dm/send";
private static String registrationId = "APA91bHeWVX8t0hE-mhks0539soI9JaNFcU1r0_BEtDShnMy0lDDF3U2nR5NxC8F18y_oqj3nbzE3Pn9PpcilYRsK09lnsAaFhRcVCfS0zaztHt6BRXveHey73ipRBeJzpdd9xegxwsQih1B-Dr9Pj903tiorIZbcQ";
public static void main(String args[]) {
// 获取注册使用C2DM服务的用户账号的ClientLogin权限Auth值
String auth = getAuthToken(clientLoginUrl, getClientLoginParams());
if (auth != null && !"".equals(auth)) {
// 按格式给C2DM服务器发送要Push的数据
Map<String, String> data = new HashMap<String, String>();
data.put("onewayonelife", "onewayonelife!" + new Date());
sendPushMessage(pushUrl, getPushParams(registrationId, "wp", data, true), auth);
}
}
public static String getClientLoginParams() {
StringBuilder clsb = new StringBuilder();
/** accountType 请求授权的帐户类型 **/
clsb.append("accountType=HOSTED_OR_GOOGLE");
/** Email 邮箱账号 **/
clsb.append("&Email=wp.android.c2dm.demo@gmail.com");
/** Passwd 邮箱账号密码 **/
clsb.append("&Passwd=wpandroidc2dmdemo");
/** service 请求授权的服务名称,C2DM服务的值为ac2dm **/
clsb.append("&service=ac2dm");
/** source 用来表示我们应用的字符串,类似companyName-applicationName-versionID **/
clsb.append("&source=wp-c2dmdemo-1.0");
return clsb.toString();
}
public static String getAuthToken(final String url, final String params) {
String auth = null;
try {
byte[] postData = params.getBytes();
URL requestUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection();
// 上传数据,需要将setDoOutput方法的参数值设为true
connection.setDoOutput(true);
// Post请求不能使用缓存
connection.setUseCaches(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", Integer.toString(postData.length));
connection.setRequestProperty("Charset", "UTF-8");
OutputStream out = connection.getOutputStream();
// 写入POST数据
out.write(postData);
out.flush();
out.close();
// 请求成功
if (connection.getResponseCode() == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String responseLine;
StringBuilder sb = new StringBuilder();
while ((responseLine = br.readLine()) != null) {
sb.append(responseLine);
}
auth = sb.substring(sb.indexOf("Auth=") + 5);
}
} catch (Exception e) {
e.printStackTrace();
}
return auth;
}
public static String getPushParams(String registration_id, String collapse_key,
Map<String, String> data, boolean delay_while_idle) {
StringBuilder phsb = new StringBuilder();
/** registration_id Android应用程序注册获得的id **/
phsb.append("registration_id=" + registration_id);
/** collapse_key 用一个任意的字符串来标识一组类似的信息
* 当设备由离线状态重新上线以后,只有最后一条消息被发送到客户端
* 这是为了避免当设备重新上线后有太多的信息发送到手机 **/
phsb.append("&collapse_key=" + collapse_key);
/** data.<key> 推送的信息,以键值对形式保存
* 将会被包含在intent当中发送到android应用程序
* 对于个数上并没有限制,只是在数据总大小上有要求 **/
for (String key : data.keySet()) {
phsb.append("&data." + key + "=" + data.get(key));
}
/** delay_while_idle 如果包含这个值
* 表示不会在设备处于闲置状态的时候就立即发送推送消息
* 服务器会等待设备变为活动状态的时候发送
* 并且只有collapse_key标识的最后一条信息才会被发送 **/
if (delay_while_idle)
phsb.append("&delay_while_idle=0");
return phsb.toString();
}
public static void sendPushMessage(final String url, final String params, final String auth) {
try {
byte[] postData = params.getBytes();
URL requestUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection();
// 上传数据,需要将setDoOutput方法的参数值设为true
connection.setDoOutput(true);
// Post请求不能使用缓存
connection.setUseCaches(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", Integer.toString(postData.length));
connection.setRequestProperty("Charset", "UTF-8");
connection.setRequestProperty("Authorization", "GoogleLogin auth=" + auth);
OutputStream out = connection.getOutputStream();
out.write(postData);
out.flush();
out.close();
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String responseLine;
StringBuilder sb = new StringBuilder();
while ((responseLine = br.readLine()) != null) {
sb.append(responseLine);
}
if (sb.toString().startsWith("id=")) {
System.out.println("推送信息发送成功!");
}
} else if (responseCode == 503) {
System.out.println("Indicates that the server is temporarily unavailable (i.e., because of timeouts, etc ). Sender must retry later, honoring any Retry-After header included in the response. Application servers must implement exponential back off. Senders that create problems risk being blacklisted.");
} else if (responseCode == 401) {
System.out.println("Indicates that the ClientLogin AUTH_TOKEN used to validate the sender is invalid.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}