AsyncChannel.java revision d20a5d6b5a821e28d73eba6502a2135134014a84
1/** 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.util; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.os.Handler; 24import android.os.HandlerThread; 25import android.os.IBinder; 26import android.os.Looper; 27import android.os.Message; 28import android.os.Messenger; 29import android.os.RemoteException; 30import android.util.Slog; 31 32import java.util.Stack; 33 34/** 35 * An asynchronous channel between two handlers. 36 * 37 * The handlers maybe in the same process or in another process. There 38 * are two protocol styles that can be used with an AysncChannel. The 39 * first is a simple request/reply protocol where the server does 40 * not need to know which client is issuing the request. 41 * 42 * In a simple request/reply protocol the client/source sends requests to the 43 * server/destination. And the server uses the replyToMessage methods. 44 * In this usage model there is no need for the destination to 45 * use the connect methods. The typical sequence of operations is: 46 * 47 * 1) Client calls AsyncChannel#connect 48 * 2) Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel 49 50 * 3) Client calls AsyncChannel#sendMessage(msgX) 51 * 4) Server receives and processes msgX 52 * 5) Server optionally calls AsyncChannel#replyToMessage(msgY) 53 * and if sent Client receives and processes msgY 54 * 6) Loop to step 3 until done 55 * 56 * 7) When done Client calls {@link AsyncChannel#disconnect(int)} 57 * 8) Client receives CMD_CHANNEL_DISCONNECTED from AsyncChannel 58 * 59 * A second usage model is where the server/destination needs to know 60 * which client it's connected too. For example the server needs to 61 * send unsolicited messages back to the client. Or the server keeps 62 * different state for each client. In this model the server will also 63 * use the connect methods. The typical sequence of operation is: 64 * 65 * 1) Client calls AsyncChannel#connect 66 * 2) Client receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel 67 * 3) Client calls AsyncChannel#sendMessage(CMD_CHANNEL_FULL_CONNECTION) 68 * 4) Server receives CMD_CHANNEL_FULL_CONNECTION 69 * 5) Server calls AsyncChannel#connect 70 * 6) Server receives CMD_CHANNEL_HALF_CONNECTED from AsyncChannel 71 * 7) Server sends AsyncChannel#sendMessage(CMD_CHANNEL_FULLY_CONNECTED) 72 * 8) Client receives CMD_CHANNEL_FULLY_CONNECTED 73 * 74 * 9) Client/Server uses AsyncChannel#sendMessage/replyToMessage 75 * to communicate and perform work 76 * 10) Loop to step 9 until done 77 * 78 * 11) When done Client/Server calls {@link AsyncChannel#disconnect(int)} 79 * 12) Client/Server receives CMD_CHANNEL_DISCONNECTED from AsyncChannel 80 */ 81public class AsyncChannel { 82 /** Log tag */ 83 private static final String TAG = "AsyncChannel"; 84 85 /** Enable to turn on debugging */ 86 private static final boolean DBG = false; 87 88 /** 89 * Command sent when the channel is half connected. Half connected 90 * means that the channel can be used to send commends to the destination 91 * but the destination is unaware that the channel exists. The first 92 * command sent to the destination is typically CMD_CHANNEL_FULL_CONNECTION if 93 * it is desired to establish a long term connection, but any command maybe 94 * sent. 95 * 96 * msg.arg1 == 0 : STATUS_SUCCESSFUL 97 * 1 : STATUS_BINDING_UNSUCCESSFUL 98 * msg.arg2 == token parameter 99 * msg.replyTo == dstMessenger if successful 100 */ 101 public static final int CMD_CHANNEL_HALF_CONNECTED = -1; 102 103 /** 104 * Command typically sent when after receiving the CMD_CHANNEL_HALF_CONNECTED. 105 * This is used to initiate a long term connection with the destination and 106 * typically the destination will reply with CMD_CHANNEL_FULLY_CONNECTED. 107 * 108 * msg.replyTo = srcMessenger. 109 */ 110 public static final int CMD_CHANNEL_FULL_CONNECTION = -2; 111 112 /** 113 * Command typically sent after the destination receives a CMD_CHANNEL_FULL_CONNECTION. 114 * This signifies the acceptance or rejection of the channel by the sender. 115 * 116 * msg.arg1 == 0 : Accept connection 117 * : All other values signify the destination rejected the connection 118 * and {@link AsyncChannel#disconnect(int)} would typically be called. 119 */ 120 public static final int CMD_CHANNEL_FULLY_CONNECTED = -3; 121 122 /** 123 * Command sent when one side or the other wishes to disconnect. The sender 124 * may or may not be able to receive a reply depending upon the protocol and 125 * the state of the connection. The receiver should call {@link AsyncChannel#disconnect(int)} 126 * to close its side of the channel and it will receive a CMD_CHANNEL_DISCONNECTED 127 * when the channel is closed. 128 * 129 * msg.replyTo = messenger that is disconnecting 130 */ 131 public static final int CMD_CHANNEL_DISCONNECT = -4; 132 133 /** 134 * Command sent when the channel becomes disconnected. This is sent when the 135 * channel is forcibly disconnected by the system or as a reply to CMD_CHANNEL_DISCONNECT. 136 * 137 * msg.arg1 == 0 : STATUS_SUCCESSFUL 138 * : All other values signify failure and the channel state is indeterminate 139 * msg.arg2 == token parameter 140 * msg.replyTo = messenger disconnecting or null if it was never connected. 141 */ 142 public static final int CMD_CHANNEL_DISCONNECTED = -5; 143 144 /** Successful status always 0, !0 is an unsuccessful status */ 145 public static final int STATUS_SUCCESSFUL = 0; 146 147 /** Error attempting to bind on a connect */ 148 public static final int STATUS_BINDING_UNSUCCESSFUL = 1; 149 150 /** Service connection */ 151 private AsyncChannelConnection mConnection; 152 153 /** Context for source */ 154 private Context mSrcContext; 155 156 /** Handler for source */ 157 private Handler mSrcHandler; 158 159 /** Messenger for source */ 160 private Messenger mSrcMessenger; 161 162 /** Messenger for destination */ 163 private Messenger mDstMessenger; 164 165 /** 166 * AsyncChannel constructor 167 */ 168 public AsyncChannel() { 169 } 170 171 /** 172 * Connect handler to named package/class. 173 * 174 * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete. 175 * 176 * @param srcContext is the context of the source 177 * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED 178 * messages 179 * @param dstPackageName is the destination package name 180 * @param dstClassName is the fully qualified class name (i.e. contains 181 * package name) 182 * @param token unique id for this connection 183 */ 184 private void connectSrcHandlerToPackage( 185 Context srcContext, Handler srcHandler, String dstPackageName, String dstClassName, 186 int token) { 187 if (DBG) log("connect srcHandler to dst Package & class E"); 188 189 mConnection = new AsyncChannelConnection(token); 190 191 /* Initialize the source information */ 192 mSrcContext = srcContext; 193 mSrcHandler = srcHandler; 194 mSrcMessenger = new Messenger(srcHandler); 195 196 /* 197 * Initialize destination information to null they will 198 * be initialized when the AsyncChannelConnection#onServiceConnected 199 * is called 200 */ 201 mDstMessenger = null; 202 203 /* Send intent to create the connection */ 204 Intent intent = new Intent(Intent.ACTION_MAIN); 205 intent.setClassName(dstPackageName, dstClassName); 206 boolean result = srcContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 207 if (!result) { 208 replyHalfConnected(STATUS_BINDING_UNSUCCESSFUL, token); 209 } 210 211 if (DBG) log("connect srcHandler to dst Package & class X result=" + result); 212 } 213 214 /** 215 * Connect handler to named package/class. 216 * 217 * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete. 218 * msg.arg1 = status 219 * msg.arg2 = token 220 * 221 * @param srcContext is the context of the source 222 * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED 223 * messages 224 * @param dstPackageName is the destination package name 225 * @param dstClassName is the fully qualified class name (i.e. contains 226 * package name) 227 * @param token returned in msg.arg2 228 */ 229 public void connect(Context srcContext, Handler srcHandler, String dstPackageName, 230 String dstClassName, int token) { 231 if (DBG) log("connect srcHandler to dst Package & class E"); 232 233 final class ConnectAsync implements Runnable { 234 Context mSrcCtx; 235 Handler mSrcHdlr; 236 String mDstPackageName; 237 String mDstClassName; 238 int mToken; 239 240 ConnectAsync(Context srcContext, Handler srcHandler, String dstPackageName, 241 String dstClassName, int token) { 242 mSrcCtx = srcContext; 243 mSrcHdlr = srcHandler; 244 mDstPackageName = dstPackageName; 245 mDstClassName = dstClassName; 246 mToken = token; 247 } 248 249 @Override 250 public void run() { 251 connectSrcHandlerToPackage(mSrcCtx, mSrcHdlr, mDstPackageName, mDstClassName, 252 mToken); 253 } 254 } 255 256 ConnectAsync ca = new ConnectAsync(srcContext, srcHandler, dstPackageName, dstClassName, 257 token); 258 new Thread(ca).start(); 259 260 if (DBG) log("connect srcHandler to dst Package & class X"); 261 } 262 263 /** 264 * Connect handler to a class 265 * 266 * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete. 267 * msg.arg1 = status 268 * msg.arg2 = token 269 * 270 * @param srcContext 271 * @param srcHandler 272 * @param klass is the class to send messages to. 273 * @param token returned in msg.arg2 274 */ 275 public void connect(Context srcContext, Handler srcHandler, Class<?> klass, int token) { 276 connect(srcContext, srcHandler, klass.getPackage().getName(), klass.getName(), token); 277 } 278 279 /** 280 * Connect handler and messenger. 281 * 282 * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete. 283 * msg.arg1 = status 284 * msg.arg2 = token 285 * 286 * @param srcContext 287 * @param srcHandler 288 * @param dstMessenger 289 * @param token returned in msg.arg2 290 */ 291 public void connect(Context srcContext, Handler srcHandler, Messenger dstMessenger, int token) { 292 if (DBG) log("connect srcHandler to the dstMessenger E"); 293 294 // Initialize source fields 295 mSrcContext = srcContext; 296 mSrcHandler = srcHandler; 297 mSrcMessenger = new Messenger(mSrcHandler); 298 299 // Initialize destination fields 300 mDstMessenger = dstMessenger; 301 302 if (DBG) log("tell source we are half connected"); 303 304 // Tell source we are half connected 305 replyHalfConnected(STATUS_SUCCESSFUL, token); 306 307 if (DBG) log("connect srcHandler to the dstMessenger X"); 308 } 309 310 /** 311 * Connect two local Handlers. 312 * 313 * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcHandler when complete. 314 * msg.arg1 = status 315 * msg.arg2 = token 316 * 317 * @param srcContext is the context of the source 318 * @param srcHandler is the hander to receive CONNECTED & DISCONNECTED 319 * messages 320 * @param dstHandler is the hander to send messages to. 321 * @param token returned in msg.arg2 322 */ 323 public void connect(Context srcContext, Handler srcHandler, Handler dstHandler, int token) { 324 connect(srcContext, srcHandler, new Messenger(dstHandler), token); 325 } 326 327 /** 328 * Connect service and messenger. 329 * 330 * Sends a CMD_CHANNEL_HALF_CONNECTED message to srcAsyncService when complete. 331 * msg.arg1 = status 332 * msg.arg2 = token 333 * 334 * @param srcAsyncService 335 * @param dstMessenger 336 * @param token returned in msg.arg2 337 */ 338 public void connect(AsyncService srcAsyncService, Messenger dstMessenger, int token) { 339 connect(srcAsyncService, srcAsyncService.getHandler(), dstMessenger, token); 340 } 341 342 /** 343 * To close the connection call when handler receives CMD_CHANNEL_DISCONNECTED 344 */ 345 public void disconnected() { 346 mSrcHandler = null; 347 mSrcMessenger = null; 348 mDstMessenger = null; 349 mConnection = null; 350 } 351 352 /** 353 * Disconnect 354 */ 355 public void disconnect(int token) { 356 if (mConnection != null) { 357 mConnection.setToken(token); 358 mSrcContext.unbindService(mConnection); 359 } 360 if (mSrcHandler != null) { 361 Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED); 362 msg.arg1 = STATUS_SUCCESSFUL; 363 msg.arg2 = token; 364 msg.replyTo = mDstMessenger; 365 mSrcHandler.sendMessage(msg); 366 } 367 } 368 369 /** 370 * Send a message to the destination handler. 371 * 372 * @param msg 373 */ 374 public void sendMessage(Message msg) { 375 msg.replyTo = mSrcMessenger; 376 try { 377 mDstMessenger.send(msg); 378 } catch (RemoteException e) { 379 log("TODO: handle sendMessage RemoteException" + e); 380 } 381 } 382 383 /** 384 * Send a message to the destination handler 385 * 386 * @param what 387 */ 388 public void sendMessage(int what) { 389 Message msg = Message.obtain(); 390 msg.what = what; 391 sendMessage(msg); 392 } 393 394 /** 395 * Send a message to the destination handler 396 * 397 * @param what 398 * @param arg1 399 */ 400 public void sendMessage(int what, int arg1) { 401 Message msg = Message.obtain(); 402 msg.what = what; 403 msg.arg1 = arg1; 404 sendMessage(msg); 405 } 406 407 /** 408 * Send a message to the destination handler 409 * 410 * @param what 411 * @param arg1 412 * @param arg2 413 */ 414 public void sendMessage(int what, int arg1, int arg2) { 415 Message msg = Message.obtain(); 416 msg.what = what; 417 msg.arg1 = arg1; 418 msg.arg2 = arg2; 419 sendMessage(msg); 420 } 421 422 /** 423 * Send a message to the destination handler 424 * 425 * @param what 426 * @param arg1 427 * @param arg2 428 * @param obj 429 */ 430 public void sendMessage(int what, int arg1, int arg2, Object obj) { 431 Message msg = Message.obtain(); 432 msg.what = what; 433 msg.arg1 = arg1; 434 msg.arg2 = arg2; 435 msg.obj = obj; 436 sendMessage(msg); 437 } 438 439 /** 440 * Send a message to the destination handler 441 * 442 * @param what 443 * @param obj 444 */ 445 public void sendMessage(int what, Object obj) { 446 Message msg = Message.obtain(); 447 msg.what = what; 448 msg.obj = obj; 449 sendMessage(msg); 450 } 451 452 /** 453 * Reply to srcMsg sending dstMsg 454 * 455 * @param srcMsg 456 * @param dstMsg 457 */ 458 public void replyToMessage(Message srcMsg, Message dstMsg) { 459 try { 460 srcMsg.replyTo.send(dstMsg); 461 } catch (RemoteException e) { 462 log("TODO: handle replyToMessage RemoteException" + e); 463 e.printStackTrace(); 464 } 465 } 466 467 /** 468 * Reply to srcMsg 469 * 470 * @param srcMsg 471 * @param what 472 */ 473 public void replyToMessage(Message srcMsg, int what) { 474 Message msg = Message.obtain(); 475 msg.what = what; 476 replyToMessage(srcMsg, msg); 477 } 478 479 /** 480 * Reply to srcMsg 481 * 482 * @param srcMsg 483 * @param what 484 * @param arg1 485 */ 486 public void replyToMessage(Message srcMsg, int what, int arg1) { 487 Message msg = Message.obtain(); 488 msg.what = what; 489 msg.arg1 = arg1; 490 replyToMessage(srcMsg, msg); 491 } 492 493 /** 494 * Reply to srcMsg 495 * 496 * @param srcMsg 497 * @param what 498 * @param arg1 499 * @param arg2 500 */ 501 public void replyToMessage(Message srcMsg, int what, int arg1, int arg2) { 502 Message msg = Message.obtain(); 503 msg.what = what; 504 msg.arg1 = arg1; 505 msg.arg2 = arg2; 506 replyToMessage(srcMsg, msg); 507 } 508 509 /** 510 * Reply to srcMsg 511 * 512 * @param srcMsg 513 * @param what 514 * @param arg1 515 * @param arg2 516 * @param obj 517 */ 518 public void replyToMessage(Message srcMsg, int what, int arg1, int arg2, Object obj) { 519 Message msg = Message.obtain(); 520 msg.what = what; 521 msg.arg1 = arg1; 522 msg.arg2 = arg2; 523 msg.obj = obj; 524 replyToMessage(srcMsg, msg); 525 } 526 527 /** 528 * Reply to srcMsg 529 * 530 * @param srcMsg 531 * @param what 532 * @param obj 533 */ 534 public void replyToMessage(Message srcMsg, int what, Object obj) { 535 Message msg = Message.obtain(); 536 msg.what = what; 537 msg.obj = obj; 538 replyToMessage(srcMsg, msg); 539 } 540 541 /** 542 * Send the Message synchronously. 543 * 544 * @param msg to send 545 * @return reply message or null if an error. 546 */ 547 public Message sendMessageSynchronously(Message msg) { 548 Message resultMsg = SyncMessenger.sendMessageSynchronously(mDstMessenger, msg); 549 return resultMsg; 550 } 551 552 /** 553 * Send the Message synchronously. 554 * 555 * @param what 556 * @return reply message or null if an error. 557 */ 558 public Message sendMessageSynchronously(int what) { 559 Message msg = Message.obtain(); 560 msg.what = what; 561 Message resultMsg = sendMessageSynchronously(msg); 562 return resultMsg; 563 } 564 565 /** 566 * Send the Message synchronously. 567 * 568 * @param what 569 * @param arg1 570 * @return reply message or null if an error. 571 */ 572 public Message sendMessageSynchronously(int what, int arg1) { 573 Message msg = Message.obtain(); 574 msg.what = what; 575 msg.arg1 = arg1; 576 Message resultMsg = sendMessageSynchronously(msg); 577 return resultMsg; 578 } 579 580 /** 581 * Send the Message synchronously. 582 * 583 * @param what 584 * @param arg1 585 * @param arg2 586 * @return reply message or null if an error. 587 */ 588 public Message sendMessageSynchronously(int what, int arg1, int arg2) { 589 Message msg = Message.obtain(); 590 msg.what = what; 591 msg.arg1 = arg1; 592 msg.arg2 = arg2; 593 Message resultMsg = sendMessageSynchronously(msg); 594 return resultMsg; 595 } 596 597 /** 598 * Send the Message synchronously. 599 * 600 * @param what 601 * @param arg1 602 * @param arg2 603 * @param obj 604 * @return reply message or null if an error. 605 */ 606 public Message sendMessageSynchronously(int what, int arg1, int arg2, Object obj) { 607 Message msg = Message.obtain(); 608 msg.what = what; 609 msg.arg1 = arg1; 610 msg.arg2 = arg2; 611 msg.obj = obj; 612 Message resultMsg = sendMessageSynchronously(msg); 613 return resultMsg; 614 } 615 616 /** 617 * Send the Message synchronously. 618 * 619 * @param what 620 * @param obj 621 * @return reply message or null if an error. 622 */ 623 public Message sendMessageSynchronously(int what, Object obj) { 624 Message msg = Message.obtain(); 625 msg.what = what; 626 msg.obj = obj; 627 Message resultMsg = sendMessageSynchronously(msg); 628 return resultMsg; 629 } 630 631 /** 632 * Helper class to send messages synchronously 633 */ 634 private static class SyncMessenger { 635 /** A stack of SyncMessengers */ 636 private static Stack<SyncMessenger> sStack = new Stack<SyncMessenger>(); 637 /** A number of SyncMessengers created */ 638 private static int sCount = 0; 639 /** The handler thread */ 640 private HandlerThread mHandlerThread; 641 /** The handler that will receive the result */ 642 private SyncHandler mHandler; 643 /** The messenger used to send the message */ 644 private Messenger mMessenger; 645 646 /** private constructor */ 647 private SyncMessenger() { 648 } 649 650 /** Synchronous Handler class */ 651 private class SyncHandler extends Handler { 652 /** The object used to wait/notify */ 653 private Object mLockObject = new Object(); 654 /** The resulting message */ 655 private Message mResultMsg; 656 657 /** Constructor */ 658 private SyncHandler(Looper looper) { 659 super(looper); 660 } 661 662 /** Handle of the reply message */ 663 @Override 664 public void handleMessage(Message msg) { 665 mResultMsg = Message.obtain(); 666 mResultMsg.copyFrom(msg); 667 synchronized(mLockObject) { 668 mLockObject.notify(); 669 } 670 } 671 } 672 673 /** 674 * @return the SyncMessenger 675 */ 676 private static SyncMessenger obtain() { 677 SyncMessenger sm; 678 synchronized (sStack) { 679 if (sStack.isEmpty()) { 680 sm = new SyncMessenger(); 681 sm.mHandlerThread = new HandlerThread("SyncHandler-" + sCount++); 682 sm.mHandlerThread.start(); 683 sm.mHandler = sm.new SyncHandler(sm.mHandlerThread.getLooper()); 684 sm.mMessenger = new Messenger(sm.mHandler); 685 } else { 686 sm = sStack.pop(); 687 } 688 } 689 return sm; 690 } 691 692 /** 693 * Recycle this object 694 */ 695 private void recycle() { 696 synchronized (sStack) { 697 sStack.push(this); 698 } 699 } 700 701 /** 702 * Send a message synchronously. 703 * 704 * @param msg to send 705 * @return result message or null if an error occurs 706 */ 707 private static Message sendMessageSynchronously(Messenger dstMessenger, Message msg) { 708 SyncMessenger sm = SyncMessenger.obtain(); 709 try { 710 msg.replyTo = sm.mMessenger; 711 dstMessenger.send(msg); 712 synchronized (sm.mHandler.mLockObject) { 713 sm.mHandler.mLockObject.wait(); 714 } 715 } catch (InterruptedException e) { 716 sm.mHandler.mResultMsg = null; 717 } catch (RemoteException e) { 718 sm.mHandler.mResultMsg = null; 719 } 720 Message resultMsg = sm.mHandler.mResultMsg; 721 sm.recycle(); 722 return resultMsg; 723 } 724 } 725 726 /** 727 * Reply to the src handler that we're half connected. 728 * 729 * @param status to be stored in msg.arg1 730 */ 731 private void replyHalfConnected(int status, int token) { 732 Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_HALF_CONNECTED); 733 msg.arg1 = status; 734 msg.arg2 = token; 735 msg.replyTo = mDstMessenger; 736 mSrcHandler.sendMessage(msg); 737 } 738 739 /** 740 * ServiceConnection to receive call backs. 741 */ 742 class AsyncChannelConnection implements ServiceConnection { 743 private int mToken; 744 745 AsyncChannelConnection(int token) { 746 mToken = token; 747 } 748 749 /** 750 * @param token 751 */ 752 public void setToken(int token) { 753 mToken = token; 754 } 755 756 public void onServiceConnected(ComponentName className, IBinder service) { 757 mDstMessenger = new Messenger(service); 758 replyHalfConnected(STATUS_SUCCESSFUL, mToken); 759 } 760 761 public void onServiceDisconnected(ComponentName className) { 762 Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED); 763 msg.arg1 = STATUS_SUCCESSFUL; 764 msg.arg2 = mToken; 765 msg.replyTo = mDstMessenger; 766 mSrcHandler.sendMessage(msg); 767 } 768 } 769 770 /** 771 * Log the string. 772 * 773 * @param s 774 */ 775 private static void log(String s) { 776 Slog.d(TAG, s); 777 } 778} 779