4 * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd. All rights reserved.
7 * GiWoong Kim <giwoong.kim@samsung.com>
8 * YeongKyoon Lee <yeongkyoon.lee@samsung.com>
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
30 package org.tizen.emulator.skin.comm.sock;
32 import java.io.BufferedInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.DataInputStream;
35 import java.io.DataOutputStream;
36 import java.io.IOException;
37 import java.net.Socket;
38 import java.net.UnknownHostException;
39 import java.util.ArrayList;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.Timer;
43 import java.util.TimerTask;
44 import java.util.concurrent.atomic.AtomicInteger;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
48 import org.tizen.emulator.skin.EmulatorSkin;
49 import org.tizen.emulator.skin.comm.ICommunicator;
50 import org.tizen.emulator.skin.comm.ICommunicator.SendCommand;
51 import org.tizen.emulator.skin.comm.sock.data.ISendData;
52 import org.tizen.emulator.skin.comm.sock.data.StartData;
53 import org.tizen.emulator.skin.config.EmulatorConfig;
54 import org.tizen.emulator.skin.config.EmulatorConfig.ArgsConstants;
55 import org.tizen.emulator.skin.log.SkinLogger;
56 import org.tizen.emulator.skin.util.IOUtil;
57 import org.tizen.emulator.skin.util.SkinUtil;
64 public class SocketCommunicator implements ICommunicator {
66 public class DataTranfer {
68 private boolean isTransferState;
69 private byte[] receivedData;
72 private long maxWaitTime;
75 private DataTranfer() {
78 private void setData( byte[] data ) {
79 this.receivedData = data;
80 isTransferState = false;
85 public static final int HEART_BEAT_INTERVAL = 1; //second
86 public static final int HEART_BEAT_EXPIRE = 15;
88 public final static int SCREENSHOT_WAIT_INTERVAL = 3; // milli-seconds
89 public final static int SCREENSHOT_WAIT_LIMIT = 3000; // milli-seconds
90 public final static int DETAIL_INFO_WAIT_INTERVAL = 1; // milli-seconds
91 public final static int DETAIL_INFO_WAIT_LIMIT = 3000; // milli-seconds
93 public final static int MAX_SEND_QUEUE_SIZE = 100000;
95 private static int reqId;
97 private Logger logger =
98 SkinLogger.getSkinLogger(SocketCommunicator.class).getLogger();
100 private EmulatorConfig config;
102 private long initialData;
103 private EmulatorSkin skin;
105 private Socket socket;
106 private DataInputStream dis;
107 private DataOutputStream dos;
109 private AtomicInteger heartbeatCount;
110 private boolean isTerminated;
111 private boolean isSensorDaemonStarted;
112 private boolean isRamdump;
113 private TimerTask heartbeatExecutor;
114 private Timer heartbeatTimer;
116 private DataTranfer screenShotDataTransfer;
117 private DataTranfer detailInfoTransfer;
118 private DataTranfer progressDataTransfer;
120 private Thread sendThread;
121 private LinkedList<SkinSendData> sendQueue;
123 public SocketCommunicator(EmulatorConfig config, int uId, EmulatorSkin skin) {
125 this.config = config;
129 this.screenShotDataTransfer = new DataTranfer();
130 this.screenShotDataTransfer.sleep = SCREENSHOT_WAIT_INTERVAL;
131 this.screenShotDataTransfer.maxWaitTime = SCREENSHOT_WAIT_LIMIT;
133 this.detailInfoTransfer = new DataTranfer();
134 this.detailInfoTransfer.sleep = DETAIL_INFO_WAIT_INTERVAL;
135 this.detailInfoTransfer.maxWaitTime = DETAIL_INFO_WAIT_LIMIT;
137 this.progressDataTransfer = new DataTranfer();
138 this.progressDataTransfer.sleep = SCREENSHOT_WAIT_INTERVAL;
139 this.progressDataTransfer.maxWaitTime = SCREENSHOT_WAIT_LIMIT;
141 this.heartbeatCount = new AtomicInteger(0);
142 //this.heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
143 this.heartbeatTimer = new Timer();
147 int port = config.getArgInt( ArgsConstants.SERVER_PORT );
148 socket = new Socket( "127.0.0.1", port );
149 logger.info( "socket.isConnected():" + socket.isConnected() );
151 } catch ( UnknownHostException e ) {
152 logger.log( Level.SEVERE, e.getMessage(), e );
153 } catch ( IOException e ) {
154 logger.log( Level.SEVERE, e.getMessage(), e );
159 public void setInitialData(long data) {
160 this.initialData = data;
166 sendQueue = new LinkedList<SkinSendData>();
168 sendThread = new Thread("sendThread") {
170 List<SkinSendData> list = new ArrayList<SkinSendData>();
177 synchronized ( sendQueue ) {
179 if ( sendQueue.isEmpty() ) {
182 } catch ( InterruptedException e ) {
183 logger.log( Level.SEVERE, e.getMessage(), e );
187 SkinSendData sendData = null;
189 sendData = sendQueue.poll();
190 if ( null != sendData ) {
191 list.add( sendData );
199 for ( SkinSendData data : list ) {
200 sendToQEMUInternal( data );
205 if ( isTerminated ) {
218 dis = new DataInputStream( socket.getInputStream() );
219 dos = new DataOutputStream( socket.getOutputStream() );
221 int width = config.getArgInt( ArgsConstants.RESOLUTION_WIDTH );
222 int height = config.getArgInt( ArgsConstants.RESOLUTION_HEIGHT );
223 int scale = SkinUtil.getValidScale( config );
224 // short rotation = config.getSkinPropertyShort( SkinPropertiesConstants.WINDOW_ROTATION,
225 // EmulatorConfig.DEFAULT_WINDOW_ROTATION );
226 // has to be portrait mode at first booting time
227 short rotation = EmulatorConfig.DEFAULT_WINDOW_ROTATION;
229 StartData startData = new StartData(initialData, width, height, scale, rotation);
230 logger.info("StartData" + startData);
232 sendToQEMU( SendCommand.SEND_START, startData );
234 } catch ( IOException e ) {
235 logger.log( Level.SEVERE, e.getMessage(), e );
240 boolean ignoreHeartbeat = config.getArgBoolean( ArgsConstants.TEST_HEART_BEAT_IGNORE );
242 if (ignoreHeartbeat) {
243 logger.info("Ignore Skin heartbeat.");
245 heartbeatExecutor = new TimerTask() {
248 increaseHeartbeatCount();
250 if (isHeartbeatExpired()) {
251 logger.info("heartbeat was expired");
257 heartbeatTimer.schedule(heartbeatExecutor, 1, HEART_BEAT_INTERVAL * 1000);
262 if ( isTerminated ) {
268 int reqId = dis.readInt();
269 short cmd = dis.readShort();
270 int length = dis.readInt();
272 if ( logger.isLoggable( Level.FINE ) ) {
273 logger.fine( "[Socket] read - reqId:" + reqId + ", command:" + cmd + ", dataLength:" + length );
276 ReceiveCommand command = null;
279 command = ReceiveCommand.getValue( cmd );
280 } catch ( IllegalArgumentException e ) {
281 logger.severe( "unknown command:" + cmd );
287 resetHeartbeatCount();
288 if ( logger.isLoggable( Level.FINE ) ) {
289 logger.fine( "received HEAR_BEAT from QEMU." );
291 sendToQEMU( SendCommand.RESPONSE_HEART_BEAT, null );
294 case SCREEN_SHOT_DATA: {
295 logger.info( "received SCREEN_SHOT_DATA from QEMU." );
296 receiveData( screenShotDataTransfer, length );
300 case DETAIL_INFO_DATA: {
301 logger.info( "received DETAIL_INFO_DATA from QEMU." );
302 receiveData( detailInfoTransfer, length );
306 case RAMDUMP_COMPLETE: {
307 logger.info("received RAMDUMP_COMPLETE from QEMU.");
308 setRamdumpFlag(false);
311 case BOOTING_PROGRESS: {
312 logger.info("received BOOTING_PROGRESS from QEMU.");
314 resetDataTransfer(progressDataTransfer);
315 receiveData(progressDataTransfer, length);
317 byte[] receivedData = getReceivedData(progressDataTransfer);
318 if (null != receivedData) {
319 String strValue = new String(receivedData, 0, length - 1);
323 value = Integer.parseInt(strValue);
324 } catch (NumberFormatException e) {
328 /* draw progress bar */
329 if (skin.bootingProgress != null) {
330 skin.bootingProgress.setSelection(value);
332 if (value == 100 | value == 0) {
333 /* this means progressbar will be
335 if (skin.bootingProgress != null) {
336 skin.bootingProgress = null;
344 case SENSOR_DAEMON_START: {
345 logger.info( "received SENSOR_DAEMON_START from QEMU." );
346 synchronized ( this ) {
347 isSensorDaemonStarted = true;
352 logger.info( "received RESPONSE_SHUTDOWN from QEMU." );
353 sendToQEMU( SendCommand.RESPONSE_SHUTDOWN, null );
358 logger.severe( "Unknown command from QEMU. command:" + cmd );
363 } catch ( IOException e ) {
364 logger.log( Level.SEVERE, e.getMessage(), e );
372 private void receiveData(
373 DataTranfer dataTransfer, int length) throws IOException {
374 synchronized (dataTransfer) {
376 if (null != dataTransfer.timer) {
377 dataTransfer.timer.cancel();
380 byte[] data = readData(dis, length);
383 logger.info("finished receiving data from QEMU.");
385 logger.severe("Fail to receiving data from QEMU.");
388 dataTransfer.isTransferState = false;
389 dataTransfer.timer = null;
391 dataTransfer.setData(data);
392 dataTransfer.notifyAll();
397 private byte[] readData( DataInputStream is, int length ) throws IOException {
403 BufferedInputStream bfis = new BufferedInputStream( is, length );
404 byte[] data = new byte[length];
411 if ( total == length ) {
415 read = bfis.read( data, total, length - total );
418 if ( total < length ) {
427 logger.info( "finished reading stream. read:" + total );
433 public synchronized DataTranfer sendToQEMU(
434 SendCommand command, ISendData data, boolean useDataTransfer) {
436 DataTranfer dataTranfer = null;
438 if ( useDataTransfer ) {
440 if ( SendCommand.SCREEN_SHOT.equals( command ) ) {
441 dataTranfer = resetDataTransfer( screenShotDataTransfer );
442 } else if ( SendCommand.DETAIL_INFO.equals( command ) ) {
443 dataTranfer = resetDataTransfer( detailInfoTransfer );
447 sendToQEMU( command, data );
453 private DataTranfer resetDataTransfer( final DataTranfer dataTransfer ) {
455 synchronized ( dataTransfer ) {
457 if ( dataTransfer.isTransferState ) {
458 logger.severe( "Already transter state for getting data." );
462 dataTransfer.isTransferState = true;
464 Timer timer = new Timer();
465 dataTransfer.timer = timer;
467 TimerTask timerTask = new TimerTask() {
470 synchronized ( dataTransfer ) {
471 dataTransfer.isTransferState = false;
472 dataTransfer.timer = null;
473 dataTransfer.receivedData = null;
477 timer.schedule(timerTask, dataTransfer.maxWaitTime + 1000);
486 public void sendToQEMU( SendCommand command, ISendData data ) {
488 synchronized ( sendQueue ) {
489 if ( MAX_SEND_QUEUE_SIZE < sendQueue.size() ) {
490 logger.warning( "Send queue size exceeded max value, do not push data into send queue." );
492 sendQueue.add( new SkinSendData( command, data ) );
493 sendQueue.notifyAll();
499 private void sendToQEMUInternal( SkinSendData sendData ) {
503 if( null == sendData ) {
507 SendCommand command = sendData.getCommand();
508 ISendData data = sendData.getSendData();
510 reqId = ( Integer.MAX_VALUE == reqId ) ? 0 : ++reqId;
512 ByteArrayOutputStream bao = new ByteArrayOutputStream();
513 DataOutputStream dataOutputStream = new DataOutputStream( bao );
515 dataOutputStream.writeInt( uId );
516 dataOutputStream.writeInt( reqId );
517 dataOutputStream.writeShort( command.value() );
520 if ( null == data ) {
522 dataOutputStream.writeShort( length );
524 byte[] byteData = data.serialize();
525 length = (short) byteData.length;
526 dataOutputStream.writeShort( length );
527 dataOutputStream.write( byteData );
530 dataOutputStream.flush();
532 dos.write( bao.toByteArray() );
535 if ( logger.isLoggable( Level.FINE ) ) {
536 logger.fine( "[Socket] write - uid:" + uId + ", reqId:" + reqId + ", command:" + command.value()
537 + " - " + command.toString() + ", length:" + length );
541 if ( logger.isLoggable( Level.FINE ) ) {
542 logger.fine( "[Socket] data - " + data.toString() );
546 } catch ( IOException e ) {
547 logger.log( Level.SEVERE, e.getMessage(), e );
552 public byte[] getReceivedData( DataTranfer dataTranfer ) {
554 if (null == dataTranfer) {
558 synchronized (dataTranfer) {
561 byte[] receivedData = null;
562 long sleep = dataTranfer.sleep;
563 long maxWaitTime = dataTranfer.maxWaitTime;
564 int limitCount = (int) ( maxWaitTime / sleep );
566 while ( dataTranfer.isTransferState ) {
568 if ( limitCount < count ) {
569 logger.severe( "time out for receiving data from skin server." );
570 dataTranfer.receivedData = null;
575 dataTranfer.wait( sleep );
576 } catch ( InterruptedException e ) {
577 logger.log( Level.SEVERE, e.getMessage(), e );
581 logger.info( "wait data... count:" + count );
585 receivedData = dataTranfer.receivedData;
586 dataTranfer.receivedData = null;
594 public Socket getSocket() {
598 public synchronized boolean isSensorDaemonStarted() {
599 return isSensorDaemonStarted;
602 public synchronized void setRamdumpFlag(boolean flag) {
606 public synchronized boolean getRamdumpFlag() {
610 private void increaseHeartbeatCount() {
611 int count = heartbeatCount.incrementAndGet();
613 if (logger.isLoggable(Level.FINE)) {
614 logger.info("HB count : " + count);
618 private boolean isHeartbeatExpired() {
619 return HEART_BEAT_EXPIRE < heartbeatCount.get();
622 private void resetHeartbeatCount() {
623 heartbeatCount.set(0);
625 if (logger.isLoggable(Level.FINE)) {
626 logger.info("HB count reset");
631 public void terminate() {
633 logger.info("terminated");
635 if (null != sendQueue) {
636 synchronized (sendQueue) {
637 sendQueue.notifyAll();
641 if (null != heartbeatTimer) {
642 heartbeatTimer.cancel();
645 IOUtil.closeSocket(socket);
647 synchronized (this) {
652 public void resetSkin( EmulatorSkin skin ) {
653 synchronized ( this ) {
662 private SendCommand command;
663 private ISendData data;
665 public SkinSendData(SendCommand command, ISendData data) {
666 this.command = command;
670 public SendCommand getCommand() {
674 public ISendData getSendData() {