/* * Copyright (C) 2012 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.example.google.tv.anymotelibrary.connection; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log; import com.example.google.tv.anymotelibrary.client.AnymoteSender; /** * This class manages the requests for acknowledgments that are sent to the * Anymote server to monitor the connection state. */ public final class AckManager { /** * The tag used for debug logging. */ private static final String LOG_TAG = "AckManager"; /** * The flag which controls whether debug output should be generated. */ private static final boolean DEBUG = false; /** * The listener which listens for lost connection events. */ private final Listener connectionListener; /** * Inner class that handles start / stop / ping / ack / timeout messages. */ private final AckHandler handler; /** * The proxy for sending Anymote events. */ private final AnymoteSender sender; /** * Interface used when the connection is lost. */ public interface Listener { /** * Called on connection timeout. */ public void onTimeout(); } /** * Constructor. * * @param listener Listens for lost connection events. * @param sender Sends Anymote events to server. */ public AckManager(final Listener listener, final 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.sendEmptyMessage(Action.ACK.ordinal()); } /** * Starts monitoring connection to Anymote server. */ public void start() { handler.sendEmptyMessage(Action.START.ordinal()); } /** * Stops monitoring connection to Anymote server. */ public void stop() { handler.removeMessages(Action.PING, Action.START); } /** * Stop the ACK handling thread. */ public void quit() { handler.getLooper().quit(); } /** * Notifies the listener that the connection to Anymote server has been * lost. */ private void connectionTimeout() { connectionListener.onTimeout(); } /** * Enum defining action for messages sent to AckHandler. */ private enum Action { START, PING, ACK, } /** * Inner class that handles start / stop / ping / ack / timeout messages * from multiple threads, and serializes their execution. */ private final class AckHandler extends Handler { /** * Duration between two ack requests. */ private static final int PING_PERIOD = 3 * 1000; private int lostAcks; /** * Max number of missing requests in a row that indicade conneciton lost * this is more robust and only fails if server stops responding */ private static final int MAX_LOST_ACKS = 3; AckHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { Action action = actionValueOf(msg.what); if (DEBUG) { Log.d(LOG_TAG, "action=" + action + " : msg=" + msg + " @ " + System.currentTimeMillis()); } switch (action) { case START: handleStart(); break; case PING: handlePing(); break; case ACK: handleAck(); break; } } private void handlePing() { sender.sendPing(); sendMessageDelayed(obtainMessage(Action.PING), PING_PERIOD); ++lostAcks; if (lostAcks > MAX_LOST_ACKS) { handleTimeout(); } } private void handleStart() { lostAcks = 0; handlePing(); } private void handleTimeout() { removeMessages(Action.PING, Action.ACK); connectionTimeout(); } private void handleAck() { lostAcks = 0; } private void removeMessages(Action... actions) { for (Action action : actions) { removeMessages(action.ordinal()); } } private Message obtainMessage(Action action) { return obtainMessage(action.ordinal()); } private Action actionValueOf(int what) { return Action.values()[what]; } } }