/* * Copyright (C) 2009 Google Inc. All rights reserved. * * 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.apps.tvremote.protocol; import java.util.concurrent.atomic.AtomicInteger; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; /** * This class manages the requests for acknowledgments that are sent to the * server to monitor the connection and the replies from it. * */ public final class AckManager { private static final String LOG_TAG = "AckManager"; private static final boolean DEBUG = false; /** * Duration between two ack requests. */ private static final long PONG_PERIOD = 3 * 1000; /** * Timeout before the connection is declared lost. */ private static final long PING_TIMEOUT = 500; /** * Interface used when the connection is lost. */ public interface Listener { /** * Called when the connection is considered lost, if no acknowledgment * message has been received after {@link #PING_TIMEOUT}. */ public void onTimeout(); } private final Listener connectionListener; private final AckHandler handler; private final AnymoteSender sender; public AckManager(Listener listener, AnymoteSender sender) { HandlerThread handlerThread = new HandlerThread("AckHandlerThread"); handlerThread.start(); handler = new AckHandler(handlerThread.getLooper()); connectionListener = listener; this.sender = sender; } /** * Notifies the AckManager that a acknowledgment message has been received. */ public void onAck() { handler.sendEmptyMessageAndIncrement(Action.ACK); } public void start() { handler.sendEmptyMessageAndIncrement(Action.START); } public void cancel() { handler.getLooper().quit(); } /** * Notifies the listener that the connection has been lost. */ private void connectionTimeout() { connectionListener.onTimeout(); } private enum Action { START, PING, ACK, TIMEOUT, } /** * Inner class that handles start / stop / pong / ack / timeout messages * from multiple threads, and serializes their execution. */ private final class AckHandler extends Handler { /** * The current sequence number. */ private final AtomicInteger sequence; AckHandler(Looper looper) { super(looper); sequence = new AtomicInteger(); } @Override public void handleMessage(Message msg) { Action action = actionValueOf(msg.what); if (DEBUG) { Log.d(LOG_TAG, "action=" + action + " : msg=" + msg + " : seq=" + sequence.get() + " @ " + System.currentTimeMillis()); } switch (action) { case START: handleStart(); break; case PING: handlePing(); break; case ACK: handleAck(); break; case TIMEOUT: handleTimeout(msg.arg1); break; } } private void handlePing() { int token = sequence.incrementAndGet(); removeMessages(Action.ACK, Action.TIMEOUT); sender.ping(); sendMessageDelayed(obtainMessage(Action.TIMEOUT, token), PING_TIMEOUT); } private void handleStart() { sequence.incrementAndGet(); removeMessages(Action.TIMEOUT, Action.PING, Action.ACK); handlePing(); } private void handleAck() { sequence.incrementAndGet(); removeMessages(Action.TIMEOUT); sendMessageDelayed(obtainMessage(Action.PING), PONG_PERIOD); } private void handleTimeout(int token) { removeMessages(Action.PING, Action.ACK); if (sequence.compareAndSet(token, token + 1)) { connectionTimeout(); } sequence.incrementAndGet(); } private void removeMessages(Action... actions) { for (Action action : actions) { removeMessages(action.ordinal()); } } private Message obtainMessage(Action action) { return obtainMessage(action.ordinal()); } private Message obtainMessage(Action action, int arg1) { return obtainMessage(action.ordinal(), arg1, 0); } private Action actionValueOf(int what) { return Action.values()[what]; } void sendEmptyMessageAndIncrement(Action action) { sequence.incrementAndGet(); sendEmptyMessage(action.ordinal()); } } }