/* * 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.ticl.android; 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.InvalidationService; import com.google.ipc.invalidation.external.client.android.service.ListenerService; import com.google.ipc.invalidation.external.client.android.service.Request; import com.google.ipc.invalidation.external.client.android.service.Request.Action; import com.google.ipc.invalidation.external.client.android.service.Response; import com.google.ipc.invalidation.external.client.android.service.Response.Status; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; /** * Abstract base class for implementing the Android invalidation service. The service implements the * set of actions defined in {@link Action}. For each supported action, the service will extract the * action parameters and invoke an abstract methods that will be implemented by subclasses to * provide the action-specific processing. * <p> * This class acquires a lock before calling into the subclass and releases it after the call. * It also ensures that no call into the subclass will be made after the service has been destroyed. * <p> * The class also provides {@code sendEvent} methods that can be used to generate events back to the * client. * */ public abstract class AbstractInvalidationService extends Service { private static final Logger logger = AndroidLogger.forTag("InvService"); /** * Simple service stub that delegates back to methods on the service. */ private final InvalidationService.Stub serviceBinder = new InvalidationService.Stub() { @Override public void handleRequest(Bundle input, Bundle output) { AbstractInvalidationService.this.handleRequest(input, output); } }; /** Lock over all state in this class. */ 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 int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return serviceBinder; } /** Returns whether the service is started. */ boolean isCreatedForTest() { synchronized (lock) { return isCreated; } } protected void handleRequest(Bundle input, Bundle output) { synchronized (lock) { if (!isCreated) { logger.warning("Dropping bundle since not created: %s", input); return; } Request request = new Request(input); Response.Builder response = Response.newBuilder(request.getActionOrdinal(), output); Action action = request.getAction(); logger.fine("%s request from %s", action, request.getClientKey()); try { switch(action) { case CREATE: create(request, response); break; case RESUME: resume(request, response); break; case START: start(request, response); break; case STOP: stop(request, response); break; case REGISTER: register(request, response); break; case UNREGISTER: unregister(request, response); break; case ACKNOWLEDGE: acknowledge(request, response); break; case DESTROY: destroy(request, response); break; default: throw new IllegalStateException("Unknown action:" + action); } } catch (Exception e) { logger.severe("Client request error", e); response.setStatus(Status.RUNTIME_ERROR); // Subclass might already have set status. response.setException(e); } } } protected abstract void create(Request request, Response.Builder response); protected abstract void resume(Request request, Response.Builder response); protected abstract void start(Request request, Response.Builder response); protected abstract void stop(Request request, Response.Builder response); protected abstract void register(Request request, Response.Builder response); protected abstract void unregister(Request request, Response.Builder response); protected abstract void acknowledge(Request request, Response.Builder response); protected abstract void destroy(Request request, Response.Builder response); /** * Send event messages to application clients and provides common processing * of the response. */ protected void sendEvent(ListenerService listenerService, Event event) { try { logger.fine("Sending %s event", event.getAction()); Bundle responseBundle = new Bundle(); listenerService.handleEvent(event.getBundle(), responseBundle); // Wrap the response bundle and throw on any failure from the client Response response = new Response(responseBundle); response.warnOnFailure(); } catch (RemoteException exception) { logger.severe("Unable to send event", exception); throw new RuntimeException("Unable to send event", exception); } } }