/**
* Copyright (C) 2010 The Android Open Source Project
*
* 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.android.internal.util;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Slog;
import java.util.Stack;
/**
*
An asynchronous channel between two handlers.
*
* The handlers maybe in the same process or in another process. There
* are two protocol styles that can be used with an AysncChannel. The
* first is a simple request/reply protocol where the server does
* not need to know which client is issuing the request.
*
* In a simple request/reply protocol the client/source sends requests to the
* server/destination. And the server uses the replyToMessage methods.
* In this usage model there is no need for the destination to
* use the connect methods. The typical sequence of operations is:
*
* - Client calls AsyncChannel#connectSync or Asynchronously:
* For an asynchronous half connection client calls AsyncChannel#connect.
* - Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel
*
* comm-loop:
* Client calls AsyncChannel#sendMessage
* Server processes messages and optionally replies using AsyncChannel#replyToMessage
* Loop to comm-loop
until done
* When done Client calls {@link AsyncChannel#disconnect}
* Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel
*
*
* A second usage model is where the server/destination needs to know
* which client it's connected too. For example the server needs to
* send unsolicited messages back to the client. Or the server keeps
* different state for each client. In this model the server will also
* use the connect methods. The typical sequence of operation is:
*
* - Client calls AsyncChannel#fullyConnectSync or Asynchronously:
-
*
For an asynchronous full connection it calls AsyncChannel#connect
* - Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel
* - Client calls AsyncChannel#sendMessage(CMD_CHANNEL_FULL_CONNECTION)
*
* Server receives CMD_CHANNEL_FULL_CONNECTION
* Server calls AsyncChannel#connected
* Server sends AsyncChannel#sendMessage(CMD_CHANNEL_FULLY_CONNECTED)
* Client receives CMD_CHANNEL_FULLY_CONNECTED
* comm-loop:
* Client/Server uses AsyncChannel#sendMessage/replyToMessage
* to communicate and perform work
* Loop to comm-loop
until done
* When done Client/Server calls {@link AsyncChannel#disconnect}
* Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel
*
*
* TODO: Consider simplifying where we have connect and fullyConnect with only one response
* message RSP_CHANNEL_CONNECT instead of two, CMD_CHANNEL_HALF_CONNECTED and
* CMD_CHANNEL_FULLY_CONNECTED. We'd also change CMD_CHANNEL_FULL_CONNECTION to REQ_CHANNEL_CONNECT.
*/
public class AsyncChannel {
/** Log tag */
private static final String TAG = "AsyncChannel";
/** Enable to turn on debugging */
private static final boolean DBG = false;
private static final int BASE = Protocol.BASE_SYSTEM_ASYNC_CHANNEL;
/**
* Command sent when the channel is half connected. Half connected
* means that the channel can be used to send commends to the destination
* but the destination is unaware that the channel exists. The first
* command sent to the destination is typically CMD_CHANNEL_FULL_CONNECTION if
* it is desired to establish a long term connection, but any command maybe
* sent.
*
* msg.arg1 == 0 : STATUS_SUCCESSFUL
* 1 : STATUS_BINDING_UNSUCCESSFUL
* msg.obj == the AsyncChannel
* msg.replyTo == dstMessenger if successful
*/
public static final int CMD_CHANNEL_HALF_CONNECTED = BASE + 0;
/**
* Command typically sent when after receiving the CMD_CHANNEL_HALF_CONNECTED.
* This is used to initiate a long term connection with the destination and
* typically the destination will reply with CMD_CHANNEL_FULLY_CONNECTED.
*
* msg.replyTo = srcMessenger.
*/
public static final int CMD_CHANNEL_FULL_CONNECTION = BASE + 1;
/**
* Command typically sent after the destination receives a CMD_CHANNEL_FULL_CONNECTION.
* This signifies the acceptance or rejection of the channel by the sender.
*
* msg.arg1 == 0 : Accept connection
* : All other values signify the destination rejected the connection
* and {@link AsyncChannel#disconnect} would typically be called.
*/
public static final int CMD_CHANNEL_FULLY_CONNECTED = BASE + 2;
/**
* Command sent when one side or the other wishes to disconnect. The sender
* may or may not be able to receive a reply depending upon the protocol and
* the state of the connection. The receiver should call {@link AsyncChannel#disconnect}
* to close its side of the channel and it will receive a CMD_CHANNEL_DISCONNECTED
* when the channel is closed.
*
* msg.replyTo = messenger that is disconnecting
*/
public static final int CMD_CHANNEL_DISCONNECT = BASE + 3;
/**
* Command sent when the channel becomes disconnected. This is sent when the
* channel is forcibly disconnected by the system or as a reply to CMD_CHANNEL_DISCONNECT.
*
* msg.arg1 == 0 : STATUS_SUCCESSFUL
* 1 : STATUS_BINDING_UNSUCCESSFUL
* 2 : STATUS_SEND_UNSUCCESSFUL
* : All other values signify failure and the channel state is indeterminate
* msg.obj == the AsyncChannel
* msg.replyTo = messenger disconnecting or null if it was never connected.
*/
public static final int CMD_CHANNEL_DISCONNECTED = BASE + 4;
private static final int CMD_TO_STRING_COUNT = CMD_CHANNEL_DISCONNECTED - BASE + 1;
private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
static {
sCmdToString[CMD_CHANNEL_HALF_CONNECTED - BASE] = "CMD_CHANNEL_HALF_CONNECTED";
sCmdToString[CMD_CHANNEL_FULL_CONNECTION - BASE] = "CMD_CHANNEL_FULL_CONNECTION";
sCmdToString[CMD_CHANNEL_FULLY_CONNECTED - BASE] = "CMD_CHANNEL_FULLY_CONNECTED";
sCmdToString[CMD_CHANNEL_DISCONNECT - BASE] = "CMD_CHANNEL_DISCONNECT";
sCmdToString[CMD_CHANNEL_DISCONNECTED - BASE] = "CMD_CHANNEL_DISCONNECTED";
}
protected static String cmdToString(int cmd) {
cmd -= BASE;
if ((cmd >= 0) && (cmd < sCmdToString.length)) {
return sCmdToString[cmd];
} else {
return null;
}
}
/** Successful status always 0, !0 is an unsuccessful status */
public static final int STATUS_SUCCESSFUL = 0;
/** Error attempting to bind on a connect */
public static final int STATUS_BINDING_UNSUCCESSFUL = 1;
/** Error attempting to send a message */
public static final int STATUS_SEND_UNSUCCESSFUL = 2;
/** CMD_FULLY_CONNECTED refused because a connection already exists*/
public static final int STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED = 3;
/** Service connection */
private AsyncChannelConnection mConnection;
/** Context for source */
private Context mSrcContext;
/** Handler for source */
private Handler mSrcHandler;
/** Messenger for source */
private Messenger mSrcMessenger;
/** Messenger for destination */
private Messenger mDstMessenger;
/**
* AsyncChannel constructor
*/
public AsyncChannel() {
}
/**
* Connect handler to named package/class synchronously.
*
* @param srcContext is the context of the source
* @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
* messages
* @param dstPackageName is the destination package name
* @param dstClassName is the fully qualified class name (i.e. contains
* package name)
*
* @return STATUS_SUCCESSFUL on success any other value is an error.
*/
public int connectSrcHandlerToPackageSync(
Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName) {
if (DBG) log("connect srcHandler to dst Package & class E");
mConnection = new AsyncChannelConnection();
/* Initialize the source information */
mSrcContext = srcContext;
mSrcHandler = srcHandler;
mSrcMessenger = new Messenger(srcHandler);
/*
* Initialize destination information to null they will
* be initialized when the AsyncChannelConnection#onServiceConnected
* is called
*/
mDstMessenger = null;
/* Send intent to create the connection */
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(dstPackageName, dstClassName);
boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
if (DBG) log("connect srcHandler to dst Package & class X result=" + result);
return result ? STATUS_SUCCESSFUL : STATUS_BINDING_UNSUCCESSFUL;
}
/**
* Connect a handler to Messenger synchronously.
*
* @param srcContext is the context of the source
* @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
* messages
* @param dstMessenger is the hander to send messages to.
*
* @return STATUS_SUCCESSFUL on success any other value is an error.
*/
public int connectSync(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
if (DBG) log("halfConnectSync srcHandler to the dstMessenger E");
// We are connected
connected(srcContext, srcHandler, dstMessenger);
if (DBG) log("halfConnectSync srcHandler to the dstMessenger X");
return STATUS_SUCCESSFUL;
}
/**
* connect two local Handlers synchronously.
*
* @param srcContext is the context of the source
* @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
* messages
* @param dstHandler is the hander to send messages to.
*
* @return STATUS_SUCCESSFUL on success any other value is an error.
*/
public int connectSync(Context srcContext, Handler srcHandler, Handler dstHandler) {
return connectSync(srcContext, srcHandler, new Messenger(dstHandler));
}
/**
* Fully connect two local Handlers synchronously.
*
* @param srcContext is the context of the source
* @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
* messages
* @param dstHandler is the hander to send messages to.
*
* @return STATUS_SUCCESSFUL on success any other value is an error.
*/
public int fullyConnectSync(Context srcContext, Handler srcHandler, Handler dstHandler) {
int status = connectSync(srcContext, srcHandler, dstHandler);
if (status == STATUS_SUCCESSFUL) {
Message response = sendMessageSynchronously(CMD_CHANNEL_FULL_CONNECTION);
status = response.arg1;
}
return status;
}
/**
* Connect handler to named package/class.
*
* Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
* msg.arg1 = status
* msg.obj = the AsyncChannel
*
* @param srcContext is the context of the source
* @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
* messages
* @param dstPackageName is the destination package name
* @param dstClassName is the fully qualified class name (i.e. contains
* package name)
*/
public void connect(Context srcContext, Handler srcHandler, String dstPackageName,
String dstClassName) {
if (DBG) log("connect srcHandler to dst Package & class E");
final class ConnectAsync implements Runnable {
Context mSrcCtx;
Handler mSrcHdlr;
String mDstPackageName;
String mDstClassName;
ConnectAsync(Context srcContext, Handler srcHandler, String dstPackageName,
String dstClassName) {
mSrcCtx = srcContext;
mSrcHdlr = srcHandler;
mDstPackageName = dstPackageName;
mDstClassName = dstClassName;
}
@Override
public void run() {
int result = connectSrcHandlerToPackageSync(mSrcCtx, mSrcHdlr, mDstPackageName,
mDstClassName);
replyHalfConnected(result);
}
}
ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName);
new Thread(ca).start();
if (DBG) log("connect srcHandler to dst Package & class X");
}
/**
* Connect handler to a class
*
* Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
* msg.arg1 = status
* msg.obj = the AsyncChannel
*
* @param srcContext
* @param srcHandler
* @param klass is the class to send messages to.
*/
public void connect(Context srcContext, Handler srcHandler, Class> klass) {
connect(srcContext, srcHandler, klass.getPackage().getName(), klass.getName());
}
/**
* Connect handler and messenger.
*
* Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete.
* msg.arg1 = status
* msg.obj = the AsyncChannel
*
* @param srcContext
* @param srcHandler
* @param dstMessenger
*/
public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
if (DBG) log("connect srcHandler to the dstMessenger E");
// We are connected
connected(srcContext, srcHandler, dstMessenger);
// Tell source we are half connected
replyHalfConnected(STATUS_SUCCESSFUL);
if (DBG) log("connect srcHandler to the dstMessenger X");
}
/**
* Connect handler to messenger. This method is typically called
* when a server receives a CMD_CHANNEL_FULL_CONNECTION request
* and initializes the internal instance variables to allow communication
* with the dstMessenger.
*
* @param srcContext
* @param srcHandler
* @param dstMessenger
*/
public void connected(Context srcContext, Handler srcHandler, Messenger dstMessenger) {
if (DBG) log("connected srcHandler to the dstMessenger E");
// Initialize source fields
mSrcContext = srcContext;
mSrcHandler = srcHandler;
mSrcMessenger = new Messenger(mSrcHandler);
// Initialize destination fields
mDstMessenger = dstMessenger;
if (DBG) log("connected srcHandler to the dstMessenger X");
}
/**
* Connect two local Handlers.
*
* @param srcContext is the context of the source
* @param srcHandler is the hander to receive CONNECTED & DISCONNECTED
* messages
* @param dstHandler is the hander to send messages to.
*/
public void connect(Context srcContext, Handler srcHandler, Handler dstHandler) {
connect(srcContext, srcHandler, new Messenger(dstHandler));
}
/**
* Connect service and messenger.
*
* Sends a CMD_CHANNEL_HALF_CONNECTED message to srcAsyncService when complete.
* msg.arg1 = status
* msg.obj = the AsyncChannel
*
* @param srcAsyncService
* @param dstMessenger
*/
public void connect(AsyncService srcAsyncService, Messenger dstMessenger) {
connect(srcAsyncService, srcAsyncService.getHandler(), dstMessenger);
}
/**
* To close the connection call when handler receives CMD_CHANNEL_DISCONNECTED
*/
public void disconnected() {
mSrcContext = null;
mSrcHandler = null;
mSrcMessenger = null;
mDstMessenger = null;
mConnection = null;
}
/**
* Disconnect
*/
public void disconnect() {
if ((mConnection != null) && (mSrcContext != null)) {
mSrcContext.unbindService(mConnection);
}
if (mSrcHandler != null) {
replyDisconnected(STATUS_SUCCESSFUL);
}
}
/**
* Send a message to the destination handler.
*
* @param msg
*/
public void sendMessage(Message msg) {
msg.replyTo = mSrcMessenger;
try {
mDstMessenger.send(msg);
} catch (RemoteException e) {
replyDisconnected(STATUS_SEND_UNSUCCESSFUL);
}
}
/**
* Send a message to the destination handler
*
* @param what
*/
public void sendMessage(int what) {
Message msg = Message.obtain();
msg.what = what;
sendMessage(msg);
}
/**
* Send a message to the destination handler
*
* @param what
* @param arg1
*/
public void sendMessage(int what, int arg1) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
sendMessage(msg);
}
/**
* Send a message to the destination handler
*
* @param what
* @param arg1
* @param arg2
*/
public void sendMessage(int what, int arg1, int arg2) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
sendMessage(msg);
}
/**
* Send a message to the destination handler
*
* @param what
* @param arg1
* @param arg2
* @param obj
*/
public void sendMessage(int what, int arg1, int arg2, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
msg.obj = obj;
sendMessage(msg);
}
/**
* Send a message to the destination handler
*
* @param what
* @param obj
*/
public void sendMessage(int what, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
sendMessage(msg);
}
/**
* Reply to srcMsg sending dstMsg
*
* @param srcMsg
* @param dstMsg
*/
public void replyToMessage(Message srcMsg, Message dstMsg) {
try {
dstMsg.replyTo = mSrcMessenger;
srcMsg.replyTo.send(dstMsg);
} catch (RemoteException e) {
log("TODO: handle replyToMessage RemoteException" + e);
e.printStackTrace();
}
}
/**
* Reply to srcMsg
*
* @param srcMsg
* @param what
*/
public void replyToMessage(Message srcMsg, int what) {
Message msg = Message.obtain();
msg.what = what;
replyToMessage(srcMsg, msg);
}
/**
* Reply to srcMsg
*
* @param srcMsg
* @param what
* @param arg1
*/
public void replyToMessage(Message srcMsg, int what, int arg1) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
replyToMessage(srcMsg, msg);
}
/**
* Reply to srcMsg
*
* @param srcMsg
* @param what
* @param arg1
* @param arg2
*/
public void replyToMessage(Message srcMsg, int what, int arg1, int arg2) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
replyToMessage(srcMsg, msg);
}
/**
* Reply to srcMsg
*
* @param srcMsg
* @param what
* @param arg1
* @param arg2
* @param obj
*/
public void replyToMessage(Message srcMsg, int what, int arg1, int arg2, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
msg.obj = obj;
replyToMessage(srcMsg, msg);
}
/**
* Reply to srcMsg
*
* @param srcMsg
* @param what
* @param obj
*/
public void replyToMessage(Message srcMsg, int what, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
replyToMessage(srcMsg, msg);
}
/**
* Send the Message synchronously.
*
* @param msg to send
* @return reply message or null if an error.
*/
public Message sendMessageSynchronously(Message msg) {
Message resultMsg = SyncMessenger.sendMessageSynchronously(mDstMessenger, msg);
return resultMsg;
}
/**
* Send the Message synchronously.
*
* @param what
* @return reply message or null if an error.
*/
public Message sendMessageSynchronously(int what) {
Message msg = Message.obtain();
msg.what = what;
Message resultMsg = sendMessageSynchronously(msg);
return resultMsg;
}
/**
* Send the Message synchronously.
*
* @param what
* @param arg1
* @return reply message or null if an error.
*/
public Message sendMessageSynchronously(int what, int arg1) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
Message resultMsg = sendMessageSynchronously(msg);
return resultMsg;
}
/**
* Send the Message synchronously.
*
* @param what
* @param arg1
* @param arg2
* @return reply message or null if an error.
*/
public Message sendMessageSynchronously(int what, int arg1, int arg2) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
Message resultMsg = sendMessageSynchronously(msg);
return resultMsg;
}
/**
* Send the Message synchronously.
*
* @param what
* @param arg1
* @param arg2
* @param obj
* @return reply message or null if an error.
*/
public Message sendMessageSynchronously(int what, int arg1, int arg2, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = arg1;
msg.arg2 = arg2;
msg.obj = obj;
Message resultMsg = sendMessageSynchronously(msg);
return resultMsg;
}
/**
* Send the Message synchronously.
*
* @param what
* @param obj
* @return reply message or null if an error.
*/
public Message sendMessageSynchronously(int what, Object obj) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
Message resultMsg = sendMessageSynchronously(msg);
return resultMsg;
}
/**
* Helper class to send messages synchronously
*/
private static class SyncMessenger {
/** A stack of SyncMessengers */
private static Stack sStack = new Stack();
/** A number of SyncMessengers created */
private static int sCount = 0;
/** The handler thread */
private HandlerThread mHandlerThread;
/** The handler that will receive the result */
private SyncHandler mHandler;
/** The messenger used to send the message */
private Messenger mMessenger;
/** private constructor */
private SyncMessenger() {
}
/** Synchronous Handler class */
private class SyncHandler extends Handler {
/** The object used to wait/notify */
private Object mLockObject = new Object();
/** The resulting message */
private Message mResultMsg;
/** Constructor */
private SyncHandler(Looper looper) {
super(looper);
}
/** Handle of the reply message */
@Override
public void handleMessage(Message msg) {
mResultMsg = Message.obtain();
mResultMsg.copyFrom(msg);
synchronized(mLockObject) {
mLockObject.notify();
}
}
}
/**
* @return the SyncMessenger
*/
private static SyncMessenger obtain() {
SyncMessenger sm;
synchronized (sStack) {
if (sStack.isEmpty()) {
sm = new SyncMessenger();
sm.mHandlerThread = new HandlerThread("SyncHandler-" + sCount++);
sm.mHandlerThread.start();
sm.mHandler = sm.new SyncHandler(sm.mHandlerThread.getLooper());
sm.mMessenger = new Messenger(sm.mHandler);
} else {
sm = sStack.pop();
}
}
return sm;
}
/**
* Recycle this object
*/
private void recycle() {
synchronized (sStack) {
sStack.push(this);
}
}
/**
* Send a message synchronously.
*
* @param msg to send
* @return result message or null if an error occurs
*/
private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) {
SyncMessenger sm = SyncMessenger.obtain();
try {
if (dstMessenger != null && msg != null) {
msg.replyTo = sm.mMessenger;
synchronized (sm.mHandler.mLockObject) {
dstMessenger.send(msg);
sm.mHandler.mLockObject.wait();
}
} else {
sm.mHandler.mResultMsg = null;
}
} catch (InterruptedException e) {
sm.mHandler.mResultMsg = null;
} catch (RemoteException e) {
sm.mHandler.mResultMsg = null;
}
Message resultMsg = sm.mHandler.mResultMsg;
sm.recycle();
return resultMsg;
}
}
/**
* Reply to the src handler that we're half connected.
* see: CMD_CHANNEL_HALF_CONNECTED for message contents
*
* @param status to be stored in msg.arg1
*/
private void replyHalfConnected(int status) {
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED);
msg.arg1 = status;
msg.obj = this;
msg.replyTo = mDstMessenger;
mSrcHandler.sendMessage(msg);
}
/**
* Reply to the src handler that we are disconnected
* see: CMD_CHANNEL_DISCONNECTED for message contents
*
* @param status to be stored in msg.arg1
*/
private void replyDisconnected(int status) {
Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED);
msg.arg1 = status;
msg.obj = this;
msg.replyTo = mDstMessenger;
mSrcHandler.sendMessage(msg);
}
/**
* ServiceConnection to receive call backs.
*/
class AsyncChannelConnection implements ServiceConnection {
AsyncChannelConnection() {
}
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mDstMessenger = new Messenger(service);
replyHalfConnected(STATUS_SUCCESSFUL);
}
@Override
public void onServiceDisconnected(ComponentName className) {
replyDisconnected(STATUS_SUCCESSFUL);
}
}
/**
* Log the string.
*
* @param s
*/
private static void log(String s) {
Slog.d(TAG, s);
}
}