/*
* Copyright 2011 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.ipc.invalidation.external.client.android;
import com.google.ipc.invalidation.external.client.InvalidationListener;
import com.google.ipc.invalidation.external.client.SystemResources.Logger;
import com.google.ipc.invalidation.external.client.android.service.AndroidLogger;
import com.google.ipc.invalidation.external.client.android.service.Event;
import com.google.ipc.invalidation.external.client.android.service.Event.Action;
import com.google.ipc.invalidation.external.client.android.service.ListenerService;
import com.google.ipc.invalidation.external.client.android.service.Response;
import com.google.ipc.invalidation.external.client.types.AckHandle;
import com.google.ipc.invalidation.external.client.types.ErrorInfo;
import com.google.ipc.invalidation.external.client.types.Invalidation;
import com.google.ipc.invalidation.external.client.types.ObjectId;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
/**
* An abstract base class for implementing a {@link Service} component
* that handles events from the invalidation service. This class should be
* subclassed and concrete implementations of the {@link InvalidationListener}
* methods added to provide application-specific handling of invalidation
* events.
* <p>
* This implementing subclass should be registered in {@code
* AndroidManifest.xml} as a service of the invalidation
* listener binding intent, as in the following sample fragment:
*
* <pre>
* {@code
* <manifest ...>
* <application ...>
* ...
* service android:name="com.myco.example.AppListenerService" ...>
* <intent-filter>
* <action android:name="com.google.ipc.invalidation.LISTENER"/>
* </intent-filter>
* </receiver>
* ...
* <application>
* ...
* </manifest>
* }
* </pre>
*
*/
public abstract class AndroidInvalidationListener extends Service
implements InvalidationListener {
/** Logger */
private static final Logger logger = AndroidLogger.forTag("InvListener");
/**
* Simple service stub that delegates back to methods on the service.
*/
private final ListenerService.Stub listenerBinder = new ListenerService.Stub() {
@Override
public void handleEvent(Bundle input, Bundle output) {
AndroidInvalidationListener.this.handleEvent(input, output);
}
};
/** Lock over all state in this class. */
private final Object lock = new Object();
/** Whether the service is in the created state. */
private boolean isCreated = false;
@Override
public void onCreate() {
synchronized (lock) {
super.onCreate();
logger.fine("onCreate: %s", this.getClass());
this.isCreated = true;
}
}
@Override
public void onDestroy() {
synchronized (lock) {
logger.fine("onDestroy: %s", this.getClass());
this.isCreated = false;
super.onDestroy();
}
}
@Override
public IBinder onBind(Intent arg0) {
synchronized (lock) {
logger.fine("Binding: %s", arg0);
return listenerBinder;
}
}
/**
* Handles a {@link ListenerService#handleEvent} call received by the
* listener service.
*
* @param input bundle containing event parameters.
* @param output bundled used to return response to the invalidation service.
*/
protected void handleEvent(Bundle input, Bundle output) {
synchronized (lock) {
if (!isCreated) {
logger.warning("Dropping bundle since not created: %s", input);
return;
}
Event event = new Event(input);
Response.Builder response = Response.newBuilder(event.getActionOrdinal(), output);
// All events should contain an action and client id
Action action = event.getAction();
String clientKey = event.getClientKey();
logger.fine("Received %s event for %s", action, clientKey);
AndroidInvalidationClient client = null;
try {
if (clientKey == null) {
throw new IllegalStateException("Missing client id:" + event);
}
// Obtain the client instance for the client receiving the event. Do not attempt to load it
// at the service: if a Ticl has been unloaded, the listener shouldn't resurrect it, because
// that can lead to a zombie client.
client = AndroidClientFactory.resume(this, clientKey, false);
// Determine the event type based upon the request action, extract parameters
// from extras, and invoke the listener event handler method.
logger.fine("%s event for %s", action, clientKey);
switch(action) {
case READY:
{
ready(client);
break;
}
case INVALIDATE:
{
Invalidation invalidation = event.getInvalidation();
AckHandle ackHandle = event.getAckHandle();
invalidate(client, invalidation, ackHandle);
break;
}
case INVALIDATE_UNKNOWN:
{
ObjectId objectId = event.getObjectId();
AckHandle ackHandle = event.getAckHandle();
invalidateUnknownVersion(client, objectId, ackHandle);
break;
}
case INVALIDATE_ALL:
{
AckHandle ackHandle = event.getAckHandle();
invalidateAll(client, ackHandle);
break;
}
case INFORM_REGISTRATION_STATUS:
{
ObjectId objectId = event.getObjectId();
RegistrationState state = event.getRegistrationState();
informRegistrationStatus(client, objectId, state);
break;
}
case INFORM_REGISTRATION_FAILURE:
{
ObjectId objectId = event.getObjectId();
String errorMsg = event.getError();
boolean isTransient = event.getIsTransient();
informRegistrationFailure(client, objectId, isTransient, errorMsg);
break;
}
case REISSUE_REGISTRATIONS:
{
byte[] prefix = event.getPrefix();
int prefixLength = event.getPrefixLength();
reissueRegistrations(client, prefix, prefixLength);
break;
}
case INFORM_ERROR:
{
ErrorInfo errorInfo = event.getErrorInfo();
informError(client, errorInfo);
break;
}
default:
logger.warning("Urecognized event: %s", event);
}
response.setStatus(Response.Status.SUCCESS);
} catch (RuntimeException re) {
// If an exception occurs during processing, log it, and store the
// result in the response sent back to the service.
logger.severe("Failure in handleEvent", re);
response.setError(re.getMessage());
} finally {
// Listeners will only use a client reference for the life of the event and release
// it immediately since there is no way to know if additional events are coming.
if (client != null) {
client.release();
}
}
}
}
}