Skip to content

Commit

Permalink
Issue phonegap#211: added flag FLAG_INCLUDE_STOPPED_PACKAGES
Browse files Browse the repository at this point in the history
  • Loading branch information
vikasing committed Oct 13, 2015
1 parent bdbc198 commit 0b3719f
Show file tree
Hide file tree
Showing 4 changed files with 1,119 additions and 0 deletions.
347 changes: 347 additions & 0 deletions src/android/com/google/android/gcm/GCMBaseIntentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,347 @@
/*
* Copyright 2012 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.gcm;

import static com.google.android.gcm.GCMConstants.ERROR_SERVICE_NOT_AVAILABLE;
import static com.google.android.gcm.GCMConstants.EXTRA_ERROR;
import static com.google.android.gcm.GCMConstants.EXTRA_REGISTRATION_ID;
import static com.google.android.gcm.GCMConstants.EXTRA_SPECIAL_MESSAGE;
import static com.google.android.gcm.GCMConstants.EXTRA_TOTAL_DELETED;
import static com.google.android.gcm.GCMConstants.EXTRA_UNREGISTERED;
import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY;
import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_MESSAGE;
import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_REGISTRATION_CALLBACK;
import static com.google.android.gcm.GCMConstants.VALUE_DELETED_MESSAGES;

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.os.SystemClock;
import android.util.Log;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
* Skeleton for application-specific {@link IntentService}s responsible for
* handling communication from Google Cloud Messaging service.
* <p>
* The abstract methods in this class are called from its worker thread, and
* hence should run in a limited amount of time. If they execute long
* operations, they should spawn new threads, otherwise the worker thread will
* be blocked.
* <p>
* Subclasses must provide a public no-arg constructor.
*/
public abstract class GCMBaseIntentService extends IntentService {

public static final String TAG = "GCMBaseIntentService";

// wakelock
private static final String WAKELOCK_KEY = "GCM_LIB";
private static PowerManager.WakeLock sWakeLock;

// Java lock used to synchronize access to sWakelock
private static final Object LOCK = GCMBaseIntentService.class;

private final String[] mSenderIds;

// instance counter
private static int sCounter = 0;

private static final Random sRandom = new Random();

private static final int MAX_BACKOFF_MS =
(int) TimeUnit.SECONDS.toMillis(3600); // 1 hour

// token used to check intent origin
private static final String TOKEN =
Long.toBinaryString(sRandom.nextLong());
private static final String EXTRA_TOKEN = "token";

/**
* Constructor that does not set a sender id, useful when the sender id
* is context-specific.
* <p>
* When using this constructor, the subclass <strong>must</strong>
* override {@link #getSenderIds(Context)}, otherwise methods such as
* {@link #onHandleIntent(Intent)} will throw an
* {@link IllegalStateException} on runtime.
*/
protected GCMBaseIntentService() {
this(getName("DynamicSenderIds"), null);
}

/**
* Constructor used when the sender id(s) is fixed.
*/
protected GCMBaseIntentService(String... senderIds) {
this(getName(senderIds), senderIds);
}

private GCMBaseIntentService(String name, String[] senderIds) {
super(name); // name is used as base name for threads, etc.
mSenderIds = senderIds;
}

private static String getName(String senderId) {
String name = "GCMIntentService-" + senderId + "-" + (++sCounter);
Log.v(TAG, "Intent service name: " + name);
return name;
}

private static String getName(String[] senderIds) {
String flatSenderIds = GCMRegistrar.getFlatSenderIds(senderIds);
return getName(flatSenderIds);
}

/**
* Gets the sender ids.
*
* <p>By default, it returns the sender ids passed in the constructor, but
* it could be overridden to provide a dynamic sender id.
*
* @throws IllegalStateException if sender id was not set on constructor.
*/
protected String[] getSenderIds(Context context) {
if (mSenderIds == null) {
throw new IllegalStateException("sender id not set on constructor");
}
return mSenderIds;
}

/**
* Called when a cloud message has been received.
*
* @param context application's context.
* @param intent intent containing the message payload as extras.
*/
protected abstract void onMessage(Context context, Intent intent);

/**
* Called when the GCM server tells pending messages have been deleted
* because the device was idle.
*
* @param context application's context.
* @param total total number of collapsed messages
*/
protected void onDeletedMessages(Context context, int total) {
}

/**
* Called on a registration error that could be retried.
*
* <p>By default, it does nothing and returns {@literal true}, but could be
* overridden to change that behavior and/or display the error.
*
* @param context application's context.
* @param errorId error id returned by the GCM service.
*
* @return if {@literal true}, failed operation will be retried (using
* exponential backoff).
*/
protected boolean onRecoverableError(Context context, String errorId) {
return true;
}

/**
* Called on registration or unregistration error.
*
* @param context application's context.
* @param errorId error id returned by the GCM service.
*/
protected abstract void onError(Context context, String errorId);

/**
* Called after a device has been registered.
*
* @param context application's context.
* @param registrationId the registration id returned by the GCM service.
*/
protected abstract void onRegistered(Context context,
String registrationId);

/**
* Called after a device has been unregistered.
*
* @param registrationId the registration id that was previously registered.
* @param context application's context.
*/
protected abstract void onUnregistered(Context context,
String registrationId);

@Override
public final void onHandleIntent(Intent intent) {
try {
Context context = getApplicationContext();
String action = intent.getAction();
switch (action) {
case INTENT_FROM_GCM_REGISTRATION_CALLBACK:
GCMRegistrar.setRetryBroadcastReceiver(context);
handleRegistration(context, intent);
break;
case INTENT_FROM_GCM_MESSAGE:
// checks for special messages
String messageType =
intent.getStringExtra(EXTRA_SPECIAL_MESSAGE);
if (messageType != null) {
if (messageType.equals(VALUE_DELETED_MESSAGES)) {
String sTotal =
intent.getStringExtra(EXTRA_TOTAL_DELETED);
if (sTotal != null) {
try {
int total = Integer.parseInt(sTotal);
Log.v(TAG, "Received deleted messages " +
"notification: " + total);
onDeletedMessages(context, total);
} catch (NumberFormatException e) {
Log.e(TAG, "GCM returned invalid number of " +
"deleted messages: " + sTotal);
}
}
} else {
// application is not using the latest GCM library
Log.e(TAG, "Received unknown special message: " +
messageType);
}
} else {
onMessage(context, intent);
}
break;
case INTENT_FROM_GCM_LIBRARY_RETRY:
String token = intent.getStringExtra(EXTRA_TOKEN);
if (!TOKEN.equals(token)) {
// make sure intent was generated by this class, not by a
// malicious app.
Log.e(TAG, "Received invalid token: " + token);
return;
}
// retry last call
if (GCMRegistrar.isRegistered(context)) {
GCMRegistrar.internalUnregister(context);
} else {
String[] senderIds = getSenderIds(context);
GCMRegistrar.internalRegister(context, senderIds);
}
break;
}
} 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 onMessage() needs to spawn a thread or do something else,
// it should use its own lock.
synchronized (LOCK) {
// sanity check for null as this is a public method
if (sWakeLock != null) {
sWakeLock.release();
} else {
// should never happen during normal workflow
Log.e(TAG, "Wakelock reference is null");
}
}
}
}

