14d20e7367c6e8431f13b1173edbc6a0e1c8468d
[sdk/emulator/qemu.git] / tizen / src / skin / client / src / org / tizen / emulator / skin / comm / sock / SocketCommunicator.java
1 /**
2  * 
3  *
4  * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd. All rights reserved.
5  *
6  * Contact:
7  * GiWoong Kim <giwoong.kim@samsung.com>
8  * YeongKyoon Lee <yeongkyoon.lee@samsung.com>
9  * HyunJun Son
10  *
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.
15  *
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.
20  *
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.
24  *
25  * Contributors:
26  * - S-Core Co., Ltd
27  *
28  */
29
30 package org.tizen.emulator.skin.comm.sock;
31
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;
47
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;
58
59
60 /**
61  * 
62  * 
63  */
64 public class SocketCommunicator implements ICommunicator {
65
66         public class DataTranfer {
67
68                 private boolean isTransferState;
69                 private byte[] receivedData;
70
71                 private long sleep;
72                 private long maxWaitTime;
73                 private Timer timer;
74                 
75                 private DataTranfer() {
76                 }
77
78                 private void setData( byte[] data ) {
79                         this.receivedData = data;
80                         isTransferState = false;
81                 }
82
83         }
84
85         public static final int HEART_BEAT_INTERVAL = 1; //second
86         public static final int HEART_BEAT_EXPIRE = 15;
87
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
92
93         public final static int MAX_SEND_QUEUE_SIZE = 100000;
94
95         private static int reqId;
96
97         private Logger logger =
98                         SkinLogger.getSkinLogger(SocketCommunicator.class).getLogger();
99
100         private EmulatorConfig config;
101         private int uId;
102         private long initialData;
103         private EmulatorSkin skin;
104
105         private Socket socket;
106         private DataInputStream dis;
107         private DataOutputStream dos;
108
109         private AtomicInteger heartbeatCount;
110         private boolean isTerminated;
111         private boolean isSensorDaemonStarted;
112         private boolean isRamdump;
113         private TimerTask heartbeatExecutor;
114         private Timer heartbeatTimer;
115
116         private DataTranfer screenShotDataTransfer;
117         private DataTranfer detailInfoTransfer;
118         private DataTranfer progressDataTransfer;
119
120         private Thread sendThread;
121         private LinkedList<SkinSendData> sendQueue;
122
123         public SocketCommunicator(EmulatorConfig config, int uId, EmulatorSkin skin) {
124
125                 this.config = config;
126                 this.uId = uId;
127                 this.skin = skin;
128
129                 this.screenShotDataTransfer = new DataTranfer();
130                 this.screenShotDataTransfer.sleep = SCREENSHOT_WAIT_INTERVAL;
131                 this.screenShotDataTransfer.maxWaitTime = SCREENSHOT_WAIT_LIMIT;
132
133                 this.detailInfoTransfer = new DataTranfer();
134                 this.detailInfoTransfer.sleep = DETAIL_INFO_WAIT_INTERVAL;
135                 this.detailInfoTransfer.maxWaitTime = DETAIL_INFO_WAIT_LIMIT;
136
137                 this.progressDataTransfer = new DataTranfer();
138                 this.progressDataTransfer.sleep = SCREENSHOT_WAIT_INTERVAL;
139                 this.progressDataTransfer.maxWaitTime = SCREENSHOT_WAIT_LIMIT;
140
141                 this.heartbeatCount = new AtomicInteger(0);
142                 //this.heartbeatExecutor = Executors.newSingleThreadScheduledExecutor();
143                 this.heartbeatTimer = new Timer();
144
145                 try {
146
147                         int port = config.getArgInt( ArgsConstants.SERVER_PORT );
148                         socket = new Socket( "127.0.0.1", port );
149                         logger.info( "socket.isConnected():" + socket.isConnected() );
150
151                 } catch ( UnknownHostException e ) {
152                         logger.log( Level.SEVERE, e.getMessage(), e );
153                 } catch ( IOException e ) {
154                         logger.log( Level.SEVERE, e.getMessage(), e );
155                 }
156
157         }
158
159         public void setInitialData(long data) {
160                 this.initialData = data;
161         }
162
163         @Override
164         public void run() {
165
166                 sendQueue = new LinkedList<SkinSendData>();
167
168                 sendThread = new Thread("sendThread") {
169
170                         List<SkinSendData> list = new ArrayList<SkinSendData>();
171
172                         @Override
173                         public void run() {
174
175                                 while ( true ) {
176
177                                         synchronized ( sendQueue ) {
178
179                                                 if ( sendQueue.isEmpty() ) {
180                                                         try {
181                                                                 sendQueue.wait();
182                                                         } catch ( InterruptedException e ) {
183                                                                 logger.log( Level.SEVERE, e.getMessage(), e );
184                                                         }
185                                                 }
186
187                                                 SkinSendData sendData = null;
188                                                 while ( true ) {
189                                                         sendData = sendQueue.poll();
190                                                         if ( null != sendData ) {
191                                                                 list.add( sendData );
192                                                         } else {
193                                                                 break;
194                                                         }
195                                                 }
196
197                                         }
198
199                                         for ( SkinSendData data : list ) {
200                                                 sendToQEMUInternal( data );
201                                         }
202
203                                         list.clear();
204
205                                         if ( isTerminated ) {
206                                                 break;
207                                         }
208
209                                 }
210
211                         }
212                 };
213
214                 sendThread.start();
215
216                 try {
217
218                         dis = new DataInputStream( socket.getInputStream() );
219                         dos = new DataOutputStream( socket.getOutputStream() );
220
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;
228
229                         StartData startData = new StartData(initialData, width, height, scale, rotation);
230                         logger.info("StartData" + startData);
231
232                         sendToQEMU( SendCommand.SEND_START, startData );
233
234                 } catch ( IOException e ) {
235                         logger.log( Level.SEVERE, e.getMessage(), e );
236                         terminate();
237                         return;
238                 }
239
240                 boolean ignoreHeartbeat = config.getArgBoolean( ArgsConstants.TEST_HEART_BEAT_IGNORE );
241
242                 if (ignoreHeartbeat) {
243                         logger.info("Ignore Skin heartbeat.");
244                 } else {
245                         heartbeatExecutor = new TimerTask() {
246                                 @Override
247                                 public void run() {
248                                         increaseHeartbeatCount();
249
250                                         if (isHeartbeatExpired()) {
251                                                 logger.info("heartbeat was expired");
252                                                 terminate();
253                                         }
254                                 }
255                         };
256
257                         heartbeatTimer.schedule(heartbeatExecutor, 1, HEART_BEAT_INTERVAL * 1000);
258                 }
259
260                 while ( true ) {
261
262                         if ( isTerminated ) {
263                                 break;
264                         }
265
266                         try {
267
268                                 int reqId = dis.readInt();
269                                 short cmd = dis.readShort();
270                                 int length = dis.readInt();
271
272                                 if ( logger.isLoggable( Level.FINE ) ) {
273                                         logger.fine( "[Socket] read - reqId:" + reqId + ", command:" + cmd + ", dataLength:" + length );
274                                 }
275
276                                 ReceiveCommand command = null;
277
278                                 try {
279                                         command = ReceiveCommand.getValue( cmd );
280                                 } catch ( IllegalArgumentException e ) {
281                                         logger.severe( "unknown command:" + cmd );
282                                         continue;
283                                 }
284
285                                 switch ( command ) {
286                                 case HEART_BEAT: {
287                                         resetHeartbeatCount();
288                                         if ( logger.isLoggable( Level.FINE ) ) {
289                                                 logger.fine( "received HEAR_BEAT from QEMU." );
290                                         }
291                                         sendToQEMU( SendCommand.RESPONSE_HEART_BEAT, null );
292                                         break;
293                                 }
294                                 case SCREEN_SHOT_DATA: {
295                                         logger.info( "received SCREEN_SHOT_DATA from QEMU." );
296                                         receiveData( screenShotDataTransfer, length );
297
298                                         break;
299                                 }
300                                 case DETAIL_INFO_DATA: {
301                                         logger.info( "received DETAIL_INFO_DATA from QEMU." );
302                                         receiveData( detailInfoTransfer, length );
303
304                                         break;
305                                 }
306                                 case RAMDUMP_COMPLETE: {
307                                         logger.info("received RAMDUMP_COMPLETE from QEMU.");
308                                         setRamdumpFlag(false);
309                                         break;
310                                 }
311                                 case BOOTING_PROGRESS: {
312                                         logger.info("received BOOTING_PROGRESS from QEMU.");
313
314                                         resetDataTransfer(progressDataTransfer);
315                                         receiveData(progressDataTransfer, length);
316
317                                         byte[] receivedData = getReceivedData(progressDataTransfer);
318                                         if (null != receivedData) {
319                                                 String strValue = new String(receivedData, 0, length - 1);
320
321                                                 int value = 0;
322                                                 try {
323                                                         value = Integer.parseInt(strValue);
324                                                 } catch (NumberFormatException e) {
325                                                         e.printStackTrace();
326                                                 }
327
328                                                 /* draw progress bar */
329                                                 if (skin.bootingProgress != null) {
330                                                         skin.bootingProgress.setSelection(value);
331
332                                                         if (value == 100 | value == 0) {
333                                                                 /* this means progressbar will be
334                                                                 dispose soon */
335                                                                 if (skin.bootingProgress != null) {
336                                                                         skin.bootingProgress = null;
337                                                                 }
338                                                         }
339                                                 }
340                                         }
341
342                                         break;
343                                 }
344                                 case SENSOR_DAEMON_START: {
345                                         logger.info( "received SENSOR_DAEMON_START from QEMU." );
346                                         synchronized ( this ) {
347                                                 isSensorDaemonStarted = true;
348                                         }
349                                         break;
350                                 }
351                                 case SHUTDOWN: {
352                                         logger.info( "received RESPONSE_SHUTDOWN from QEMU." );
353                                         sendToQEMU( SendCommand.RESPONSE_SHUTDOWN, null );
354                                         terminate();
355                                         break;
356                                 }
357                                 default: {
358                                         logger.severe( "Unknown command from QEMU. command:" + cmd );
359                                         break;
360                                 }
361                                 }
362
363                         } catch ( IOException e ) {
364                                 logger.log( Level.SEVERE, e.getMessage(), e );
365                                 break;
366                         }
367
368                 }
369
370         }
371
372         private void receiveData(
373                         DataTranfer dataTransfer, int length) throws IOException {
374                 synchronized (dataTransfer) {
375
376                         if (null != dataTransfer.timer) {
377                                 dataTransfer.timer.cancel();
378                         }
379
380                         byte[] data = readData(dis, length);
381
382                         if (null != data) {
383                                 logger.info("finished receiving data from QEMU.");
384                         } else {
385                                 logger.severe("Fail to receiving data from QEMU.");
386                         }
387
388                         dataTransfer.isTransferState = false;
389                         dataTransfer.timer = null;
390                         
391                         dataTransfer.setData(data);
392                         dataTransfer.notifyAll();
393                 }
394
395         }
396
397         private byte[] readData( DataInputStream is, int length ) throws IOException {
398
399                 if ( 0 >= length ) {
400                         return null;
401                 }
402
403                 BufferedInputStream bfis = new BufferedInputStream( is, length );
404                 byte[] data = new byte[length];
405
406                 int read = 0;
407                 int total = 0;
408
409                 while ( true ) {
410
411                         if ( total == length ) {
412                                 break;
413                         }
414
415                         read = bfis.read( data, total, length - total );
416
417                         if ( 0 > read ) {
418                                 if ( total < length ) {
419                                         continue;
420                                 }
421                         } else {
422                                 total += read;
423                         }
424
425                 }
426
427                 logger.info( "finished reading stream. read:" + total );
428
429                 return data;
430
431         }
432
433         public synchronized DataTranfer sendToQEMU(
434                         SendCommand command, ISendData data, boolean useDataTransfer) {
435
436                 DataTranfer dataTranfer = null;
437                 
438                 if ( useDataTransfer ) {
439
440                         if ( SendCommand.SCREEN_SHOT.equals( command ) ) {
441                                 dataTranfer = resetDataTransfer( screenShotDataTransfer );
442                         } else if ( SendCommand.DETAIL_INFO.equals( command ) ) {
443                                 dataTranfer = resetDataTransfer( detailInfoTransfer );
444                         }
445                 }
446
447                 sendToQEMU( command, data );
448                 
449                 return dataTranfer;
450
451         }
452         
453         private DataTranfer resetDataTransfer( final DataTranfer dataTransfer ) {
454                 
455                 synchronized ( dataTransfer ) {
456                         
457                         if ( dataTransfer.isTransferState ) {
458                                 logger.severe( "Already transter state for getting data." );
459                                 return null;
460                         }
461
462                         dataTransfer.isTransferState = true;
463
464                         Timer timer = new Timer();
465                         dataTransfer.timer = timer;
466
467                         TimerTask timerTask = new TimerTask() {
468                                 @Override
469                                 public void run() {
470                                         synchronized ( dataTransfer ) {
471                                                 dataTransfer.isTransferState = false;
472                                                 dataTransfer.timer = null;
473                                                 dataTransfer.receivedData = null;
474                                         }
475                                 }
476                         };
477                         timer.schedule(timerTask, dataTransfer.maxWaitTime + 1000);
478
479                         return dataTransfer;
480
481                 }
482
483         }
484         
485         @Override
486         public void sendToQEMU( SendCommand command, ISendData data ) {
487
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." );
491                         } else {
492                                 sendQueue.add( new SkinSendData( command, data ) );
493                                 sendQueue.notifyAll();
494                         }
495                 }
496
497         }
498         
499         private void sendToQEMUInternal( SkinSendData sendData ) {
500
501                 try {
502
503                         if( null == sendData ) {
504                                 return;
505                         }
506                         
507                         SendCommand command = sendData.getCommand();
508                         ISendData data = sendData.getSendData();
509                         
510                         reqId = ( Integer.MAX_VALUE == reqId ) ? 0 : ++reqId;
511                         
512                         ByteArrayOutputStream bao = new ByteArrayOutputStream();
513                         DataOutputStream dataOutputStream = new DataOutputStream( bao );
514
515                         dataOutputStream.writeInt( uId );
516                         dataOutputStream.writeInt( reqId );
517                         dataOutputStream.writeShort( command.value() );
518
519                         short length = 0;
520                         if ( null == data ) {
521                                 length = 0;
522                                 dataOutputStream.writeShort( length );
523                         } else {
524                                 byte[] byteData = data.serialize();
525                                 length = (short) byteData.length;
526                                 dataOutputStream.writeShort( length );
527                                 dataOutputStream.write( byteData );
528                         }
529
530                         dataOutputStream.flush();
531
532                         dos.write( bao.toByteArray() );
533                         dos.flush();
534
535                         if ( logger.isLoggable( Level.FINE ) ) {
536                                 logger.fine( "[Socket] write - uid:" + uId + ", reqId:" + reqId + ", command:" + command.value()
537                                                 + " - " + command.toString() + ", length:" + length );
538                         }
539
540                         if ( 0 < length ) {
541                                 if ( logger.isLoggable( Level.FINE ) ) {
542                                         logger.fine( "[Socket] data  - " + data.toString() );
543                                 }
544                         }
545
546                 } catch ( IOException e ) {
547                         logger.log( Level.SEVERE, e.getMessage(), e );
548                 }
549
550         }
551
552         public byte[] getReceivedData( DataTranfer dataTranfer ) {
553
554                 if (null == dataTranfer) {
555                         return null;
556                 }
557
558                 synchronized (dataTranfer) {
559
560                         int count = 0;
561                         byte[] receivedData = null;
562                         long sleep = dataTranfer.sleep;
563                         long maxWaitTime = dataTranfer.maxWaitTime;
564                         int limitCount = (int) ( maxWaitTime / sleep );
565
566                         while ( dataTranfer.isTransferState ) {
567
568                                 if ( limitCount < count ) {
569                                         logger.severe( "time out for receiving data from skin server." );
570                                         dataTranfer.receivedData = null;
571                                         break;
572                                 }
573
574                                 try {
575                                         dataTranfer.wait( sleep );
576                                 } catch ( InterruptedException e ) {
577                                         logger.log( Level.SEVERE, e.getMessage(), e );
578                                 }
579
580                                 count++;
581                                 logger.info( "wait data... count:" + count );
582
583                         }
584
585                         receivedData = dataTranfer.receivedData;
586                         dataTranfer.receivedData = null;
587                         
588                         return receivedData;
589
590                 }
591
592         }
593         
594         public Socket getSocket() {
595                 return socket;
596         }
597
598         public synchronized boolean isSensorDaemonStarted() {
599                 return isSensorDaemonStarted;
600         }
601
602         public synchronized void setRamdumpFlag(boolean flag) {
603                 isRamdump = flag;
604         }
605
606         public synchronized boolean getRamdumpFlag() {
607                 return isRamdump;
608         }
609
610         private void increaseHeartbeatCount() {
611                 int count = heartbeatCount.incrementAndGet();
612
613                 if (logger.isLoggable(Level.FINE)) {
614                         logger.info("HB count : " + count);
615                 }
616         }
617
618         private boolean isHeartbeatExpired() {
619                 return HEART_BEAT_EXPIRE < heartbeatCount.get();
620         }
621
622         private void resetHeartbeatCount() {
623                 heartbeatCount.set(0);
624
625                 if (logger.isLoggable(Level.FINE)) {
626                         logger.info("HB count reset");
627                 }
628         }
629
630         @Override
631         public void terminate() {
632                 isTerminated = true;
633                 logger.info("terminated");
634
635                 if (null != sendQueue) {
636                         synchronized (sendQueue) {
637                                 sendQueue.notifyAll();
638                         }
639                 }
640
641                 if (null != heartbeatTimer) {
642                         heartbeatTimer.cancel();
643                 }
644
645                 IOUtil.closeSocket(socket);
646
647                 synchronized (this) {
648                         skin.shutdown();
649                 }
650         }
651
652         public void resetSkin( EmulatorSkin skin ) {
653                 synchronized ( this ) {
654                         this.skin = skin;
655                 }
656         }
657
658 }
659
660 class SkinSendData {
661
662         private SendCommand command;
663         private ISendData data;
664
665         public SkinSendData(SendCommand command, ISendData data) {
666                 this.command = command;
667                 this.data = data;
668         }
669
670         public SendCommand getCommand() {
671                 return command;
672         }
673
674         public ISendData getSendData() {
675                 return data;
676         }
677
678 }