/**
* Called from the broadcast receiver.
* <p>
* 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,
String className) {
synchronized (LOCK) {
if (sWakeLock == null) {
// This is called from BroadcastReceiver, there is no init.
PowerManager pm = (PowerManager)
context.getSystemService(Context.POWER_SERVICE);
sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WAKELOCK_KEY);
}
}
sWakeLock.acquire();
intent.setClassName(context, className);
context.startService(intent);
}

private void handleRegistration(final Context context, Intent intent) {
GCMRegistrar.cancelAppPendingIntent();
String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
String error = intent.getStringExtra(EXTRA_ERROR);
String unregistered = intent.getStringExtra(EXTRA_UNREGISTERED);
Log.d(TAG, "handleRegistration: registrationId = " + registrationId +
", error = " + error + ", unregistered = " + unregistered);

// registration succeeded
if (registrationId != null) {
GCMRegistrar.resetBackoff(context);
GCMRegistrar.setRegistrationId(context, registrationId);
onRegistered(context, registrationId);
return;
}

// unregistration succeeded
if (unregistered != null) {
// Remember we are unregistered
GCMRegistrar.resetBackoff(context);
String oldRegistrationId =
GCMRegistrar.clearRegistrationId(context);
onUnregistered(context, oldRegistrationId);
return;
}

// last operation (registration or unregistration) returned an error;
Log.d(TAG, "Registration error: " + error);
// Registration failed
if (ERROR_SERVICE_NOT_AVAILABLE.equals(error)) {
boolean retry = onRecoverableError(context, error);
if (retry) {
int backoffTimeMs = GCMRegistrar.getBackoff(context);
int nextAttempt = backoffTimeMs / 2 +
sRandom.nextInt(backoffTimeMs);
Log.d(TAG, "Scheduling registration retry, backoff = " +
nextAttempt + " (" + backoffTimeMs + ")");
Intent retryIntent =
new Intent(INTENT_FROM_GCM_LIBRARY_RETRY);
retryIntent.putExtra(EXTRA_TOKEN, TOKEN);
PendingIntent retryPendingIntent = PendingIntent
.getBroadcast(context, 0, retryIntent, 0);
AlarmManager am = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + nextAttempt,
retryPendingIntent);
// Next retry should wait longer.
if (backoffTimeMs < MAX_BACKOFF_MS) {
GCMRegistrar.setBackoff(context, backoffTimeMs * 2);
}
} else {
Log.d(TAG, "Not retrying failed operation");
}
} else {
// Unrecoverable error, notify app
onError(context, error);
}
}

}
Loading

0 comments on commit 0b3719f

Please sign in to comment.