[Title] sdblib merged into common-eplugin.git from sdblib.git
authorhyunsik.noh <hyunsik.noh@samsung.com>
Thu, 22 Nov 2012 07:26:32 +0000 (16:26 +0900)
committerhyunsik.noh <hyunsik.noh@samsung.com>
Fri, 23 Nov 2012 05:47:16 +0000 (14:47 +0900)
[Type]
[Module]common
[Priority]
[CQ#]
[Redmine#]
[Problem]
[Cause]
[Solution]
[TestCase]

Change-Id: I0a6c77c760bee0c9fdba915326818017a09321b7

39 files changed:
.gitignore
org.tizen.common.connection/META-INF/MANIFEST.MF
org.tizen.common.feature/feature.xml
org.tizen.common.sdblib/.classpath [new file with mode: 0644]
org.tizen.common.sdblib/.project [new file with mode: 0644]
org.tizen.common.sdblib/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.tizen.common.sdblib/META-INF/MANIFEST.MF [new file with mode: 0644]
org.tizen.common.sdblib/build.properties [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/ArrayHelper.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/Device.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/DeviceMonitor.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/FileListingService.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/IDevice.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/IShellOutputReceiver.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/Log.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/LogReceiver.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/MultiLineReceiver.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/NullOutputReceiver.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/SdbCommandRejectedException.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/SdbHelper.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/SdbPreferences.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/SdbShellProcess.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/ShellCommandUnresponsiveException.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/SmartDevelopmentBridge.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/SyncService.java [new file with mode: 0644]
org.tizen.common.sdblib/src/org/tizen/sdblib/TimeoutException.java [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/asm-4.0.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/asm-tree-4.0.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/javassist-3.16.1-GA.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/junit-4.10-src.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/junit-4.10.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/mockito-all-1.9.0.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/lib/powermock-mockito-1.4.12-full.jar [new file with mode: 0644]
org.tizen.common.sdblib/test/src/org/tizen/sdblib/TransferInfoTest.java [new file with mode: 0644]
org.tizen.common/.classpath
org.tizen.common/META-INF/MANIFEST.MF
org.tizen.common/lib/sdblib.jar [deleted file]
package/build.linux
package/pkginfo.manifest

index 9e3955f..e5fa4bd 100644 (file)
@@ -4,7 +4,8 @@ bin
 *.class
 common-eplugin_*.tar.gz
 common-eplugin_*_*.zip
-common-eplugin.package.linux
-common-eplugin.package.windows
+sdblib_*_*.zip
+sdblib.package.*
+common-eplugin.package.*
 #log files
 .log
index f3a3e07..71ab8a0 100644 (file)
@@ -12,7 +12,8 @@ Require-Bundle: org.eclipse.ui,
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Bundle-ActivationPolicy: lazy
 Import-Package: org.eclipse.ui.console,
- org.tizen.common.util
+ org.tizen.common.util,
+ org.tizen.sdblib
 Bundle-ClassPath: .
 Export-Package: org.tizen.common.connection;
   uses:="org.eclipse.jface.resource,
index 19475de..1113880 100644 (file)
@@ -63,4 +63,11 @@ BY CLICKING THE &quot;I AGREE&quot; BUTTON OR BY USING ANY PART OF TIZEN SDK, YO
          version="0.0.0"
          unpack="false"/>
 
+   <plugin
+         id="org.tizen.common.sdblib"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
 </feature>
diff --git a/org.tizen.common.sdblib/.classpath b/org.tizen.common.sdblib/.classpath
new file mode 100644 (file)
index 0000000..ad32c83
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.tizen.common.sdblib/.project b/org.tizen.common.sdblib/.project
new file mode 100644 (file)
index 0000000..20024d8
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.tizen.common.sdblib</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.tizen.common.sdblib/.settings/org.eclipse.jdt.core.prefs b/org.tizen.common.sdblib/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..db78d86
--- /dev/null
@@ -0,0 +1,8 @@
+#Thu Nov 22 14:29:16 KST 2012
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/org.tizen.common.sdblib/META-INF/MANIFEST.MF b/org.tizen.common.sdblib/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..d0e3a0b
--- /dev/null
@@ -0,0 +1,7 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Sdblib
+Bundle-SymbolicName: org.tizen.common.sdblib
+Bundle-Version: 1.0.0.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Export-Package: org.tizen.sdblib
diff --git a/org.tizen.common.sdblib/build.properties b/org.tizen.common.sdblib/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/ArrayHelper.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/ArrayHelper.java
new file mode 100644 (file)
index 0000000..1c60c08
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+/**
+ * Utility class providing array to int/long conversion for data received from devices through sdb. 
+ */
+public final class ArrayHelper {
+
+    /**
+     * Swaps an unsigned value around, and puts the result in an array that can be sent to a device.
+     * @param value The value to swap.
+     * @param dest the destination array
+     * @param offset the offset in the array where to put the swapped value.
+     *      Array length must be at least offset + 4
+     */
+    public static void swap32bitsToArray(int value, byte[] dest, int offset) {
+        dest[offset] = (byte)(value & 0x000000FF);
+        dest[offset + 1] = (byte)((value & 0x0000FF00) >> 8);
+        dest[offset + 2] = (byte)((value & 0x00FF0000) >> 16);
+        dest[offset + 3] = (byte)((value & 0xFF000000) >> 24);
+    }
+
+    /**
+     * Reads a signed 32 bit integer from an array coming from a device.
+     * @param value the array containing the int
+     * @param offset the offset in the array at which the int starts
+     * @return the integer read from the array
+     */
+    public static int swap32bitFromArray(byte[] value, int offset) {
+        int v = 0;
+        v |= ((int)value[offset]) & 0x000000FF;
+        v |= (((int)value[offset + 1]) & 0x000000FF) << 8;
+        v |= (((int)value[offset + 2]) & 0x000000FF) << 16;
+        v |= (((int)value[offset + 3]) & 0x000000FF) << 24;
+
+        return v;
+    }
+    
+    /**
+     * Reads an unsigned 16 bit integer from an array coming from a device,
+     * and returns it as an 'int'
+     * @param value the array containing the 16 bit int (2 byte).
+     * @param offset the offset in the array at which the int starts
+     *      Array length must be at least offset + 2
+     * @return the integer read from the array.
+     */
+    public static int swapU16bitFromArray(byte[] value, int offset) {
+        int v = 0;
+        v |= ((int)value[offset]) & 0x000000FF;
+        v |= (((int)value[offset + 1]) & 0x000000FF) << 8;
+
+        return v;
+    }
+    
+    /**
+     * Reads a signed 64 bit integer from an array coming from a device.
+     * @param value the array containing the int
+     * @param offset the offset in the array at which the int starts
+     *      Array length must be at least offset + 8
+     * @return the integer read from the array
+     */
+    public static long swap64bitFromArray(byte[] value, int offset) {
+        long v = 0;
+        v |= ((long)value[offset]) & 0x00000000000000FFL;
+        v |= (((long)value[offset + 1]) & 0x00000000000000FFL) << 8;
+        v |= (((long)value[offset + 2]) & 0x00000000000000FFL) << 16;
+        v |= (((long)value[offset + 3]) & 0x00000000000000FFL) << 24;
+        v |= (((long)value[offset + 4]) & 0x00000000000000FFL) << 32;
+        v |= (((long)value[offset + 5]) & 0x00000000000000FFL) << 40;
+        v |= (((long)value[offset + 6]) & 0x00000000000000FFL) << 48;
+        v |= (((long)value[offset + 7]) & 0x00000000000000FFL) << 56;
+
+        return v;
+    }
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/Device.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/Device.java
new file mode 100644 (file)
index 0000000..e2b9aa8
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.tizen.sdblib.SyncService.SyncResult;
+
+
+
+/**
+ * A Device. It can be a physical device or an emulator.
+ */
+final class Device implements IDevice {
+       
+    private final static int INSTALL_TIMEOUT = 2*60*1000; //2min
+
+    /** Emulator Serial Number regexp. */
+    final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
+    
+    final static String COMMAND_ARCHITECTURE_INFO = "uname -m";
+
+    /** Serial number of the device */
+    private String mSerialNumber = null;
+
+    private Arch mArch = Arch.ARM;
+
+    private String mDeviceName = null;
+
+    /** State of the device. */
+    private DeviceState mState = null;
+
+    private DeviceMonitor mMonitor;
+
+    private static final String LOG_TAG = "Device";
+    
+    private SdbShellProcess sdbShellProcess = null;
+
+    public String getSerialNumber() {
+        return mSerialNumber;
+    }
+
+    @Override
+    public String getDeviceName() {
+        return mDeviceName;
+    }
+
+    public DeviceState getState() {
+        return mState;
+    }
+
+    /**
+     * Changes the state of the device.
+     */
+    void setState(DeviceState state) {
+        mState = state;
+    }
+
+    @Override
+    public String toString() {
+        return mSerialNumber;
+    }
+
+    public boolean isOnline() {
+        return mState == DeviceState.ONLINE;
+    }
+
+    public boolean isEmulator() {
+        return mSerialNumber.matches(RE_EMULATOR_SN);
+    }
+
+    public boolean isOffline() {
+        return mState == DeviceState.OFFLINE;
+    }
+
+    public SyncService getSyncService()
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+        SyncService syncService = new SyncService(SmartDevelopmentBridge.getSocketAddress(), this);
+        if (syncService.openSync()) {
+            return syncService;
+         }
+
+        return null;
+    }
+
+    public FileListingService getFileListingService() {
+        return new FileListingService(this);
+    }
+    
+    public SdbShellProcess executeShellCommand(String command) throws IOException {
+       SmartDevelopmentBridge sdb = SmartDevelopmentBridge.getBridge();
+       if (sdb == null)
+               return null;
+       String sdbPath = sdb.getSdbOsLocation();
+       if (sdbPath == null)
+               return null;
+       if (mSerialNumber == null)
+               return null;
+       
+       String sdbShellCmd = sdbPath + " -s " + mSerialNumber + " shell";
+       Process pSdb = Runtime.getRuntime().exec(sdbShellCmd);
+       sdbShellProcess = new SdbShellProcess(pSdb, command);
+       return sdbShellProcess;
+    }
+    
+    public void executeShellCommand(String command, IShellOutputReceiver receiver)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException {
+        SdbHelper.executeRemoteCommand(SmartDevelopmentBridge.getSocketAddress(), command, this,
+                receiver, SdbPreferences.getTimeOut());
+    }
+
+    public void executeShellCommand(String command, IShellOutputReceiver receiver,
+            int maxTimeToOutputResponse)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException {
+        SdbHelper.executeRemoteCommand(SmartDevelopmentBridge.getSocketAddress(), command, this,
+                receiver, maxTimeToOutputResponse);
+    }
+
+    public void runEventLogService(LogReceiver receiver)
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+        SdbHelper.runEventLogService(SmartDevelopmentBridge.getSocketAddress(), this, receiver);
+    }
+
+    public void runLogService(String logname, LogReceiver receiver)
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+        SdbHelper.runLogService(SmartDevelopmentBridge.getSocketAddress(), this, logname, receiver);
+    }
+
+    public void createForward(int localPort, int remotePort)
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+        SdbHelper.createForward(SmartDevelopmentBridge.getSocketAddress(), this, localPort, remotePort);
+    }
+
+    public void removeForward(int localPort, int remotePort)
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+        SdbHelper.removeForward(SmartDevelopmentBridge.getSocketAddress(), this, localPort, remotePort);
+    }
+
+    Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState) {
+        mMonitor = monitor;
+        mSerialNumber = serialNumber;
+        mState = deviceState;
+    }
+
+    Device(DeviceMonitor monitor, String serialNumber, DeviceState deviceState, String deviceName) {
+        mMonitor = monitor;
+        mSerialNumber = serialNumber;
+        mState = deviceState;
+        this.mDeviceName = deviceName;
+    }
+
+    DeviceMonitor getMonitor() {
+        return mMonitor;
+    }
+
+    void update(int changeMask) {
+        mMonitor.getServer().deviceChanged(this, changeMask);
+    }
+
+    public String syncPackageToDevice(String localFilePath)
+            throws IOException, SdbCommandRejectedException, TimeoutException {
+        try {
+            String packageFileName = getFileName(localFilePath);
+            String remoteFilePath = String.format("/opt/apps/tmp/%1$s", packageFileName); //$NON-NLS-1$
+
+            Log.d(packageFileName, String.format("Uploading %1$s onto device '%2$s'",
+                    packageFileName, getSerialNumber()));
+
+            SyncService sync = getSyncService();
+            if (sync != null) {
+                String message = String.format("Uploading file onto device '%1$s'",
+                        getSerialNumber());
+                Log.d(LOG_TAG, message);
+                SyncResult result = sync.pushFile(localFilePath, remoteFilePath,
+                        SyncService.getNullProgressMonitor());
+
+                if (result.getCode() != SyncService.RESULT_OK) {
+                    throw new IOException(String.format("Unable to upload file: %1$s",
+                            result.getMessage()));
+                }
+            } else {
+                throw new IOException("Unable to open sync connection!");
+            }
+            return remoteFilePath;
+        } catch (TimeoutException e) {
+            Log.e(LOG_TAG, "Unable to open sync connection! Timeout.");
+            throw e;
+        } catch (IOException e) {
+            Log.e(LOG_TAG, String.format("Unable to open sync connection! reason: %1$s",
+                    e.getMessage()));
+            throw e;
+        }
+    }
+
+    /**
+     * Helper method to retrieve the file name given a local file path
+     * @param filePath full directory path to file
+     * @return {@link String} file name
+     */
+    private String getFileName(String filePath) {
+        return new File(filePath).getName();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeRemotePackage(String remoteFilePath)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException {
+        // now we delete the app we sync'ed
+        try {
+            executeShellCommand("rm " + remoteFilePath, new NullOutputReceiver(), INSTALL_TIMEOUT);
+        } catch (IOException e) {
+            Log.e(LOG_TAG, String.format("Failed to delete temporary package: %1$s",
+                    e.getMessage()));
+            throw e;
+        }
+    }
+
+    public Arch getArch()
+    {
+        return mArch;
+    }
+
+    public void checkArchitecture()
+    {
+        try
+        {
+            executeShellCommand(COMMAND_ARCHITECTURE_INFO, new ArchInfoReceiver());
+        } catch (Exception e)
+        {
+         // sdb failed somehow, we do nothing.
+        }
+    }
+
+    class ArchInfoReceiver extends MultiLineReceiver
+    {
+        @Override
+        public void processNewLines(String[] lines)
+        {
+            if(lines[0] != null && !lines[0].contains(Arch.ARM.getArch()))
+            {
+                mArch = Arch.X86;
+            }
+        }
+    }
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/DeviceMonitor.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/DeviceMonitor.java
new file mode 100644 (file)
index 0000000..bcc82cf
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+
+import org.tizen.sdblib.Device.ArchInfoReceiver;
+import org.tizen.sdblib.IDevice.DeviceState;
+import org.tizen.sdblib.SdbHelper.SdbResponse;
+
+
+/**
+ * A Device monitor. This connects to the Smart Development Bridge and get device.
+ */
+final class DeviceMonitor {
+    private byte[] mLengthBuffer = new byte[4];
+
+    private boolean mQuit = false;
+
+    private SmartDevelopmentBridge mServer;
+
+    private SocketChannel mMainSdbConnection = null;
+    private boolean mMonitoring = false;
+    private int mConnectionAttempt = 0;
+    private int mRestartAttemptCount = 0;
+    private boolean mInitialDeviceListDone = false;
+
+    private Selector mSelector;
+    ArchInfoReceiver receiver = null;
+    private final ArrayList<Device> mDevices = new ArrayList<Device>();
+
+    /**
+     * Creates a new {@link DeviceMonitor} object and links it to the running
+     * {@link SmartDevelopmentBridge} object.
+     * @param server the running {@link SmartDevelopmentBridge}.
+     */
+    DeviceMonitor(SmartDevelopmentBridge server) {
+        mServer = server;
+    }
+
+    /**
+     * Starts the monitoring.
+     */
+    void start() {
+        new Thread("Device List Monitor") { //$NON-NLS-1$
+            @Override
+            public void run() {
+                deviceMonitorLoop();
+            }
+        }.start();
+    }
+
+    /**
+     * Stops the monitoring.
+     */
+    void stop() {
+        mQuit = true;
+
+        // wake up the main loop thread by closing the main connection to sdb.
+        try {
+            if (mMainSdbConnection != null) {
+                mMainSdbConnection.close();
+            }
+        } catch (IOException e1) {
+        }
+
+        // wake up the secondary loop by closing the selector.
+        if (mSelector != null) {
+            mSelector.wakeup();
+        }
+    }
+
+
+
+    /**
+     * Returns if the monitor is currently connected to the debug bridge server.
+     * @return
+     */
+    boolean isMonitoring() {
+        return mMonitoring;
+    }
+
+    int getConnectionAttemptCount() {
+        return mConnectionAttempt;
+    }
+
+    int getRestartAttemptCount() {
+        return mRestartAttemptCount;
+    }
+
+    /**
+     * Returns the devices.
+     */
+    Device[] getDevices() {
+        synchronized (mDevices) {
+            return mDevices.toArray(new Device[mDevices.size()]);
+        }
+    }
+
+    boolean hasInitialDeviceList() {
+        return mInitialDeviceListDone;
+    }
+
+    SmartDevelopmentBridge getServer() {
+        return mServer;
+    }
+
+    /**
+     * Monitors the devices. This connects to the Debug Bridge
+     */
+    private void deviceMonitorLoop() {
+        do {
+            try {
+                if (mMainSdbConnection == null) {
+                    Log.d("DeviceMonitor", "Opening sdb connection");
+                    mMainSdbConnection = openSdbConnection();
+                    if (mMainSdbConnection == null) {
+                        if (mServer.startSdb() == false) {
+                            mRestartAttemptCount++;
+                            Log.e("DeviceMonitor",
+                                    "sdb restart attempts: " + mRestartAttemptCount);
+                        }
+                        waitABit();
+                    } else {
+                        Log.d("DeviceMonitor", "Connected to sdb for device monitoring");
+                    }
+                }
+
+                if (mMainSdbConnection != null && mMonitoring == false) {
+                    mMonitoring = sendDeviceListMonitoringRequest();
+                }
+
+                if (mMonitoring) {
+                    // read the length of the incoming message
+                    int length = readLength(mMainSdbConnection, mLengthBuffer);
+
+                    if (length >= 0) {
+                        // read the incoming message
+                        processIncomingDeviceData(length);
+
+                        // flag the fact that we have build the list at least once.
+                        mInitialDeviceListDone = true;
+                    }
+                }
+            } catch (AsynchronousCloseException ace) {
+                // this happens because of a call to Quit. We do nothing, and the loop will break.
+            } catch (TimeoutException ioe) {
+                handleExpectioninMonitorLoop(ioe);
+            } catch (IOException ioe) {
+                handleExpectioninMonitorLoop(ioe);
+            }
+        } while (mQuit == false);
+    }
+
+    private void handleExpectioninMonitorLoop(Exception e) {
+        if (mQuit == false) {
+            if (e instanceof TimeoutException) {
+                Log.e("DeviceMonitor", "Sdb connection Error: timeout");
+            } else {
+                Log.e("DeviceMonitor", "Sdb connection Error:" + e.getMessage());
+            }
+            mMonitoring = false;
+            if (mMainSdbConnection != null) {
+                try {
+                    mMainSdbConnection.close();
+                } catch (IOException ioe) {
+                    // we can safely ignore that one.
+                }
+                mMainSdbConnection = null;
+            }
+        }
+    }
+
+    /**
+     * Sleeps for a little bit.
+     */
+    private void waitABit() {
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e1) {
+        }
+    }
+
+    /**
+     * Attempts to connect to the debug bridge server.
+     * @return a connect socket if success, null otherwise
+     */
+    private SocketChannel openSdbConnection() {
+        Log.d("DeviceMonitor", "Connecting to sdb for Device List Monitoring...");
+
+        SocketChannel sdbChannel = null;
+        try {
+            sdbChannel = SocketChannel.open(SmartDevelopmentBridge.getSocketAddress());
+            sdbChannel.socket().setTcpNoDelay(true);
+        } catch (IOException e) {
+        }
+
+        return sdbChannel;
+    }
+
+    /**
+     *
+     * @return
+     * @throws IOException
+     */
+    private boolean sendDeviceListMonitoringRequest() throws TimeoutException, IOException {
+        byte[] request = SdbHelper.formSdbRequest("host:track-devices"); //$NON-NLS-1$
+
+        try {
+            SdbHelper.write(mMainSdbConnection, request);
+
+            SdbResponse resp = SdbHelper.readSdbResponse(mMainSdbConnection,
+                    false /* readDiagString */);
+
+            if (resp.okay == false) {
+                // request was refused by sdb!
+                Log.e("DeviceMonitor", "sdb refused request: " + resp.message);
+            }
+
+            return resp.okay;
+        } catch (IOException e) {
+            Log.e("DeviceMonitor", "Sending Tracking request failed!");
+            mMainSdbConnection.close();
+            throw e;
+        }
+    }
+
+    /**
+     * Processes an incoming device message from the socket
+     * @param socket
+     * @param length
+     * @throws IOException
+     */
+    private void processIncomingDeviceData(int length) throws IOException {
+        ArrayList<Device> list = new ArrayList<Device>();
+
+        if (length > 0) {
+            byte[] buffer = new byte[length];
+            String result = read(mMainSdbConnection, buffer);
+
+            String[] devices = result.split("\n"); // $NON-NLS-1$
+
+            for (String d : devices) {
+                String[] param = d.split("\t"); // $NON-NLS-1$
+                if (param.length == 3) {
+                    // new sdb uses only serial numbers to identify devices
+                    Device device = new Device(this, param[0].trim() /*serialnumber*/,
+                            DeviceState.getState(param[1].trim()),
+                            param[2].trim() /*device name*/);
+
+                    //add the device to the list
+                    list.add(device);
+                }
+            }
+        }
+
+        // now merge the new devices with the old ones.
+        updateDevices(list);
+    }
+
+    /**
+     *  Updates the device list with the new items received from the monitoring service.
+     */
+    private void updateDevices(ArrayList<Device> newList) {
+        // because we are going to call mServer.deviceDisconnected which will acquire this lock
+        // we lock it first, so that the SmartDevelopmentBridge lock is always locked first.
+        synchronized (SmartDevelopmentBridge.getLock()) {
+            // array to store the devices that must be queried for information.
+            // it's important to not do it inside the synchronized loop as this could block
+            // the whole workspace (this lock is acquired during build too).
+            ArrayList<Device> devicesToQuery = new ArrayList<Device>();
+            synchronized (mDevices) {
+                // For each device in the current list, we look for a matching the new list.
+                // * if we find it, we update the current object with whatever new information
+                //   there is
+                //   (mostly state change, if the device becomes ready, we query for build info).
+                //   We also remove the device from the new list to mark it as "processed"
+                // * if we do not find it, we remove it from the current list.
+                // Once this is done, the new list contains device we aren't monitoring yet, so we
+                // add them to the list, and start monitoring them.
+                for (int d = 0 ; d < mDevices.size() ;) {
+                    Device device = mDevices.get(d);
+                    // look for a similar device in the new list.
+                    int count = newList.size();
+                    boolean foundMatch = false;
+                    for (int dd = 0 ; dd < count ; dd++) {
+                        Device newDevice = newList.get(dd);
+                        // see if it matches in id and serial number.
+                        if (newDevice.getSerialNumber().equals(device.getSerialNumber())) {
+                            foundMatch = true;
+
+                            // update the state if needed.
+                            if (device.getState() != newDevice.getState()) {
+                                device.setState(newDevice.getState());
+                                device.update(Device.CHANGE_STATE);
+                                //should to check architecture if device is an emulator and state changed from "off-lined" to "on-lined"
+                                if(device.isEmulator() && device.getState() == DeviceState.ONLINE)
+                                {
+                                    device.checkArchitecture();
+                                }
+                            }
+                            // remove the new device from the list since it's been used
+                            newList.remove(dd);
+                            break;
+                        }
+                    }
+
+                    if (foundMatch == false) {
+                        // the device is gone, we need to remove it, and keep current index
+                        // to process the next one.
+                        removeDevice(device);
+                        mServer.deviceDisconnected(device);
+                    } else {
+                        // process the next one
+                        d++;
+                    }
+                }
+
+                // at this point we should still have some new devices in newList, so we
+                // process them.
+                for (Device newDevice : newList) {
+                    //should to check architecture if new device is an emulator on-lined
+                    if(newDevice.isOnline() && newDevice.isEmulator())
+                    {
+                        newDevice.checkArchitecture();
+                    }
+                    // add them to the list
+                    mDevices.add(newDevice);
+                    mServer.deviceConnected(newDevice);
+
+                    // look for their build info.
+                    if (newDevice.isOnline()) {
+                        devicesToQuery.add(newDevice);
+                    }
+                }
+            }
+        }
+        newList.clear();
+    }
+
+    private void removeDevice(Device device) {
+        mDevices.remove(device);
+    }
+
+
+    /**
+     * Reads the length of the next message from a socket.
+     * @param socket The {@link SocketChannel} to read from.
+     * @return the length, or 0 (zero) if no data is available from the socket.
+     * @throws IOException if the connection failed.
+     */
+    private int readLength(SocketChannel socket, byte[] buffer) throws IOException {
+        String msg = read(socket, buffer);
+
+        if (msg != null) {
+            try {
+                return Integer.parseInt(msg, 16);
+            } catch (NumberFormatException nfe) {
+                // we'll throw an exception below.
+            }
+       }
+
+        // we receive something we can't read. It's better to reset the connection at this point.
+        throw new IOException("Unable to read length");
+    }
+
+    /**
+     * Fills a buffer from a socket.
+     * @param socket
+     * @param buffer
+     * @return the content of the buffer as a string, or null if it failed to convert the buffer.
+     * @throws IOException
+     */
+    private String read(SocketChannel socket, byte[] buffer) throws IOException {
+        ByteBuffer buf = ByteBuffer.wrap(buffer, 0, buffer.length);
+
+        while (buf.position() != buf.limit()) {
+            int count;
+
+            count = socket.read(buf);
+            if (count < 0) {
+                throw new IOException("EOF");
+            }
+        }
+
+        try {
+            return new String(buffer, 0, buf.position(), SdbHelper.DEFAULT_ENCODING);
+        } catch (UnsupportedEncodingException e) {
+            // we'll return null below.
+        }
+
+        return null;
+    }
+
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/FileListingService.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/FileListingService.java
new file mode 100644 (file)
index 0000000..be6e84a
--- /dev/null
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Provides {@link Device} side file listing service.
+ * <p/>
+ * To get an instance for a known {@link Device}, call
+ * {@link Device#getFileListingService()}.
+ */
+public final class FileListingService
+{
+
+    /** Pattern to find filenames that match "*.apk" */
+    private final static Pattern sApkPattern = Pattern.compile(".*\\.apk", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
+
+    private final static String PM_FULL_LISTING = "pm list packages -f"; //$NON-NLS-1$
+
+    /**
+     * Pattern to parse the output of the 'pm -lf' command.<br>
+     * The output format looks like:<br>
+     * /data/app/myapp.apk=com.mypackage.myapp
+     */
+    private final static Pattern sPmPattern = Pattern.compile("^package:(.+?)=(.+)$"); //$NON-NLS-1$
+
+    /** Top level data folder. */
+    public final static String DIRECTORY_DATA = "data"; //$NON-NLS-1$
+    /** Top level sdcard folder. */
+    public final static String DIRECTORY_SDCARD = "sdcard"; //$NON-NLS-1$
+    /** Top level mount folder. */
+    public final static String DIRECTORY_MNT = "mnt"; //$NON-NLS-1$
+    /** Top level system folder. */
+    public final static String DIRECTORY_SYSTEM = "system"; //$NON-NLS-1$
+    /** Top level temp folder. */
+    public final static String DIRECTORY_TEMP = "tmp"; //$NON-NLS-1$
+    /** Application folder. */
+    public final static String DIRECTORY_APP = "app"; //$NON-NLS-1$
+
+    public static final long REFRESH_RATE = 5000L;
+    /**
+     * Refresh test has to be slightly lower for precision issue.
+     */
+    static final long REFRESH_TEST = (long) (REFRESH_RATE * .8);
+
+    /** Entry type: File */
+    public static final int TYPE_FILE = 0;
+    /** Entry type: Directory */
+    public static final int TYPE_DIRECTORY = 1;
+    /** Entry type: Directory Link */
+    public static final int TYPE_DIRECTORY_LINK = 2;
+    /** Entry type: Block */
+    public static final int TYPE_BLOCK = 3;
+    /** Entry type: Character */
+    public static final int TYPE_CHARACTER = 4;
+    /** Entry type: Link */
+    public static final int TYPE_LINK = 5;
+    /** Entry type: Socket */
+    public static final int TYPE_SOCKET = 6;
+    /** Entry type: FIFO */
+    public static final int TYPE_FIFO = 7;
+    /** Entry type: Other */
+    public static final int TYPE_OTHER = 8;
+    /** Entry type: root */
+    public static final int TYPE_ROOT_EMULATOR = 9;
+    public static final int TYPE_ROOT_DEVICE = 10;
+
+    /** Device side file separator. */
+    public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
+
+    private static final String FILE_ROOT = "/"; //$NON-NLS-1$
+
+    /**
+     * Regexp pattern to parse the result from ls. Do not think the same format
+     * between Emulator and Device such as "ls -l --time-style=long-iso"
+     */
+
+    private static String LS_COMMAND_TIMESTYLE = " --time-style=long-iso" ;
+
+//    private static String patternAnsiColor = "\\033\\[[0-9;]*m";
+    private static String patternPermisions = "([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])";
+    private static String patternFilecount = "(\\d{1,})";
+    private static String patternOwner = "(\\S+)";
+    private static String patternGroup = "(\\S+)";
+    private static String patternSize = "([\\d\\s,]*)";
+    private static String patternDate = "(\\d{4}-\\d{2}-\\d{2}|[a-zA-Z]{3}\\s+\\d{1,2})";
+    private static String patternTime = "(\\d{1,2}:\\d{1,2}|[0-9]{4})";
+    private static String patternName = "(.*)";
+    private static String patternLs = "^" + patternPermisions + "\\s+" + patternFilecount + "\\s+" + patternOwner + "\\s+" + patternGroup + "\\s+"
+            + patternSize + "\\s+" + patternDate + "\\s+" + patternTime + "\\s" + patternName + "$";
+
+    // private static Pattern sLsPattern = Pattern.compile(
+    //   "^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+(\\S+)\\s+(\\S+)\\s+([\\d\\s,]*)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$
+    private static Pattern sLsPattern = Pattern.compile(patternLs);
+//    private static Pattern ansiColorReplacePattern = Pattern.compile(patternAnsiColor);
+    private final Device mDevice;
+    private FileEntry mRoot;
+
+    private final ArrayList<Thread> mThreadList = new ArrayList<Thread>();
+
+    /**
+     * Represents an entry in a directory. This can be a file or a directory.
+     */
+    public final static class FileEntry
+    {
+        /** Pattern to escape filenames for shell command consumption. */
+        private final static Pattern sEscapePattern = Pattern.compile("([\\\\\"$])"); //$NON-NLS-1$
+
+        /**
+         * Comparator object for FileEntry
+         */
+        private static Comparator<FileEntry> sEntryComparator = new Comparator<FileEntry>()
+        {
+            @Override
+            public int compare(FileEntry o1, FileEntry o2)
+            {
+                if (o1 instanceof FileEntry && o2 instanceof FileEntry)
+                {
+                    FileEntry fe1 = (FileEntry) o1;
+                    FileEntry fe2 = (FileEntry) o2;
+                    return fe1.name.compareTo(fe2.name);
+                }
+                return 0;
+            }
+        };
+
+        FileEntry parent;
+        String name;
+        String info;
+        String permissions;
+        String size;
+        String date;
+        String time;
+        String owner;
+        String group;
+        String linkPath;
+        FileListingService fileListingService;
+        int type;
+        boolean isAppPackage;
+
+        boolean isRoot;
+
+        /**
+         * Indicates whether the entry content has been fetched yet, or not.
+         */
+        long fetchTime = 0;
+
+        final ArrayList<FileEntry> mChildren = new ArrayList<FileEntry>();
+
+        /**
+         * Creates a new file entry.
+         *
+         * @param parent
+         *            parent entry or null if entry is root
+         * @param name
+         *            name of the entry.
+         * @param type
+         *            entry type. Can be one of the following:
+         *            {@link FileListingService#TYPE_FILE},
+         *            {@link FileListingService#TYPE_DIRECTORY},
+         *            {@link FileListingService#TYPE_OTHER}.
+         */
+        public FileEntry(FileEntry parent, String name, int type, boolean isRoot, FileListingService service)
+        {
+            this.parent = parent;
+            this.name = name;
+            this.type = type;
+            this.isRoot = isRoot;
+            this.fileListingService = service;
+        }
+
+        /**
+         * Returns the name of the entry
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        public void setName(String name)
+        {
+            this.name = name;
+        }
+
+        /**
+         * Returns the size string of the entry, as returned by <code>ls</code>.
+         */
+        public String getSize()
+        {
+            return size;
+        }
+
+        /**
+         * Returns the size of the entry.
+         */
+        public int getSizeValue()
+        {
+            return Integer.parseInt(size);
+        }
+
+        /**
+         * Returns the date string of the entry, as returned by <code>ls</code>.
+         */
+        public String getDate()
+        {
+            return date;
+        }
+
+        /**
+         * Returns the time string of the entry, as returned by <code>ls</code>.
+         */
+        public String getTime()
+        {
+            return time;
+        }
+
+        /**
+         * Returns the permission string of the entry, as returned by
+         * <code>ls</code>.
+         */
+        public String getPermissions()
+        {
+            return permissions;
+        }
+
+        /**
+         * Returns the extra info for the entry.
+         * <p/>
+         * For a link, it will be a description of the link.
+         * <p/>
+         * For an application apk file it will be the application package as
+         * returned by the Package Manager.
+         */
+        public String getInfo()
+        {
+            return info;
+        }
+
+        public String getLinkFullPath()
+        {
+            return linkPath;
+        }
+
+        /**
+         * Return the full path of the entry.
+         *
+         * @return a path string using {@link FileListingService#FILE_SEPARATOR}
+         *         as separator.
+         */
+        public String getFullPath()
+        {
+            if (isRoot)
+            {
+                return FILE_ROOT;
+            }
+            StringBuilder pathBuilder = new StringBuilder();
+            fillPathBuilder(pathBuilder, false);
+
+            return pathBuilder.toString();
+        }
+
+        /**
+         * Return the fully escaped path of the entry. This path is safe to use
+         * in a shell command line.
+         *
+         * @return a path string using {@link FileListingService#FILE_SEPARATOR}
+         *         as separator
+         */
+        public String getFullEscapedPath()
+        {
+            StringBuilder pathBuilder = new StringBuilder();
+            fillPathBuilder(pathBuilder, true);
+
+            return pathBuilder.toString();
+        }
+
+        /**
+         * Returns the path as a list of segments.
+         */
+        public String[] getPathSegments()
+        {
+            ArrayList<String> list = new ArrayList<String>();
+            fillPathSegments(list);
+
+            return list.toArray(new String[list.size()]);
+        }
+
+        /**
+         * Returns true if the entry is a directory, false otherwise;
+         */
+        public int getType()
+        {
+            return type;
+        }
+
+        public FileListingService getFileListingService()
+        {
+            return fileListingService;
+        }
+
+        /**
+         * Returns if the entry is a folder or a link to a folder.
+         */
+        public boolean isDirectory()
+        {
+            return type == TYPE_DIRECTORY || type == TYPE_DIRECTORY_LINK
+                    || type == TYPE_ROOT_DEVICE || type == TYPE_ROOT_EMULATOR ;
+        }
+
+        /**
+         * Returns the parent entry.
+         */
+        public FileEntry getParent()
+        {
+            return parent;
+        }
+
+        /**
+         * Returns the cached children of the entry. This returns the cache
+         * created from calling <code>FileListingService.getChildren()</code>.
+         */
+        public FileEntry[] getCachedChildren()
+        {
+            return mChildren.toArray(new FileEntry[mChildren.size()]);
+        }
+
+        /**
+         * Returns the child {@link FileEntry} matching the name. This uses the
+         * cached children list.
+         *
+         * @param name
+         *            the name of the child to return.
+         * @return the FileEntry matching the name or null.
+         */
+        public FileEntry findChild(String name)
+        {
+            for (FileEntry entry : mChildren)
+            {
+                if (entry.name.equals(name))
+                {
+                    return entry;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Returns whether the entry is the root.
+         */
+        public boolean isRoot()
+        {
+            return isRoot;
+        }
+
+        void addChild(FileEntry child)
+        {
+            mChildren.add(child);
+        }
+
+        void setChildren(ArrayList<FileEntry> newChildren)
+        {
+            mChildren.clear();
+            mChildren.addAll(newChildren);
+        }
+
+        boolean needFetch()
+        {
+            if (fetchTime == 0)
+            {
+                return true;
+            }
+            long current = System.currentTimeMillis();
+            if (current - fetchTime > REFRESH_TEST)
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns if the entry is a valid application package.
+         */
+        public boolean isApplicationPackage()
+        {
+            return isAppPackage;
+        }
+
+        /**
+         * Returns if the file name is an application package name.
+         */
+        public boolean isAppFileName()
+        {
+            Matcher m = sApkPattern.matcher(name);
+            return m.matches();
+        }
+
+        /**
+         * Recursively fills the pathBuilder with the full path
+         *
+         * @param pathBuilder
+         *            a StringBuilder used to create the path.
+         * @param escapePath
+         *            Whether the path need to be escaped for consumption by a
+         *            shell command line.
+         */
+        protected void fillPathBuilder(StringBuilder pathBuilder, boolean escapePath)
+        {
+            if (isRoot)
+            {
+                return;
+            }
+
+            if (parent != null)
+            {
+                parent.fillPathBuilder(pathBuilder, escapePath);
+            }
+            pathBuilder.append(FILE_SEPARATOR);
+            pathBuilder.append(escapePath ? escape(name) : name);
+        }
+
+        /**
+         * Recursively fills the segment list with the full path.
+         *
+         * @param list
+         *            The list of segments to fill.
+         */
+        protected void fillPathSegments(ArrayList<String> list)
+        {
+            if (isRoot)
+            {
+                return;
+            }
+
+            if (parent != null)
+            {
+                parent.fillPathSegments(list);
+            }
+
+            list.add(name);
+        }
+
+        /**
+         * Returns an escaped version of the entry name.
+         *
+         * @param entryName
+         */
+        private String escape(String entryName)
+        {
+            return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$
+        }
+    }
+
+    private class LsReceiver extends MultiLineReceiver
+    {
+
+        private final ArrayList<FileEntry> mEntryList;
+        private final FileEntry[] mCurrentChildren;
+        private final FileEntry mParentEntry;
+//        private final boolean supportAnsiColor;
+        /**
+         * Create an ls receiver/parser.
+         *
+         * @param currentChildren
+         *            The list of current children. To prevent collapse during
+         *            update, reusing the same FileEntry objects for files that
+         *            were already there is paramount.
+         * @param entryList
+         *            the list of new children to be filled by the receiver.
+         * @param linkList
+         *            the list of link path to compute post ls, to figure out if
+         *            the link pointed to a file or to a directory.
+         * @param bSupportAnsiColor
+         *            decision to get result from "ls" command without ansicode for color
+         */
+        public LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList)
+        {
+            mParentEntry = parentEntry;
+            mCurrentChildren = parentEntry.getCachedChildren();
+            mEntryList = entryList;
+        }
+
+        @Override
+        public void processNewLines(String[] lines)
+        {
+            for (String line : lines)
+            {
+                // no need to handle empty lines.
+                if (line.length() == 0)
+                {
+                    continue;
+                }
+
+
+//              if(!supportAnsiColor)
+//              {
+//                  StringBuffer buffer = new StringBuffer();
+//
+//                  Matcher matcher = ansiColorReplacePattern.matcher(line);
+//
+//                  while(matcher.find())
+//                  {
+//                      matcher.appendReplacement(buffer, "");
+//                  }
+//                  }
+//                  matcher.appendTail(buffer);
+//                  line = buffer.toString();
+//              }
+
+                // run the line through the regexp
+                Matcher m = sLsPattern.matcher(line);
+                if (m.matches() == false)
+                {
+                    continue;
+                }
+
+                // get the name
+                String name = m.group(8);
+                // get the rest of the groups
+                String permissions = m.group(1);
+                String owner = m.group(3);
+                String group = m.group(4);
+                String size = m.group(5);
+                String date = m.group(6);
+                String time = m.group(7);
+                String info = null;
+                String linkTo = null;
+                int objectType = getFileType(permissions.charAt(0));
+
+                // now check what we may be linking to
+                if (objectType == TYPE_LINK)
+                {
+                    String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
+
+                    // we should have 2 segments
+                    if (segments.length == 2)
+                    {
+                        // update the entry name to not
+                        // contain the link
+                        name = segments[0];
+
+                        // and the link name
+                        info = segments[1];
+                        linkTo = segments[1];
+
+                        // add an arrow in front to specify
+                        // it's a link.
+                        info = "-> " + info; //$NON-NLS-1$;
+                        // now get the path to the link
+                        String[] pathSegments = info.split(FILE_SEPARATOR);
+
+                        if (pathSegments.length >= 1)
+                        {
+                            // FIXME : not fully impl
+                            String command = "ls -al ";
+                            if (linkTo.charAt(0) != '/')
+                                command = command + "/";
+                            command = command + linkTo;
+                            try
+                            {
+                                LsLinkReceiver receiver = new LsLinkReceiver();
+                                mDevice.executeShellCommand(command, receiver);
+                                if (receiver.isDirectoryLink == true)
+                                    objectType = TYPE_DIRECTORY_LINK;
+                            } catch (Exception e)
+                            {
+                                // sdb failed somehow, we do nothing.
+                            }
+                        }
+                    }
+                }
+                // get the entry, either from an existing one, or a
+                // new one
+                FileEntry entry = getExistingEntry(name);
+                if (entry == null)
+                {
+                    entry = new FileEntry(mParentEntry, name, objectType, false /* isRoot */, mParentEntry.fileListingService);
+                }
+
+                // add some misc info
+                entry.permissions = permissions;
+                entry.size = size;
+                entry.date = date;
+                entry.time = time;
+                entry.owner = owner;
+                entry.group = group;
+                if (objectType == TYPE_LINK || objectType == TYPE_DIRECTORY_LINK)
+                {
+                    entry.info = info;
+                    entry.linkPath = linkTo;
+                }
+
+                mEntryList.add(entry);
+            }
+        }
+
+        final class LsLinkReceiver extends MultiLineReceiver
+        {
+            boolean isDirectoryLink = false;
+
+            @Override
+            public void processNewLines(String[] lines)
+            {
+                for (String line : lines)
+                {
+                    if (line.length() > 0)
+                    {
+                        Matcher m = sLsPattern.matcher(line);
+                        if (m.matches())
+                        {
+                            int objectType = getFileType(m.group(1).charAt(0));
+                            String name = m.group(8);
+                            if (".".equals(name) && objectType == TYPE_DIRECTORY)
+                            {
+                                isDirectoryLink = true;
+                                break;
+                            }
+                        }
+                    }
+                }
+
+            }
+
+            @Override
+            public boolean isCancelled()
+            {
+                return false;
+            }
+        }
+
+        /**
+         * Queries for an already existing Entry per name
+         *
+         * @param name
+         *            the name of the entry
+         * @return the existing FileEntry or null if no entry with a matching
+         *         name exists.
+         */
+        private FileEntry getExistingEntry(String name)
+        {
+            for (int i = 0; i < mCurrentChildren.length; i++)
+            {
+                FileEntry e = mCurrentChildren[i];
+
+                // since we're going to "erase" the one we use, we need to
+                // check that the item is not null.
+                if (e != null)
+                {
+                    // compare per name, case-sensitive.
+                    if (name.equals(e.name))
+                    {
+                        // erase from the list
+                        mCurrentChildren[i] = null;
+
+                        // and return the object
+                        return e;
+                    }
+                }
+            }
+
+            // couldn't find any matching object, return null
+            return null;
+        }
+
+        @Override
+        public boolean isCancelled()
+        {
+            return false;
+        }
+
+        public void finishLinks()
+        {
+            // TODO Handle links in the listing service
+        }
+
+        private int getFileType(char type)
+        {
+            // and the type
+            int objectType = TYPE_OTHER;
+            switch (type)
+            {
+                case '-':
+                    objectType = TYPE_FILE;
+                    break;
+                case 'b':
+                    objectType = TYPE_BLOCK;
+                    break;
+                case 'c':
+                    objectType = TYPE_CHARACTER;
+                    break;
+                case 'd':
+                    objectType = TYPE_DIRECTORY;
+                    break;
+                case 'l':
+                    objectType = TYPE_LINK;
+                    break;
+                case 's':
+                    objectType = TYPE_SOCKET;
+                    break;
+                case 'p':
+                    objectType = TYPE_FIFO;
+                    break;
+            }
+            return objectType;
+        }
+    }
+
+    /**
+     * Classes which implement this interface provide a method that deals with
+     * asynchronous result from <code>ls</code> command on the device.
+     *
+     * @see FileListingService#getChildren(com.samsung.sdblib.FileListingService.FileEntry,
+     *      boolean, com.samsung.sdblib.FileListingService.IListingReceiver)
+     */
+    public interface IListingReceiver
+    {
+        public void setChildren(FileEntry entry, FileEntry[] children);
+
+        public void refreshEntry(FileEntry entry);
+    }
+
+    /**
+     * Creates a File Listing Service for a specified {@link Device}.
+     *
+     * @param device
+     *            The Device the service is connected to.
+     */
+    FileListingService(Device device)
+    {
+        mDevice = device;
+    }
+
+    /**
+     * Returns the root element.
+     *
+     * @return the {@link FileEntry} object representing the root element or
+     *         <code>null</code> if the device is invalid.
+     */
+    public FileEntry getRoot()
+    {
+        if (mDevice != null)
+        {
+            if (mRoot == null)
+            {
+                int deviceType = FileListingService.TYPE_ROOT_DEVICE;
+                if (mDevice.isEmulator())
+                    deviceType = FileListingService.TYPE_ROOT_EMULATOR;
+
+                mRoot = new FileEntry(null, mDevice.getDeviceName(), deviceType, true, this);
+            }
+            return mRoot;
+        }
+        return null;
+    }
+
+    public FileEntry[] getChildren(final FileEntry entry, boolean useCache, final IListingReceiver receiver)
+    {
+        return getChildren(entry, useCache, receiver, false);
+    }
+
+    public FileEntry[] getChildrenWithAnsicodeColor(final FileEntry entry, boolean useCache, final IListingReceiver receiver)
+    {
+        return getChildren(entry, useCache, receiver, true);
+    }
+
+    /**
+     * Returns the children of a {@link FileEntry}.
+     * <p/>
+     * This method supports a cache mechanism and synchronous and asynchronous
+     * modes.
+     * <p/>
+     * If <var>receiver</var> is <code>null</code>, the device side
+     * <code>ls</code> command is done synchronously, and the method will return
+     * upon completion of the command.<br>
+     * If <var>receiver</var> is non <code>null</code>, the command is launched
+     * is a separate thread and upon completion, the receiver will be notified
+     * of the result.
+     * <p/>
+     * The result for each <code>ls</code> command is cached in the parent
+     * <code>FileEntry</code>. <var>useCache</var> allows usage of this cache,
+     * but only if the cache is valid. The cache is valid only for
+     * {@link FileListingService#REFRESH_RATE} ms. After that a new
+     * <code>ls</code> command is always executed.
+     * <p/>
+     * If the cache is valid and <code>useCache == true</code>, the method will
+     * always simply return the value of the cache, whether a
+     * {@link IListingReceiver} has been provided or not.
+     *
+     * @param entry
+     *            The parent entry.
+     * @param useCache
+     *            A flag to use the cache or to force a new ls command.
+     * @param receiver
+     *            A receiver for asynchronous calls.
+     * @param bSupportAnsiColor
+         *        A boolean value to get result from "ls" command without ansicode for color
+     * @return The list of children or <code>null</code> for asynchronous calls.
+     *
+     * @see FileEntry#getCachedChildren()
+     */
+    private FileEntry[] getChildren(final FileEntry entry, boolean useCache, final IListingReceiver receiver, final boolean bSupportAnsiColor)
+    {
+        // first thing we do is check the cache, and if we already have a recent
+        // enough children list, we just return that.
+        if (useCache && entry.needFetch() == false)
+        {
+            return entry.getCachedChildren();
+        }
+
+        // if there's no receiver, then this is a synchronous call, and we
+        // return the result of ls
+        if (receiver == null)
+        {
+            doLs(entry, bSupportAnsiColor);
+            return entry.getCachedChildren();
+        }
+
+        // this is a asynchronous call.
+        // we launch a thread that will do ls and give the listing
+        // to the receiver
+        Thread t = new Thread("ls " + entry.getFullEscapedPath()) { //$NON-NLS-1$
+            @Override
+            public void run()
+            {
+                doLs(entry, bSupportAnsiColor);
+
+                receiver.setChildren(entry, entry.getCachedChildren());
+
+                final FileEntry[] children = entry.getCachedChildren();
+                if (children.length > 0 && children[0].isApplicationPackage())
+                {
+                    final HashMap<String, FileEntry> map = new HashMap<String, FileEntry>();
+
+                    for (FileEntry child : children)
+                    {
+                        String path = child.getFullEscapedPath();
+                        map.put(path, child);
+                    }
+
+                    // call pm.
+                    String command = PM_FULL_LISTING;
+                    try
+                    {
+                        mDevice.executeShellCommand(command, new MultiLineReceiver()
+                        {
+                            @Override
+                            public void processNewLines(String[] lines)
+                            {
+                                for (String line : lines)
+                                {
+                                    if (line.length() > 0)
+                                    {
+                                        // get the filepath and package
+                                        // from the line
+                                        Matcher m = sPmPattern.matcher(line);
+                                        if (m.matches())
+                                        {
+                                            // get the children with
+                                            // that path
+                                            FileEntry entry = map.get(m.group(1));
+                                            if (entry != null)
+                                            {
+                                                entry.info = m.group(2);
+                                                receiver.refreshEntry(entry);
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+
+                            @Override
+                            public boolean isCancelled()
+                            {
+                                return false;
+                            }
+                        });
+                    } catch (Exception e)
+                    {
+                        // sdb failed somehow, we do nothing.
+                    }
+                }
+
+                // if another thread is pending, launch it
+                synchronized (mThreadList)
+                {
+                    // first remove ourselves from the list
+                    mThreadList.remove(this);
+
+                    // then launch the next one if applicable.
+                    if (mThreadList.size() > 0)
+                    {
+                        Thread t = mThreadList.get(0);
+                        t.start();
+                    }
+                }
+            }
+        };
+
+        // we don't want to run multiple ls on the device at the same time, so
+        // we
+        // store the thread in a list and launch it only if there's no other
+        // thread running.
+        // the thread will launch the next one once it's done.
+        synchronized (mThreadList)
+        {
+            // add to the list
+            mThreadList.add(t);
+
+            // if it's the only one, launch it.
+            if (mThreadList.size() == 1)
+            {
+                t.start();
+            }
+        }
+
+        // and we return null.
+        return null;
+    }
+
+    public IDevice getDevice()
+    {
+        return mDevice;
+    }
+
+    private void doLs(FileEntry entry, boolean supportAnsiColor)
+    {
+        // create a list that will receive the list of the entries
+        ArrayList<FileEntry> entryList = new ArrayList<FileEntry>();
+
+        try
+        {
+            // create the command
+            String lsCommand = null;
+            String teeCommand = "";
+            if (entry.getType() == FileListingService.TYPE_LINK || entry.getType() == FileListingService.TYPE_DIRECTORY_LINK)
+            {
+                if (entry.getLinkFullPath().charAt(0) == '/')
+                    lsCommand = "ls -l " + getStringWithDoubleQuote(entry.getLinkFullPath()); //$NON-NLS-1$
+                else
+                    lsCommand = "ls -l " + getStringWithDoubleQuote("/" + entry.getLinkFullPath()); //$NON-NLS-1$
+            } else
+            {
+                lsCommand = "ls -l " + getStringWithDoubleQuote("/" + entry.getFullEscapedPath()); //$NON-NLS-1$
+            }
+
+            if(!supportAnsiColor)
+            {
+                teeCommand = " 2> /dev/null | tee /dev/null";
+            }
+
+            // create the receiver object that will parse the result from ls
+            LsReceiver receiver = new LsReceiver(entry, entryList);
+            // call ls.
+            mDevice.executeShellCommand(lsCommand + LS_COMMAND_TIMESTYLE + teeCommand, receiver);
+
+            // finish the process of the receiver to handle links
+            receiver.finishLinks();
+
+            //if fail to execute command, retry without timesytle
+            if(entryList.size() == 0)
+            {
+                mDevice.executeShellCommand(lsCommand + teeCommand, receiver);
+                receiver.finishLinks();
+            }
+
+        } catch (Exception e)
+        {
+            // catch all and do nothing.
+        }
+
+        // at this point we need to refresh the viewer
+        entry.fetchTime = System.currentTimeMillis();
+
+        // sort the children and set them as the new children
+        Collections.sort(entryList, FileEntry.sEntryComparator);
+        entry.setChildren(entryList);
+    }
+    
+    private String getStringWithDoubleQuote(String str)
+    {
+        return "\"" + str + "\"";
+    }
+}
\ No newline at end of file
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/IDevice.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/IDevice.java
new file mode 100644 (file)
index 0000000..8423f49
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2008 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 org.tizen.sdblib;
+
+import java.io.IOException;
+
+/**
+ *  A Device. It can be a physical device or an emulator.
+ */
+public interface IDevice {
+    /** Serial number of the first connected emulator. */
+    public final static String FIRST_EMULATOR_SN = "emulator-26100"; //$NON-NLS-1$
+    /** Device change bit mask: {@link DeviceState} change. */
+    public static final int CHANGE_STATE = 0x0001;
+    /** Device change bit mask: {@link Client} list change. */
+    public static final int CHANGE_CLIENT_LIST = 0x0002;
+    /** Device change bit mask: build info change. */
+    public static final int CHANGE_BUILD_INFO = 0x0004;
+    
+    /**
+     * The architecture of a device.
+     * Returns {@link Arch#X86} or {@link Arch#ARM} about device architecture.
+     */
+    public static enum Arch{
+        X86("x86"),
+        ARM("arm");
+
+        private String arch;
+
+        Arch(String arch)
+        {
+            this.arch = arch;
+        }
+
+        public String getArch()
+        {
+            return arch;
+        }
+    }
+
+    /**
+     * The state of a device.
+     */
+    public static enum DeviceState {
+        OFFLINE("offline"), //$NON-NLS-1$
+        ONLINE("device"); //$NON-NLS-1$
+
+        private String mState;
+
+        DeviceState(String state) {
+            mState = state;
+        }
+
+        /**
+         * Returns a {@link DeviceState} from the string returned by <code>sdb devices</code>.
+         *
+         * @param state the device state.
+         * @return a {@link DeviceState} object or <code>null</code> if the state is unknown.
+         */
+        public static DeviceState getState(String state) {
+            for (DeviceState deviceState : values()) {
+                if (deviceState.mState.equals(state)) {
+                    return deviceState;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns the serial number of the device.
+     */
+    public String getSerialNumber();
+
+    /**
+     * Returns the name of the device.
+     */
+    public String getDeviceName();
+
+    public DeviceState getState();
+
+    /**
+     * Returns if the device is ready.
+     *
+     * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#ONLINE}.
+     */
+    public boolean isOnline();
+
+    /**
+     * Returns <code>true</code> if the device is an emulator.
+     */
+    public boolean isEmulator();
+    
+    /**
+     * Returns {@link Arch} about device architecture.
+     */
+    public Arch getArch();
+
+    /**
+     * Returns if the device is offline.
+     *
+     * @return <code>true</code> if {@link #getState()} returns {@link DeviceState#OFFLINE}.
+     */
+    public boolean isOffline();
+
+    /**
+     * Returns a {@link SyncService} object to push / pull files to and from the device.
+     *
+     * @return <code>null</code> if the SyncService couldn't be created. This can happen if sdb
+     *            refuse to open the connection because the {@link IDevice} is invalid
+     *            (or got disconnected).
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException if the connection with sdb failed.
+     */
+    public SyncService getSyncService()
+            throws TimeoutException, SdbCommandRejectedException, IOException;
+
+    /**
+     * Returns a {@link FileListingService} for this device.
+     */
+    public FileListingService getFileListingService();
+
+    /**
+     * Executes a shell command on the device and return SdbShellProcess
+     *
+     * @param command the shell command to execute
+     * @throws IOException in case of I/O error on the connection.
+     *
+     * @see SdbShellProcess
+     */
+    public SdbShellProcess executeShellCommand(String command) throws IOException;
+
+    /**
+     * Executes a shell command on the device, and sends the result to a <var>receiver</var>
+     * <p/>This is similar to calling
+     * <code>executeShellCommand(command, receiver, SdbPreferences.getTimeOut())</code>.
+     *
+     * @param command the shell command to execute
+     * @param receiver the {@link IShellOutputReceiver} that will receives the output of the shell
+     *            command
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws ShellCommandUnresponsiveException in case the shell command doesn't send output
+     *            for a given time.
+     * @throws IOException in case of I/O error on the connection.
+     *
+     * @see #executeShellCommand(String, IShellOutputReceiver, int)
+     * @see SdbPreferences#getTimeOut()
+     */
+    public void executeShellCommand(String command, IShellOutputReceiver receiver)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException;
+
+    /**
+     * Executes a shell command on the device, and sends the result to a <var>receiver</var>.
+     * <p/><var>maxTimeToOutputResponse</var> is used as a maximum waiting time when expecting the
+     * command output from the device.<br>
+     * At any time, if the shell command does not output anything for a period longer than
+     * <var>maxTimeToOutputResponse</var>, then the method will throw
+     * {@link ShellCommandUnresponsiveException}.
+     * <p/>For commands like log output, a <var>maxTimeToOutputResponse</var> value of 0, meaning
+     * that the method will never throw and will block until the receiver's
+     * {@link IShellOutputReceiver#isCancelled()} returns <code>true</code>, should be
+     * used.
+     *
+     * @param command the shell command to execute
+     * @param receiver the {@link IShellOutputReceiver} that will receives the output of the shell
+     *            command
+     * @param maxTimeToOutputResponse the maximum amount of time during which the command is allowed
+     *            to not output any response. A value of 0 means the method will wait forever
+     *            (until the <var>receiver</var> cancels the execution) for command output and
+     *            never throw.
+     * @throws TimeoutException in case of timeout on the connection when sending the command.
+     * @throws SdbCommandRejectedException if sdb rejects the command.
+     * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
+     *            for a period longer than <var>maxTimeToOutputResponse</var>.
+     * @throws IOException in case of I/O error on the connection.
+     *
+     * @see SdbPreferences#getTimeOut()
+     */
+    public void executeShellCommand(String command, IShellOutputReceiver receiver,
+            int maxTimeToOutputResponse)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException;
+
+    /**
+     * Runs the event log service and outputs the event log to the {@link LogReceiver}.
+     * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+     * @param receiver the receiver to receive the event log entries.
+     * @throws TimeoutException in case of timeout on the connection. This can only be thrown if the
+     * timeout happens during setup. Once logs start being received, no timeout will occur as it's
+     * not possible to detect a difference between no log and timeout.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public void runEventLogService(LogReceiver receiver)
+            throws TimeoutException, SdbCommandRejectedException, IOException;
+
+    /**
+     * Runs the log service for the given log and outputs the log to the {@link LogReceiver}.
+     * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+     *
+     * @param logname the logname of the log to read from.
+     * @param receiver the receiver to receive the event log entries.
+     * @throws TimeoutException in case of timeout on the connection. This can only be thrown if the
+     *            timeout happens during setup. Once logs start being received, no timeout will
+     *            occur as it's not possible to detect a difference between no log and timeout.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public void runLogService(String logname, LogReceiver receiver)
+            throws TimeoutException, SdbCommandRejectedException, IOException;
+
+    /**
+     * Creates a port forwarding between a local and a remote port.
+     *
+     * @param localPort the local port to forward
+     * @param remotePort the remote port.
+     * @return <code>true</code> if success.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public void createForward(int localPort, int remotePort)
+            throws TimeoutException, SdbCommandRejectedException, IOException;
+
+    /**
+     * Removes a port forwarding between a local and a remote port.
+     *
+     * @param localPort the local port to forward
+     * @param remotePort the remote port.
+     * @return <code>true</code> if success.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public void removeForward(int localPort, int remotePort)
+            throws TimeoutException, SdbCommandRejectedException, IOException;
+
+    /**
+     * Pushes a file to device
+     *
+     * @param localFilePath the absolute path to file on local host
+     * @return {@link String} destination path on device for file
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public String syncPackageToDevice(String localFilePath)
+            throws TimeoutException, SdbCommandRejectedException, IOException;
+
+    /**
+     * Removes a file from device.
+     *
+     * @param remoteFilePath path on device of file to remove
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws ShellCommandUnresponsiveException if the device didn't respond for long time when
+     *            performing the action.
+     * @throws IOException if file removal failed
+     */
+    public void removeRemotePackage(String remoteFilePath)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException;
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/IShellOutputReceiver.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/IShellOutputReceiver.java
new file mode 100644 (file)
index 0000000..76b63e7
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+/**
+ * Classes which implement this interface provide methods that deal with out from a remote shell
+ * command on a device/emulator.
+ */
+public interface IShellOutputReceiver {
+    /**
+     * Called every time some new data is available.
+     * @param data The new data.
+     * @param offset The offset at which the new data starts.
+     * @param length The length of the new data.
+     */
+    public void addOutput(byte[] data, int offset, int length);
+
+    /**
+     * Called at the end of the process execution (unless the process was
+     * canceled). This allows the receiver to terminate and flush whatever
+     * data was not yet processed.
+     */
+    public void flush();
+
+    /**
+     * Cancel method to stop the execution of the remote shell command.
+     * @return true to cancel the execution of the command.
+     */
+    public boolean isCancelled();
+};
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/Log.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/Log.java
new file mode 100644 (file)
index 0000000..45f0e2e
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Log class that mirrors the API in main SLP sources.
+ * <p/>Default behavior outputs the log to {@link System#out}. Use
+ * {@link #setLogOutput(com.samsung.sdblib.Log.ILogOutput)} to redirect the log somewhere else.
+ */
+public final class Log {
+
+    /**
+     * Log Level enum.
+     */
+    public enum LogLevel {
+        VERBOSE(2, "Verbose", 'V'), //$NON-NLS-1$
+        DEBUG(3, "Debug", 'D'), //$NON-NLS-1$
+        INFO(4, "Info", 'I'), //$NON-NLS-1$
+        WARN(5, "Warning", 'W'), //$NON-NLS-1$
+        ERROR(6, "Error", 'E'), //$NON-NLS-1$
+        ASSERT(7, "Assert", 'A'); //$NON-NLS-1$
+
+        private int mPriorityLevel;
+        private String mStringValue;
+        private char mPriorityLetter;
+
+        LogLevel(int intPriority, String stringValue, char priorityChar) {
+            mPriorityLevel = intPriority;
+            mStringValue = stringValue;
+            mPriorityLetter = priorityChar;
+        }
+
+        public static LogLevel getByString(String value) {
+            for (LogLevel mode : values()) {
+                if (mode.mStringValue.equals(value)) {
+                    return mode;
+                }
+            }
+
+            return null;
+        }
+        
+        /**
+         * Returns the {@link LogLevel} enum matching the specified letter.
+         * @param letter the letter matching a <code>LogLevel</code> enum
+         * @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
+         */
+        public static LogLevel getByLetter(char letter) {
+            for (LogLevel mode : values()) {
+                if (mode.mPriorityLetter == letter) {
+                    return mode;
+                }
+            }
+
+            return null;
+        }
+
+        /**
+         * Returns the {@link LogLevel} enum matching the specified letter.
+         * <p/>
+         * The letter is passed as a {@link String} argument, but only the first character
+         * is used. 
+         * @param letter the letter matching a <code>LogLevel</code> enum
+         * @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
+         */
+        public static LogLevel getByLetterString(String letter) {
+            if (letter.length() > 0) {
+                return getByLetter(letter.charAt(0));
+            }
+
+            return null;
+        }
+
+        /**
+         * Returns the letter identifying the priority of the {@link LogLevel}.
+         */
+        public char getPriorityLetter() {
+            return mPriorityLetter;
+        }
+
+        /**
+         * Returns the numerical value of the priority.
+         */
+        public int getPriority() {
+            return mPriorityLevel;
+        }
+
+        /**
+         * Returns a non translated string representing the LogLevel.
+         */
+        public String getStringValue() {
+            return mStringValue;
+        }
+    }
+    
+    /**
+     * Classes which implement this interface provides methods that deal with outputting log
+     * messages.
+     */
+    public interface ILogOutput {
+        /**
+         * Sent when a log message needs to be printed.
+         * @param logLevel The {@link LogLevel} enum representing the priority of the message.
+         * @param tag The tag associated with the message.
+         * @param message The message to display.
+         */
+        public void printLog(LogLevel logLevel, String tag, String message);
+
+        /**
+         * Sent when a log message needs to be printed, and, if possible, displayed to the user
+         * in a dialog box.
+         * @param logLevel The {@link LogLevel} enum representing the priority of the message.
+         * @param tag The tag associated with the message.
+         * @param message The message to display.
+         */
+        public void printAndPromptLog(LogLevel logLevel, String tag, String message);
+    }
+
+    private static LogLevel mLevel = SdbPreferences.getLogLevel();
+
+    private static ILogOutput sLogOutput;
+
+    private static final char[] mSpaceLine = new char[72];
+    private static final char[] mHexDigit = new char[]
+        { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
+    static {
+        /* prep for hex dump */
+        int i = mSpaceLine.length-1;
+        while (i >= 0)
+            mSpaceLine[i--] = ' ';
+        mSpaceLine[0] = mSpaceLine[1] = mSpaceLine[2] = mSpaceLine[3] = '0';
+        mSpaceLine[4] = '-';
+    }
+
+    static final class Config {
+        static final boolean LOGV = true;
+        static final boolean LOGD = true;
+    };
+
+    private Log() {}
+
+    /**
+     * Outputs a {@link LogLevel#VERBOSE} level message.
+     * @param tag The tag associated with the message.
+     * @param message The message to output.
+     */
+    public static void v(String tag, String message) {
+        println(LogLevel.VERBOSE, tag, message);
+    }
+
+    /**
+     * Outputs a {@link LogLevel#DEBUG} level message.
+     * @param tag The tag associated with the message.
+     * @param message The message to output.
+     */
+    public static void d(String tag, String message) {
+        println(LogLevel.DEBUG, tag, message);
+    }
+
+    /**
+     * Outputs a {@link LogLevel#INFO} level message.
+     * @param tag The tag associated with the message.
+     * @param message The message to output.
+     */
+    public static void i(String tag, String message) {
+        println(LogLevel.INFO, tag, message);
+    }
+
+    /**
+     * Outputs a {@link LogLevel#WARN} level message.
+     * @param tag The tag associated with the message.
+     * @param message The message to output.
+     */
+    public static void w(String tag, String message) {
+        println(LogLevel.WARN, tag, message);
+    }
+
+    /**
+     * Outputs a {@link LogLevel#ERROR} level message.
+     * @param tag The tag associated with the message.
+     * @param message The message to output.
+     */
+    public static void e(String tag, String message) {
+        println(LogLevel.ERROR, tag, message);
+    }
+
+    /**
+     * Outputs a log message and attempts to display it in a dialog.
+     * @param tag The tag associated with the message.
+     * @param message The message to output.
+     */
+    public static void logAndDisplay(LogLevel logLevel, String tag, String message) {
+        if (sLogOutput != null) {
+            sLogOutput.printAndPromptLog(logLevel, tag, message);
+        } else {
+            println(logLevel, tag, message);
+        }
+    }
+
+    /**
+     * Outputs a {@link LogLevel#ERROR} level {@link Throwable} information.
+     * @param tag The tag associated with the message.
+     * @param throwable The {@link Throwable} to output.
+     */
+    public static void e(String tag, Throwable throwable) {
+        if (throwable != null) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+
+            throwable.printStackTrace(pw);
+            println(LogLevel.ERROR, tag, throwable.getMessage() + '\n' + sw.toString());
+        }
+    }
+
+    static void setLevel(LogLevel logLevel) {
+        mLevel = logLevel;
+    }
+
+    /**
+     * Sets the {@link ILogOutput} to use to print the logs. If not set, {@link System#out}
+     * will be used.
+     * @param logOutput The {@link ILogOutput} to use to print the log.
+     */
+    public static void setLogOutput(ILogOutput logOutput) {
+        sLogOutput = logOutput;
+    }
+
+    /**
+     * Show hex dump.
+     * <p/>
+     * Local addition.  Output looks like:
+     * 1230- 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  0123456789abcdef
+     * <p/>
+     * Uses no string concatenation; creates one String object per line.
+     */
+    static void hexDump(String tag, LogLevel level, byte[] data, int offset, int length) {
+
+        int kHexOffset = 6;
+        int kAscOffset = 55;
+        char[] line = new char[mSpaceLine.length];
+        int addr, baseAddr, count;
+        int i, ch;
+        boolean needErase = true;
+
+        //Log.w(tag, "HEX DUMP: off=" + offset + ", length=" + length);
+
+        baseAddr = 0;
+        while (length != 0) {
+            if (length > 16) {
+                // full line
+                count = 16;
+            } else {
+                // partial line; re-copy blanks to clear end
+                count = length;
+                needErase = true;
+            }
+
+            if (needErase) {
+                System.arraycopy(mSpaceLine, 0, line, 0, mSpaceLine.length);
+                needErase = false;
+            }
+
+            // output the address (currently limited to 4 hex digits)
+            addr = baseAddr;
+            addr &= 0xffff;
+            ch = 3;
+            while (addr != 0) {
+                line[ch] = mHexDigit[addr & 0x0f];
+                ch--;
+                addr >>>= 4;
+            }
+
+            // output hex digits and ASCII chars
+            ch = kHexOffset;
+            for (i = 0; i < count; i++) {
+                byte val = data[offset + i];
+
+                line[ch++] = mHexDigit[(val >>> 4) & 0x0f];
+                line[ch++] = mHexDigit[val & 0x0f];
+                ch++;
+
+                if (val >= 0x20 && val < 0x7f)
+                    line[kAscOffset + i] = (char) val;
+                else
+                    line[kAscOffset + i] = '.';
+            }
+
+            println(level, tag, new String(line));
+
+            // advance to next chunk of data
+            length -= count;
+            offset += count;
+            baseAddr += count;
+        }
+
+    }
+
+    /**
+     * Dump the entire contents of a byte array with DEBUG priority.
+     */
+    static void hexDump(byte[] data) {
+        hexDump("sdb", LogLevel.DEBUG, data, 0, data.length);
+    }
+
+    /* currently prints to stdout; could write to a log window */
+    private static void println(LogLevel logLevel, String tag, String message) {
+        if (logLevel.getPriority() >= mLevel.getPriority()) {
+            if (sLogOutput != null) {
+                sLogOutput.printLog(logLevel, tag, message);
+            } else {
+                printLog(logLevel, tag, message);
+            }
+        }
+    }
+    
+    /**
+     * Prints a log message.
+     * @param logLevel
+     * @param tag
+     * @param message
+     */
+    public static void printLog(LogLevel logLevel, String tag, String message) {
+        System.out.print(getLogFormatString(logLevel, tag, message));
+    }
+
+    /**
+     * Formats a log message.
+     * @param logLevel
+     * @param tag
+     * @param message
+     */
+    public static String getLogFormatString(LogLevel logLevel, String tag, String message) {
+        SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss");
+        return String.format("%s %c/%s: %s\n", formatter.format(new Date()),
+                logLevel.getPriorityLetter(), tag, message);
+    }
+}
+
+
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/LogReceiver.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/LogReceiver.java
new file mode 100644 (file)
index 0000000..ac7ec70
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2008 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 org.tizen.sdblib;
+
+import java.security.InvalidParameterException;
+
+/**
+ * Receiver able to provide low level parsing for device-side log services.
+ */
+public final class LogReceiver {
+
+    private final static int ENTRY_HEADER_SIZE = 20; // 2*2 + 4*4; see LogEntry.
+
+    /**
+     * Represents a log entry and its raw data.
+     */
+    public final static class LogEntry {
+        /*
+         * See //device/include/utils/logger.h
+         */
+        /** 16bit unsigned: length of the payload. */
+        public int  len; /* This is normally followed by a 16 bit padding */
+        /** pid of the process that generated this {@link LogEntry} */
+        public int   pid;
+        /** tid of the process that generated this {@link LogEntry} */
+        public int   tid;
+        /** Seconds since epoch. */
+        public int   sec;
+        /** nanoseconds. */
+        public int   nsec;
+        /** The entry's raw data. */
+        public byte[] data;
+    };
+
+    /**
+     * Classes which implement this interface provide a method that deals
+     * with {@link LogEntry} objects coming from log service through a {@link LogReceiver}.
+     * <p/>This interface provides two methods.
+     * <ul>
+     * <li>{@link #newEntry(com.samsung.sdblib.log.LogReceiver.LogEntry)} provides a
+     * first level of parsing, extracting {@link LogEntry} objects out of the log service output.</li>
+     * <li>{@link #newData(byte[], int, int)} provides a way to receive the raw information
+     * coming directly from the log service.</li>
+     * </ul>
+     */
+    public interface ILogListener {
+        /**
+         * Sent when a new {@link LogEntry} has been parsed by the {@link LogReceiver}.
+         * @param entry the new log entry.
+         */
+        public void newEntry(LogEntry entry);
+        
+        /**
+         * Sent when new raw data is coming from the log service.
+         * @param data the raw data buffer.
+         * @param offset the offset into the buffer signaling the beginning of the new data.
+         * @param length the length of the new data.
+         */
+        public void newData(byte[] data, int offset, int length);
+    }
+
+    /** Current {@link LogEntry} being read, before sending it to the listener. */
+    private LogEntry mCurrentEntry;
+
+    /** Temp buffer to store partial entry headers. */
+    private byte[] mEntryHeaderBuffer = new byte[ENTRY_HEADER_SIZE];
+    /** Offset in the partial header buffer */
+    private int mEntryHeaderOffset = 0;
+    /** Offset in the partial entry data */
+    private int mEntryDataOffset = 0;
+    
+    /** Listener waiting for receive fully read {@link LogEntry} objects */
+    private ILogListener mListener;
+
+    private boolean mIsCancelled = false;
+    
+    /**
+     * Creates a {@link LogReceiver} with an {@link ILogListener}.
+     * <p/>
+     * The {@link ILogListener} will receive new log entries as they are parsed, in the form 
+     * of {@link LogEntry} objects.
+     * @param listener the listener to receive new log entries.
+     */
+    public LogReceiver(ILogListener listener) {
+        mListener = listener;
+    }
+    
+
+    /**
+     * Parses new data coming from the log service.
+     * @param data the data buffer
+     * @param offset the offset into the buffer signaling the beginning of the new data.
+     * @param length the length of the new data.
+     */
+    public void parseNewData(byte[] data, int offset, int length) {
+        // notify the listener of new raw data
+        if (mListener != null) {
+            mListener.newData(data, offset, length);
+        }
+
+        // loop while there is still data to be read and the receiver has not be cancelled.
+        while (length > 0 && mIsCancelled == false) {
+            // first check if we have no current entry.
+            if (mCurrentEntry == null) {
+                if (mEntryHeaderOffset + length < ENTRY_HEADER_SIZE) {
+                    // if we don't have enough data to finish the header, save
+                    // the data we have and return
+                    System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset, length);
+                    mEntryHeaderOffset += length;
+                    return;
+                } else {
+                    // we have enough to fill the header, let's do it.
+                    // did we store some part at the beginning of the header?
+                    if (mEntryHeaderOffset != 0) {
+                        // copy the rest of the entry header into the header buffer
+                        int size = ENTRY_HEADER_SIZE - mEntryHeaderOffset; 
+                        System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset,
+                                size);
+                        
+                        // create the entry from the header buffer
+                        mCurrentEntry = createEntry(mEntryHeaderBuffer, 0);
+    
+                        // since we used the whole entry header buffer, we reset  the offset
+                        mEntryHeaderOffset = 0;
+                        
+                        // adjust current offset and remaining length to the beginning
+                        // of the entry data
+                        offset += size;
+                        length -= size;
+                    } else {
+                        // create the entry directly from the data array
+                        mCurrentEntry = createEntry(data, offset);
+                        
+                        // adjust current offset and remaining length to the beginning
+                        // of the entry data
+                        offset += ENTRY_HEADER_SIZE;
+                        length -= ENTRY_HEADER_SIZE;
+                    }
+                }
+            }
+            
+            // at this point, we have an entry, and offset/length have been updated to skip
+            // the entry header.
+    
+            // if we have enough data for this entry or more, we'll need to end this entry
+            if (length >= mCurrentEntry.len - mEntryDataOffset) {
+                // compute and save the size of the data that we have to read for this entry,
+                // based on how much we may already have read.
+                int dataSize = mCurrentEntry.len - mEntryDataOffset;  
+    
+                // we only read what we need, and put it in the entry buffer.
+                System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, dataSize);
+                
+                // notify the listener of a new entry
+                if (mListener != null) {
+                    mListener.newEntry(mCurrentEntry);
+                }
+    
+                // reset some flags: we have read 0 data of the current entry.
+                // and we have no current entry being read.
+                mEntryDataOffset = 0;
+                mCurrentEntry = null;
+                
+                // and update the data buffer info to the end of the current entry / start
+                // of the next one.
+                offset += dataSize;
+                length -= dataSize;
+            } else {
+                // we don't have enough data to fill this entry, so we store what we have
+                // in the entry itself.
+                System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, length);
+                
+                // save the amount read for the data.
+                mEntryDataOffset += length;
+                return;
+            }
+        }
+    }
+
+    /**
+     * Returns whether this receiver is canceling the remote service.
+     */
+    public boolean isCancelled() {
+        return mIsCancelled;
+    }
+    
+    /**
+     * Cancels the current remote service.
+     */
+    public void cancel() {
+        mIsCancelled = true;
+    }
+    
+    /**
+     * Creates a {@link LogEntry} from the array of bytes. This expects the data buffer size
+     * to be at least <code>offset + {@link #ENTRY_HEADER_SIZE}</code>.
+     * @param data the data buffer the entry is read from.
+     * @param offset the offset of the first byte from the buffer representing the entry.
+     * @return a new {@link LogEntry} or <code>null</code> if some error happened.
+     */
+    private LogEntry createEntry(byte[] data, int offset) {
+        if (data.length < offset + ENTRY_HEADER_SIZE) {
+            throw new InvalidParameterException(
+                    "Buffer not big enough to hold full LoggerEntry header");
+        }
+
+        // create the new entry and fill it.
+        LogEntry entry = new LogEntry();
+        entry.len = ArrayHelper.swapU16bitFromArray(data, offset);
+        
+        // we've read only 16 bits, but since there's also a 16 bit padding,
+        // we can skip right over both.
+        offset += 4;
+        
+        entry.pid = ArrayHelper.swap32bitFromArray(data, offset);
+        offset += 4;
+        entry.tid = ArrayHelper.swap32bitFromArray(data, offset);
+        offset += 4;
+        entry.sec = ArrayHelper.swap32bitFromArray(data, offset);
+        offset += 4;
+        entry.nsec = ArrayHelper.swap32bitFromArray(data, offset);
+        offset += 4;
+        
+        // allocate the data
+        entry.data = new byte[entry.len];
+        
+        return entry;
+    }
+    
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/MultiLineReceiver.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/MultiLineReceiver.java
new file mode 100644 (file)
index 0000000..2ad0e5f
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Base implementation of {@link IShellOutputReceiver}, that takes the raw data
+ * coming from the socket, and convert it into {@link String} objects.
+ * <p/>
+ * Additionally, it splits the string by lines.
+ * <p/>
+ * Classes extending it must implement {@link #processNewLines(String[])} which
+ * receives new parsed lines as they become available.
+ */
+public abstract class MultiLineReceiver implements IShellOutputReceiver {
+
+       private String OUTPUT_DECODING = "UTF-8";
+       private boolean mTrimLines = true;
+
+       /** unfinished message line, stored for next packet */
+       private String mUnfinishedLine = null;
+
+       private final ArrayList<String> mArray = new ArrayList<String>();
+
+       /**
+        * Set the trim lines flag.
+        * 
+        * @param trim
+        *            hether the lines are trimmed, or not.
+        */
+       public void setTrimLine(boolean trim) {
+               mTrimLines = trim;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see com.samsung.sdblib.sdb.IShellOutputReceiver#addOutput( byte[], int,
+        * int)
+        */
+       public final void addOutput(byte[] data, int offset, int length) {
+               if (isCancelled() == false) {
+                       String s = null;
+                       try {
+                               s = new String(data, offset, length, OUTPUT_DECODING); //$NON-NLS-1$
+
+                       } catch (UnsupportedEncodingException e) {
+                               // normal encoding didn't work, try the default one
+                               s = new String(data, offset, length);
+                       }
+
+                       // ok we've got a string
+                       if (s != null) {
+                               // if we had an unfinished line we add it.
+                               if (mUnfinishedLine != null) {
+                                       s = mUnfinishedLine + s;
+                                       mUnfinishedLine = null;
+                               }
+
+                               // now we split the lines
+                               mArray.clear();
+                               int start = 0;
+                               do {
+                                       int index = s.indexOf("\r\n", start); //$NON-NLS-1$
+
+                                       // if \r\n was not found, this is an unfinished line
+                                       // and we store it to be processed for the next packet
+                                       if (index == -1) {
+                                               mUnfinishedLine = s.substring(start);
+                                               break;
+                                       }
+
+                                       // so we found a \r\n;
+                                       // extract the line
+                                       String line = s.substring(start, index);
+                                       if (mTrimLines) {
+                                               line = line.trim();
+                                       }
+                                       mArray.add(line);
+
+                                       // move start to after the \r\n we found
+                                       start = index + 2;
+                               } while (true);
+
+                               if (mArray.size() > 0) {
+                                       // at this point we've split all the lines.
+                                       // make the array
+                                       String[] lines = mArray.toArray(new String[mArray.size()]);
+
+                                       // send it for final processing
+                                       processNewLines(lines);
+                               }
+                       }
+               }
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see com.samsung.sdblib.sdb.IShellOutputReceiver#flush()
+        */
+       public final void flush() {
+               if (mUnfinishedLine != null && !mUnfinishedLine.isEmpty()) {
+                       processNewLines(new String[] { mUnfinishedLine });
+               }
+
+               done();
+       }
+
+       /**
+        * Terminates the process. This is called after the last lines have been
+        * through {@link #processNewLines(String[])}.
+        */
+       public void done() {
+               // do nothing.
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see com.samsung.sdblib.sdb.IShellOutputReceiver#isCancelled()
+        */
+       public boolean isCancelled() {
+               return false;
+       }
+
+       /**
+        * Called when new lines are being received by the remote process.
+        * <p/>
+        * It is guaranteed that the lines are complete when they are given to this
+        * method.
+        * 
+        * @param lines
+        *            The array containing the new lines.
+        */
+       public abstract void processNewLines(String[] lines);
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/NullOutputReceiver.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/NullOutputReceiver.java
new file mode 100644 (file)
index 0000000..1c30a82
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+/**
+ * Implementation of {@link IShellOutputReceiver} that does nothing.
+ * <p/>This can be used to execute a remote shell command when the output is not needed.
+ */
+public final class NullOutputReceiver implements IShellOutputReceiver {
+
+    private static NullOutputReceiver sReceiver = new NullOutputReceiver();
+
+    public static IShellOutputReceiver getReceiver() {
+        return sReceiver;
+    }
+
+    /* (non-Javadoc)
+     * @see com.samsung.sdblib.sdb.IShellOutputReceiver#addOutput(byte[], int, int)
+     */
+    public void addOutput(byte[] data, int offset, int length) {
+    }
+
+    /* (non-Javadoc)
+     * @see com.samsung.sdblib.sdb.IShellOutputReceiver#flush()
+     */
+    public void flush() {
+    }
+
+    /* (non-Javadoc)
+     * @see com.samsung.sdblib.sdb.IShellOutputReceiver#isCancelled()
+     */
+    public boolean isCancelled() {
+        return false;
+    }
+
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbCommandRejectedException.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbCommandRejectedException.java
new file mode 100644 (file)
index 0000000..1209939
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 org.tizen.sdblib;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when sdb refuses a command.
+ */
+public class SdbCommandRejectedException extends IOException {
+    private static final long serialVersionUID = 1L;
+    private final boolean mIsDeviceOffline;
+    private final boolean mErrorDuringDeviceSelection;
+
+    SdbCommandRejectedException(String message) {
+        super(message);
+        mIsDeviceOffline = "device offline".equals(message);
+        mErrorDuringDeviceSelection = false;
+    }
+
+    SdbCommandRejectedException(String message, boolean errorDuringDeviceSelection) {
+        super(message);
+        mErrorDuringDeviceSelection = errorDuringDeviceSelection;
+        mIsDeviceOffline = "device offline".equals(message);
+    }
+
+    /**
+     * Returns true if the error is due to the device being offline.
+     */
+    public boolean isDeviceOffline() {
+        return mIsDeviceOffline;
+    }
+
+    /**
+     * Returns whether sdb refused to target a given device for the command.
+     * <p/>If false, sdb refused the command itself, if true, it refused to target the given
+     * device.
+     */
+    public boolean wasErrorDuringDeviceSelection() {
+        return mErrorDuringDeviceSelection;
+    }
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbHelper.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbHelper.java
new file mode 100644 (file)
index 0000000..58ec7e5
--- /dev/null
@@ -0,0 +1,738 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+
+/**
+ * Helper class to handle requests and connections to sdb.
+ * <p/>{@link DebugBridgeServer} is the public API to connection to sdb, while {@link SdbHelper}
+ * does the low level stuff.
+ * <p/>This currently uses spin-wait non-blocking I/O. A Selector would be more efficient,
+ * but seems like overkill for what we're doing here.
+ */
+final class SdbHelper {
+
+    // public static final long kOkay = 0x59414b4fL;
+    // public static final long kFail = 0x4c494146L;
+
+    static final int WAIT_TIME = 5; // spin-wait sleep, in ms
+
+    static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
+    static final String UTF_ENCODING = "UTF-8"; 
+
+    /** do not instantiate */
+    private SdbHelper() {
+    }
+
+    /**
+     * Response from SDB.
+     */
+    static class SdbResponse {
+        public SdbResponse() {
+            message = "";
+        }
+
+        public boolean okay; // first 4 bytes in response were "OKAY"?
+
+        public String message; // diagnostic string if #okay is false
+    }
+
+    /**
+     * Create and connect a new pass-through socket, from the host to a port on
+     * the device.
+     *
+     * @param sdbSockAddr
+     * @param device the device to connect to. Can be null in which case the connection will be
+     * to the first available device.
+     * @param devicePort the port we're opening
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws IOException in case of I/O error on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     */
+    public static SocketChannel open(InetSocketAddress sdbSockAddr,
+            Device device, int devicePort)
+            throws IOException, TimeoutException, SdbCommandRejectedException {
+
+        SocketChannel sdbChan = SocketChannel.open(sdbSockAddr);
+        try {
+            sdbChan.socket().setTcpNoDelay(true);
+            sdbChan.configureBlocking(false);
+
+            // if the device is not -1, then we first tell sdb we're looking to
+            // talk to a specific device
+            setDevice(sdbChan, device);
+
+            byte[] req = createsdbForwardRequest(null, devicePort);
+            // Log.hexDump(req);
+
+            write(sdbChan, req);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false);
+            if (resp.okay == false) {
+                throw new SdbCommandRejectedException(resp.message);
+            }
+
+            sdbChan.configureBlocking(true);
+        } catch (TimeoutException e) {
+            sdbChan.close();
+            throw e;
+        } catch (IOException e) {
+            sdbChan.close();
+            throw e;
+        }
+
+        return sdbChan;
+    }
+
+    /**
+     * Creates and connects a new pass-through socket, from the host to a port on
+     * the device.
+     *
+     * @param sdbSockAddr
+     * @param device the device to connect to. Can be null in which case the connection will be
+     * to the first available device.
+     * @param pid the process pid to connect to.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public static SocketChannel createPassThroughConnection(InetSocketAddress sdbSockAddr,
+            Device device, int pid)
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+
+        SocketChannel sdbChan = SocketChannel.open(sdbSockAddr);
+        try {
+            sdbChan.socket().setTcpNoDelay(true);
+            sdbChan.configureBlocking(false);
+
+            // if the device is not -1, then we first tell sdb we're looking to
+            // talk to a specific device
+            setDevice(sdbChan, device);
+
+            byte[] req = createJdwpForwardRequest(pid);
+            // Log.hexDump(req);
+
+            write(sdbChan, req);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                throw new SdbCommandRejectedException(resp.message);
+            }
+
+            sdbChan.configureBlocking(true);
+        } catch (TimeoutException e) {
+            sdbChan.close();
+            throw e;
+        } catch (IOException e) {
+            sdbChan.close();
+            throw e;
+        }
+
+        return sdbChan;
+    }
+
+    /**
+     * Creates a port forwarding request for sdb. This returns an array
+     * containing "####tcp:{port}:{addStr}".
+     * @param addrStr the host. Can be null.
+     * @param port the port on the device. This does not need to be numeric.
+     */
+    private static byte[] createsdbForwardRequest(String addrStr, int port) {
+        String reqStr;
+
+        if (addrStr == null)
+            reqStr = "tcp:" + port;
+        else
+            reqStr = "tcp:" + port + ":" + addrStr;
+        return formSdbRequest(reqStr);
+    }
+
+    /**
+     * Creates a port forwarding request to a jdwp process. This returns an array
+     * containing "####jwdp:{pid}".
+     * @param pid the jdwp process pid on the device.
+     */
+    private static byte[] createJdwpForwardRequest(int pid) {
+        String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$
+        return formSdbRequest(reqStr);
+    }
+
+    /**
+     * Create an ASCII string preceeded by four hex digits. The opening "####"
+     * is the length of the rest of the string, encoded as ASCII hex (case
+     * doesn't matter). "port" and "host" are what we want to forward to. If
+     * we're on the host side connecting into the device, "addrStr" should be
+     * null.
+     */
+    static byte[] formSdbRequest(String req) {
+
+        byte [] result;
+        byte [] reqByte;
+        byte [] lengthByte;
+
+        try {
+            reqByte = req.getBytes(UTF_ENCODING);
+            
+            //info about reqByte length with 4 digit ex) 001D
+            lengthByte = String.format("%04X", reqByte.length).getBytes();
+
+            result = new byte[reqByte.length + 4];
+            System.arraycopy(lengthByte, 0, result, 0, 4);
+            System.arraycopy(reqByte, 0, result, 4, reqByte.length);
+            
+        } catch (UnsupportedEncodingException uee) {
+            uee.printStackTrace(); // not expected
+            return null;
+        }
+        assert result.length == reqByte.length + 4;
+        return result;
+    }
+
+    /**
+     * Reads the response from sdb after a command.
+     * @param chan The socket channel that is connected to sdb.
+     * @param readDiagString If true, we're expecting an OKAY response to be
+     *      followed by a diagnostic string. Otherwise, we only expect the
+     *      diagnostic string to follow a FAIL.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws IOException in case of I/O error on the connection.
+     */
+    static SdbResponse readSdbResponse(SocketChannel chan, boolean readDiagString)
+            throws TimeoutException, IOException {
+
+        SdbResponse resp = new SdbResponse();
+
+        byte[] reply = new byte[4];
+        read(chan, reply);
+
+        if (isOkay(reply)) {
+            resp.okay = true;
+        } else {
+            readDiagString = true; // look for a reason after the FAIL
+            resp.okay = false;
+        }
+
+        // not a loop -- use "while" so we can use "break"
+        try {
+            while (readDiagString) {
+                // length string is in next 4 bytes
+                byte[] lenBuf = new byte[4];
+                read(chan, lenBuf);
+
+                String lenStr = replyToString(lenBuf);
+
+                int len;
+                try {
+                    len = Integer.parseInt(lenStr, 16);
+                } catch (NumberFormatException nfe) {
+                    Log.w("sdb", "Expected digits, got '" + lenStr + "': "
+                            + lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " "
+                            + lenBuf[3]);
+                    Log.w("sdb", "reply was " + replyToString(reply));
+                    break;
+                }
+
+                byte[] msg = new byte[len];
+                read(chan, msg);
+
+                resp.message = replyToString(msg);
+                Log.v("sdb", "Got reply '" + replyToString(reply) + "', diag='"
+                        + resp.message + "'");
+
+                break;
+            }
+        } catch (Exception e) {
+            // ignore those, since it's just reading the diagnose string, the response will
+            // contain okay==false anyway.
+        }
+
+        return resp;
+    }
+
+    /**
+     * Executes a shell command on the device without output receiver.
+     *
+     * @param sdbSockAddr the {@link InetSocketAddress} to sdb.
+     * @param command the shell command to execute
+     * @param device the {@link IDevice} on which to execute the command.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+        *
+     */
+    static void executeRemoteCommand(InetSocketAddress sdbSockAddr,
+            String command, IDevice device)
+            throws SdbCommandRejectedException, IOException {
+        Log.v("sdb", "execute: running " + command);
+
+        SocketChannel sdbChan = null;
+        try {
+            sdbChan = SocketChannel.open(sdbSockAddr);
+            sdbChan.configureBlocking(false);
+
+            // if the device is not -1, then we first tell sdb we're looking to
+            // talk
+            // to a specific device
+            setDevice(sdbChan, device);
+
+            byte[] request = formSdbRequest("shell:" + command); //$NON-NLS-1$
+            write(sdbChan, request);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                Log.e("sdb", "sdb rejected shell command (" + command + "): " + resp.message);
+                throw new SdbCommandRejectedException(resp.message);
+            }
+            
+            byte[] data = new byte[16384];
+            ByteBuffer buf = ByteBuffer.wrap(data);
+               int exitCount = 0;
+            while(true) {
+               int count = 0;
+                   sdbChan.read(buf);
+                   if (count < 0) {
+                       // met EOF
+                       break;
+                   } else if (count == 0) {
+                       if (exitCount < 5) {
+                               try {
+                                   int wait = WAIT_TIME * 5;
+                                   exitCount++;
+                                   Thread.sleep(wait);
+                               } catch (InterruptedException ie) {
+                               }
+                       } else
+                               break;
+                   } else {
+                       buf.rewind();
+                   }
+            }
+        } finally {
+            if (sdbChan != null) {
+                sdbChan.close();
+            }
+        }
+    }
+
+    /**
+     * Executes a shell command on the device and retrieve the output. The output is
+     * handed to <var>rcvr</var> as it arrives.
+     *
+     * @param sdbSockAddr the {@link InetSocketAddress} to sdb.
+     * @param command the shell command to execute
+     * @param device the {@link IDevice} on which to execute the command.
+     * @param rcvr the {@link IShellOutputReceiver} that will receives the output of the shell
+     *            command
+     * @param maxTimeToOutputResponse max time between command output. If more time passes
+     *            between command output, the method will throw
+     *            {@link ShellCommandUnresponsiveException}. A value of 0 means the method will
+     *            wait forever for command output and never throw.
+     * @throws TimeoutException in case of timeout on the connection when sending the command.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws ShellCommandUnresponsiveException in case the shell command doesn't send any output
+     *            for a period longer than <var>maxTimeToOutputResponse</var>.
+     * @throws IOException in case of I/O error on the connection.
+     *
+     * @see SdbPreferences#getTimeOut()
+     */
+    static void executeRemoteCommand(InetSocketAddress sdbSockAddr,
+            String command, IDevice device, IShellOutputReceiver rcvr, int maxTimeToOutputResponse)
+            throws TimeoutException, SdbCommandRejectedException, ShellCommandUnresponsiveException,
+            IOException {
+        Log.v("sdb", "execute: running " + command);
+
+        SocketChannel sdbChan = null;
+        try {
+            sdbChan = SocketChannel.open(sdbSockAddr);
+            sdbChan.configureBlocking(false);
+
+            // if the device is not -1, then we first tell sdb we're looking to
+            // talk
+            // to a specific device
+            setDevice(sdbChan, device);
+
+            byte[] request = formSdbRequest("shell:" + command); //$NON-NLS-1$
+            write(sdbChan, request);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                Log.e("sdb", "sdb rejected shell command (" + command + "): " + resp.message);
+                throw new SdbCommandRejectedException(resp.message);
+            }
+
+            byte[] data = new byte[16384];
+            ByteBuffer buf = ByteBuffer.wrap(data);
+            int timeToResponseCount = 0;
+            while (true) {
+                int count;
+
+                if (rcvr != null && rcvr.isCancelled()) {
+                    Log.v("sdb", "execute: cancelled");
+                    break;
+                }
+
+                count = sdbChan.read(buf);
+                if (count < 0) {
+                    // we're at the end, we flush the output
+                    rcvr.flush();
+                    Log.v("sdb", "execute '" + command + "' on '" + device + "' : EOF hit. Read: "
+                            + count);
+                    break;
+                } else if (count == 0) {
+                    try {
+                        int wait = WAIT_TIME * 5;
+                        timeToResponseCount += wait;
+                        if (maxTimeToOutputResponse > 0 &&
+                                timeToResponseCount > maxTimeToOutputResponse) {
+                            throw new ShellCommandUnresponsiveException();
+                        }
+                        Thread.sleep(wait);
+                    } catch (InterruptedException ie) {
+                    }
+                } else {
+                    // reset timeout
+                    timeToResponseCount = 0;
+
+                    // send data to receiver if present
+                    if (rcvr != null) {
+                        rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
+                    }
+                    buf.rewind();
+                }
+            }
+        } finally {
+            if (sdbChan != null) {
+                sdbChan.close();
+            }
+            Log.v("sdb", "execute: returning");
+        }
+    }
+
+    /**
+     * Runs the Event log service on the {@link Device}, and provides its output to the
+     * {@link LogReceiver}.
+     * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+     * @param sdbSockAddr the socket address to connect to sdb
+     * @param device the Device on which to run the service
+     * @param rcvr the {@link LogReceiver} to receive the log output
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public static void runEventLogService(InetSocketAddress sdbSockAddr, Device device,
+            LogReceiver rcvr) throws TimeoutException, SdbCommandRejectedException, IOException {
+        runLogService(sdbSockAddr, device, "events", rcvr); //$NON-NLS-1$
+    }
+
+    /**
+     * Runs a log service on the {@link Device}, and provides its output to the {@link LogReceiver}.
+     * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
+     * @param sdbSockAddr the socket address to connect to sdb
+     * @param device the Device on which to run the service
+     * @param logName the name of the log file to output
+     * @param rcvr the {@link LogReceiver} to receive the log output
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public static void runLogService(InetSocketAddress sdbSockAddr, Device device, String logName,
+            LogReceiver rcvr) throws TimeoutException, SdbCommandRejectedException, IOException {
+        SocketChannel sdbChan = null;
+
+        try {
+            sdbChan = SocketChannel.open(sdbSockAddr);
+            sdbChan.configureBlocking(false);
+
+            // if the device is not -1, then we first tell sdb we're looking to talk
+            // to a specific device
+            setDevice(sdbChan, device);
+
+            byte[] request = formSdbRequest("log:" + logName);
+            write(sdbChan, request);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                throw new SdbCommandRejectedException(resp.message);
+            }
+
+            byte[] data = new byte[16384];
+            ByteBuffer buf = ByteBuffer.wrap(data);
+            while (true) {
+                int count;
+
+                if (rcvr != null && rcvr.isCancelled()) {
+                    break;
+                }
+
+                count = sdbChan.read(buf);
+                if (count < 0) {
+                    break;
+                } else if (count == 0) {
+                    try {
+                        Thread.sleep(WAIT_TIME * 5);
+                    } catch (InterruptedException ie) {
+                    }
+                } else {
+                    if (rcvr != null) {
+                        rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
+                    }
+                    buf.rewind();
+                }
+            }
+        } finally {
+            if (sdbChan != null) {
+                sdbChan.close();
+            }
+        }
+    }
+
+    /**
+     * Creates a port forwarding between a local and a remote port.
+     * @param sdbSockAddr the socket address to connect to sdb
+     * @param device the device on which to do the port fowarding
+     * @param localPort the local port to forward
+     * @param remotePort the remote port.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public static void createForward(InetSocketAddress sdbSockAddr, Device device, int localPort,
+            int remotePort) throws TimeoutException, SdbCommandRejectedException, IOException {
+
+        SocketChannel sdbChan = null;
+        try {
+            sdbChan = SocketChannel.open(sdbSockAddr);
+            sdbChan.configureBlocking(false);
+
+            byte[] request = formSdbRequest(String.format(
+                    "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
+                    device.getSerialNumber(), localPort, remotePort));
+
+            write(sdbChan, request);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                Log.w("create-forward", "Error creating forward: " + resp.message);
+                throw new SdbCommandRejectedException(resp.message);
+            }
+        } finally {
+            if (sdbChan != null) {
+                sdbChan.close();
+            }
+        }
+    }
+
+    /**
+     * Remove a port forwarding between a local and a remote port.
+     * @param sdbSockAddr the socket address to connect to sdb
+     * @param device the device on which to remove the port fowarding
+     * @param localPort the local port of the forward
+     * @param remotePort the remote port.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    public static void removeForward(InetSocketAddress sdbSockAddr, Device device, int localPort,
+            int remotePort) throws TimeoutException, SdbCommandRejectedException, IOException {
+
+        SocketChannel sdbChan = null;
+        try {
+            sdbChan = SocketChannel.open(sdbSockAddr);
+            sdbChan.configureBlocking(false);
+
+            byte[] request = formSdbRequest(String.format(
+                    "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
+                    device.getSerialNumber(), localPort, remotePort));
+
+            write(sdbChan, request);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                Log.w("remove-forward", "Error creating forward: " + resp.message);
+                throw new SdbCommandRejectedException(resp.message);
+            }
+        } finally {
+            if (sdbChan != null) {
+                sdbChan.close();
+            }
+        }
+    }
+
+    /**
+     * Checks to see if the first four bytes in "reply" are OKAY.
+     */
+    static boolean isOkay(byte[] reply) {
+        return reply[0] == (byte)'O' && reply[1] == (byte)'K'
+                && reply[2] == (byte)'A' && reply[3] == (byte)'Y';
+    }
+
+    /**
+     * Converts an sdb reply to a string.
+     */
+    static String replyToString(byte[] reply) {
+        String result;
+        try {
+            result = new String(reply, DEFAULT_ENCODING);
+        } catch (UnsupportedEncodingException uee) {
+            uee.printStackTrace(); // not expected
+            result = "";
+        }
+        return result;
+    }
+
+    /**
+     * Reads from the socket until the array is filled, or no more data is coming (because
+     * the socket closed or the timeout expired).
+     * <p/>This uses the default time out value.
+     *
+     * @param chan the opened socket to read from. It must be in non-blocking
+     *      mode for timeouts to work
+     * @param data the buffer to store the read data into.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws IOException in case of I/O error on the connection.
+     */
+    static void read(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
+        read(chan, data, -1, SdbPreferences.getTimeOut());
+    }
+
+    /**
+     * Reads from the socket until the array is filled, the optional length
+     * is reached, or no more data is coming (because the socket closed or the
+     * timeout expired). After "timeout" milliseconds since the
+     * previous successful read, this will return whether or not new data has
+     * been found.
+     *
+     * @param chan the opened socket to read from. It must be in non-blocking
+     *      mode for timeouts to work
+     * @param data the buffer to store the read data into.
+     * @param length the length to read or -1 to fill the data buffer completely
+     * @param timeout The timeout value. A timeout of zero means "wait forever".
+     */
+    static void read(SocketChannel chan, byte[] data, int length, int timeout)
+            throws TimeoutException, IOException {
+        ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
+        int numWaits = 0;
+
+        while (buf.position() != buf.limit()) {
+            int count;
+
+            count = chan.read(buf);
+            if (count < 0) {
+                Log.d("sdb", "read: channel EOF");
+                throw new IOException("EOF");
+            } else if (count == 0) {
+                // TODO: need more accurate timeout?
+                if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
+                    Log.d("sdb", "read: timeout");
+                    throw new TimeoutException();
+                }
+                // non-blocking spin
+                try {
+                    Thread.sleep(WAIT_TIME);
+                } catch (InterruptedException ie) {
+                }
+                numWaits++;
+            } else {
+                numWaits = 0;
+            }
+        }
+    }
+
+    /**
+     * Write until all data in "data" is written or the connection fails or times out.
+     * <p/>This uses the default time out value.
+     * @param chan the opened socket to write to.
+     * @param data the buffer to send.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws IOException in case of I/O error on the connection.
+     */
+    static void write(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
+        write(chan, data, -1, SdbPreferences.getTimeOut());
+    }
+
+    /**
+     * Write until all data in "data" is written, the optional length is reached,
+     * the timeout expires, or the connection fails. Returns "true" if all
+     * data was written.
+     * @param chan the opened socket to write to.
+     * @param data the buffer to send.
+     * @param length the length to write or -1 to send the whole buffer.
+     * @param timeout The timeout value. A timeout of zero means "wait forever".
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws IOException in case of I/O error on the connection.
+     */
+    static void write(SocketChannel chan, byte[] data, int length, int timeout)
+            throws TimeoutException, IOException {
+        ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
+        int numWaits = 0;
+
+        while (buf.position() != buf.limit()) {
+            int count;
+
+            count = chan.write(buf);
+            if (count < 0) {
+                Log.d("sdb", "write: channel EOF");
+                throw new IOException("channel EOF");
+            } else if (count == 0) {
+                // TODO: need more accurate timeout?
+                if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
+                    Log.d("sdb", "write: timeout");
+                    throw new TimeoutException();
+                }
+                // non-blocking spin
+                try {
+                    Thread.sleep(WAIT_TIME);
+                } catch (InterruptedException ie) {
+                }
+                numWaits++;
+            } else {
+                numWaits = 0;
+            }
+        }
+    }
+
+    /**
+     * tells sdb to talk to a specific device
+     *
+     * @param sdbChan the socket connection to sdb
+     * @param device The device to talk to.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws SdbCommandRejectedException if sdb rejects the command
+     * @throws IOException in case of I/O error on the connection.
+     */
+    static void setDevice(SocketChannel sdbChan, IDevice device)
+            throws TimeoutException, SdbCommandRejectedException, IOException {
+        // if the device is not -1, then we first tell sdb we're looking to talk
+        // to a specific device
+        if (device != null) {
+            String msg = "host:transport:" + device.getSerialNumber(); //$NON-NLS-1$
+            byte[] device_query = formSdbRequest(msg);
+
+            write(sdbChan, device_query);
+
+            SdbResponse resp = readSdbResponse(sdbChan, false /* readDiagString */);
+            if (resp.okay == false) {
+                throw new SdbCommandRejectedException(resp.message,
+                        true/*errorDuringDeviceSelection*/);
+            }
+        }
+    }
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbPreferences.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbPreferences.java
new file mode 100644 (file)
index 0000000..4e4d326
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import org.tizen.sdblib.Log.LogLevel;
+
+/**
+ * Preferences for the sdb library.
+ * <p/>This class does not handle storing the preferences. It is merely a central point for
+ * applications using the sdblib to override the default values.
+ * <p/>Various components of the sdblib query this class to get their values.
+ * <p/>Calls to some <code>set##()</code> methods will update the components using the values
+ * right away, while other methods will have no effect once {@link AndroidDebugBridge#init(boolean)}
+ * has been called.
+ * <p/>Check the documentation of each method.
+ */
+public final class SdbPreferences {
+
+       public final static LogLevel DEFAULT_LOG_LEVEL = LogLevel.ERROR;
+    /** Default timeout values for sdb connection (milliseconds) */
+    public static final int DEFAULT_TIMEOUT = 5000; // standard delay, in ms
+
+    private static LogLevel sLogLevel = DEFAULT_LOG_LEVEL;
+    private static int sTimeOut = DEFAULT_TIMEOUT;
+
+    /**
+     * Returns the minimum {@link LogLevel} being displayed.
+     */
+    public static LogLevel getLogLevel() {
+        return sLogLevel;
+    }
+
+    /**
+     * Sets the minimum {@link LogLevel} to display.
+     * <p/>This change takes effect right away.
+     */
+    public static void setLogLevel(String value) {
+        sLogLevel = LogLevel.getByString(value);
+
+        Log.setLevel(sLogLevel);
+    }
+
+    /**
+     * Returns the timeout to be used in sdb connections (milliseconds).
+     */
+    public static int getTimeOut() {
+        return sTimeOut;
+    }
+
+    /**
+     * Sets the timeout value for sdb connection.
+     * <p/>This change takes effect for newly created connections only.
+     * @param timeOut the timeout value (milliseconds).
+     */
+    public static void setTimeOut(int timeOut) {
+        sTimeOut = timeOut;
+    }
+
+    /**
+     * Non accessible constructor.
+     */
+    private SdbPreferences() {
+        // pass, only static methods in the class.
+    }
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbShellProcess.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/SdbShellProcess.java
new file mode 100644 (file)
index 0000000..69b600c
--- /dev/null
@@ -0,0 +1,82 @@
+package org.tizen.sdblib;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+public class SdbShellProcess extends Process {
+
+       public Process process = null;
+       public BufferedWriter bw = null;
+       
+       public SdbShellProcess(Process process, String command) {
+               this.process = process;
+               bw = new BufferedWriter(new OutputStreamWriter(this.process.getOutputStream()));
+               try {
+                       bw.write(command + ";exit");
+                       bw.newLine();
+                       bw.flush();
+               } catch (IOException e) {
+                       // do nothing
+               }
+       }
+       
+       private void interrupt() {
+               if (bw == null)
+                       return;
+
+               char c = 0x03; //standard code representing "Ctrl-C" sequence
+               try {
+                       bw.write(c);
+                       bw.flush();
+                       Thread.sleep(250); // wait for terminate
+               } catch (IOException e) {
+                       // do nothing
+               } catch (InterruptedException e) {
+                       // do nothing
+               } finally {
+                       try {
+                               bw.close();
+                       } catch (IOException e) {
+                               // do nothing
+                       }
+               }
+       }
+       
+       @Override
+       public void destroy() {
+               interrupt();
+               try {
+                       process.exitValue();
+               } catch (IllegalThreadStateException e) {
+                       process.destroy();
+               }
+       }
+
+       @Override
+       public int exitValue() {
+               return process.exitValue();
+       }
+
+       @Override
+       public InputStream getErrorStream() {
+               return process.getErrorStream();
+       }
+
+       @Override
+       public InputStream getInputStream() {
+               return process.getInputStream();
+       }
+
+       @Override
+       public OutputStream getOutputStream() {
+               return process.getOutputStream();
+       }
+
+       @Override
+       public int waitFor() throws InterruptedException {
+               return process.waitFor();
+       }
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/ShellCommandUnresponsiveException.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/ShellCommandUnresponsiveException.java
new file mode 100644 (file)
index 0000000..bcd8bf4
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 org.tizen.sdblib;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when a shell command executed on a device takes too long to send its output.
+ * <p/>The command may not actually be unresponsive, it just has spent too much time not outputting
+ * any thing to the console.
+ */
+public class ShellCommandUnresponsiveException extends IOException {
+    private static final long serialVersionUID = 1L;
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/SmartDevelopmentBridge.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/SmartDevelopmentBridge.java
new file mode 100644 (file)
index 0000000..1d30a74
--- /dev/null
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+
+/**
+ * A connection to the host-side smart development bridge (sdb)
+ * <p/>This is the central point to communicate with any devices, emulators, or the applications
+ * running on them.
+ * <p/><b>{@link #init(boolean)} must be called before anything is done.</b>
+ */
+public final class SmartDevelopmentBridge {
+    private final static String SDB = "sdb"; //$NON-NLS-1$
+    private final static String SDBLIB = "sdblib"; //$NON-NLS-1$
+    private final static String SERVER_PORT_ENV_VAR = "SDB_SERVER_PORT"; //$NON-NLS-1$
+
+    // Where to find the SDB bridge.
+    final static String SDB_HOST = "127.0.0.1"; //$NON-NLS-1$
+    final static int SDB_PORT = 26099;
+
+    private static InetAddress sHostAddr;
+    private static InetSocketAddress sSocketAddr;
+
+    private static SmartDevelopmentBridge sThis;
+
+    /** Full path to sdb. */
+    private String mSdbOsLocation = null;
+
+    private boolean mStarted = false;
+
+    private DeviceMonitor mDeviceMonitor;
+
+    private final static ArrayList<IDebugBridgeChangeListener> sBridgeListeners =
+        new ArrayList<IDebugBridgeChangeListener>();
+    private final static ArrayList<IDeviceChangeListener> sDeviceListeners =
+        new ArrayList<IDeviceChangeListener>();
+
+    // lock object for synchronization
+    private static final Object sLock = sBridgeListeners;
+
+    /**
+     * Classes which implement this interface provide a method that deals
+     * with {@link SmartDevelopmentBridge} changes.
+     */
+    public interface IDebugBridgeChangeListener {
+        /**
+         * Sent when a new {@link SmartDevelopmentBridge} is connected.
+         * <p/>
+         * This is sent from a non UI thread.
+         * @param bridge the new {@link SmartDevelopmentBridge} object.
+         */
+        public void bridgeChanged(SmartDevelopmentBridge bridge);
+    }
+
+    /**
+     * Classes which implement this interface provide methods that deal
+     * with {@link IDevice} addition, deletion, and changes.
+     */
+    public interface IDeviceChangeListener {
+        /**
+         * Sent when the a device is connected to the {@link SmartDevelopmentBridge}.
+         * <p/>
+         * This is sent from a non UI thread.
+         * @param device the new device.
+         */
+        public void deviceConnected(IDevice device);
+
+        /**
+         * Sent when the a device is connected to the {@link SmartDevelopmentBridge}.
+         * <p/>
+         * This is sent from a non UI thread.
+         * @param device the new device.
+         */
+        public void deviceDisconnected(IDevice device);
+
+        /**
+         * Sent when a device data changed, or when clients are started/terminated on the device.
+         * <p/>
+         * This is sent from a non UI thread.
+         * @param device the device that was updated.
+         * @param changeMask the mask describing what changed. It can contain any of the following
+         * values: {@link IDevice#CHANGE_BUILD_INFO}, {@link IDevice#CHANGE_STATE},
+         * {@link IDevice#CHANGE_CLIENT_LIST}
+         */
+        public void deviceChanged(IDevice device, int changeMask);
+    }
+
+    /**
+     * Initializes the <code>sdb</code> library.
+     * <p/>This must be called once <b>before</b> any call to
+     * {@link #createBridge(String, boolean)}.
+     * <p>The library can be initialized in 2 ways:
+     * <ul>
+     * <li>Mode 1: <var>clientSupport</var> == <code>true</code>.<br>The library monitors the
+     * devices and the applications running on them. It will connect to each application, as a
+     * debugger of sort, to be able to interact with them through JDWP packets.</li>
+     * <li>Mode 2: <var>clientSupport</var> == <code>false</code>.<br>The library only monitors
+     * devices. The applications are left untouched, letting other tools built on
+     * <code>sdblib</code> to connect a debugger to them.</li>
+     * </ul>
+     * <p/><b>Only one tool can run in mode 1 at the same time.</b>
+     * <p/>Note that mode 1 does not prevent debugging of applications running on devices. Mode 1
+     * lets debuggers connect to <code>sdblib</code> which acts as a proxy between the debuggers and
+     * the applications to debug. See {@link Client#getDebuggerListenPort()}.
+     * <p/>The preferences of <code>sdblib</code> should also be initialized with whatever default
+     * values were changed from the default values.
+     * <p/>When the application quits, {@link #terminate()} should be called.
+     * @param clientSupport Indicates whether the library should enable the monitoring and
+     * interaction with applications running on the devices.
+     * @see SmartDevelopmentBridge#createBridge(String, boolean)
+     * @see SdbPreferences
+     */
+    public static void init() {
+        // Determine port and instantiate socket address.
+        initsdbSocketAddr();
+    }
+
+    /**
+     * Terminates the sdb library. This must be called upon application termination.
+     */
+    public static void terminate() {
+        // kill the monitoring services
+        if (sThis != null && sThis.mDeviceMonitor != null) {
+            sThis.mDeviceMonitor.stop();
+            sThis.mDeviceMonitor = null;
+        }
+    }
+
+    /**
+     * Returns the socket address of the SDB server on the host.
+     */
+    public static InetSocketAddress getSocketAddress() {
+        return sSocketAddr;
+    }
+
+    /**
+     * Creates a {@link SmartDevelopmentBridge} that is not linked to any particular executable.
+     * <p/>This bridge will expect sdb to be running. It will not be able to start/stop
+     * sdb.
+     * <p/>If a bridge has already been started, it is directly returned with no changes (similar
+     * to calling {@link #getBridge()}).
+     * @return a connected bridge.
+     */
+    public static SmartDevelopmentBridge createBridge() {
+        synchronized (sLock) {
+            if (sThis != null) {
+                return sThis;
+            }
+
+            try {
+                sThis = new SmartDevelopmentBridge();
+                sThis.start();
+            } catch (InvalidParameterException e) {
+                sThis = null;
+            }
+
+            // because the listeners could remove themselves from the list while processing
+            // their event callback, we make a copy of the list and iterate on it instead of
+            // the main list.
+            // This mostly happens when the application quits.
+            IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
+                    new IDebugBridgeChangeListener[sBridgeListeners.size()]);
+
+            // notify the listeners of the change
+            for (IDebugBridgeChangeListener listener : listenersCopy) {
+                // we attempt to catch any exception so that a bad listener doesn't kill our
+                // thread
+                try {
+                    listener.bridgeChanged(sThis);
+                } catch (Exception e) {
+                    Log.e(SDBLIB, e);
+                }
+            }
+
+            return sThis;
+        }
+    }
+
+
+    /**
+     * Creates a new debug bridge from the location of the command line tool.
+     * <p/>
+     * Any existing server will be disconnected, unless the location is the same and
+     * <code>forceNewBridge</code> is set to false.
+     * @param osLocation the location of the command line tool 'sdb'
+     * @param forceNewBridge force creation of a new bridge even if one with the same location
+     * already exists.
+     * @return a connected bridge.
+     * @throws Exception
+     */
+    public static SmartDevelopmentBridge createBridge(String osLocation, boolean forceNewBridge){
+        synchronized (sLock) {
+            if (sThis != null) {
+                if (sThis.mSdbOsLocation != null && sThis.mSdbOsLocation.equals(osLocation) &&
+                        forceNewBridge == false) {
+                    return sThis;
+                } else {
+                    // stop the current server
+                    sThis.stop();
+                }
+            }
+
+            sThis = new SmartDevelopmentBridge(osLocation);
+            sThis.start();
+
+            // because the listeners could remove themselves from the list while processing
+            // their event callback, we make a copy of the list and iterate on it instead of
+            // the main list.
+            // This mostly happens when the application quits.
+            IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
+                    new IDebugBridgeChangeListener[sBridgeListeners.size()]);
+
+            // notify the listeners of the change
+            for (IDebugBridgeChangeListener listener : listenersCopy) {
+                // we attempt to catch any exception so that a bad listener doesn't kill our
+                // thread
+                try {
+                    listener.bridgeChanged(sThis);
+                } catch (Exception e) {
+                    Log.e(SDBLIB, e);
+                }
+            }
+
+            return sThis;
+        }
+    }
+
+    /**
+     * Returns the current debug bridge. Can be <code>null</code> if none were created.
+     */
+    public static SmartDevelopmentBridge getBridge() {
+        return sThis;
+    }
+
+    public String getSdbOsLocation() {
+               return mSdbOsLocation;
+       }
+
+       /**
+     * Disconnects the current debug bridge, and destroy the object.
+     * <p/>This also stops the current sdb host server.
+     * <p/>
+     * A new object will have to be created with {@link #createBridge(String, boolean)}.
+     */
+    public static void disconnectBridge() {
+        synchronized (sLock) {
+            if (sThis != null) {
+                sThis.stop();
+                sThis = null;
+
+                // because the listeners could remove themselves from the list while processing
+                // their event callback, we make a copy of the list and iterate on it instead of
+                // the main list.
+                // This mostly happens when the application quits.
+                IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
+                        new IDebugBridgeChangeListener[sBridgeListeners.size()]);
+
+                // notify the listeners.
+                for (IDebugBridgeChangeListener listener : listenersCopy) {
+                    // we attempt to catch any exception so that a bad listener doesn't kill our
+                    // thread
+                    try {
+                        listener.bridgeChanged(sThis);
+                    } catch (Exception e) {
+                        Log.e(SDBLIB, e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds the listener to the collection of listeners who will be notified when a new
+     * {@link SmartDevelopmentBridge} is connected, by sending it one of the messages defined
+     * in the {@link IDebugBridgeChangeListener} interface.
+     * @param listener The listener which should be notified.
+     */
+    public static void addDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
+        synchronized (sLock) {
+            if (sBridgeListeners.contains(listener) == false) {
+                sBridgeListeners.add(listener);
+                if (sThis != null) {
+                    // we attempt to catch any exception so that a bad listener doesn't kill our
+                    // thread
+                    try {
+                        listener.bridgeChanged(sThis);
+                    } catch (Exception e) {
+                        Log.e(SDBLIB, e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes the listener from the collection of listeners who will be notified when a new
+     * {@link SmartDevelopmentBridge} is started.
+     * @param listener The listener which should no longer be notified.
+     */
+    public static void removeDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
+        synchronized (sLock) {
+            sBridgeListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Adds the listener to the collection of listeners who will be notified when a {@link IDevice}
+     * is connected, disconnected, or when its properties or its {@link Client} list changed,
+     * by sending it one of the messages defined in the {@link IDeviceChangeListener} interface.
+     * @param listener The listener which should be notified.
+     */
+    public static void addDeviceChangeListener(IDeviceChangeListener listener) {
+        synchronized (sLock) {
+            if (sDeviceListeners.contains(listener) == false) {
+                sDeviceListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Removes the listener from the collection of listeners who will be notified when a
+     * {@link IDevice} is connected, disconnected, or when its properties or its {@link Client}
+     * list changed.
+     * @param listener The listener which should no longer be notified.
+     */
+    public static void removeDeviceChangeListener(IDeviceChangeListener listener) {
+        synchronized (sLock) {
+            sDeviceListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Returns the devices.
+     * @see #hasInitialDeviceList()
+     */
+    public IDevice[] getDevices() {
+        synchronized (sLock) {
+            if (mDeviceMonitor != null) {
+                return mDeviceMonitor.getDevices();
+            }
+        }
+
+        return new IDevice[0];
+    }
+
+    /**
+     * Returns whether the bridge has acquired the initial list from sdb after being created.
+     * <p/>Calling {@link #getDevices()} right after {@link #createBridge(String, boolean)} will
+     * generally result in an empty list. This is due to the internal asynchronous communication
+     * mechanism with <code>sdb</code> that does not guarantee that the {@link IDevice} list has been
+     * built before the call to {@link #getDevices()}.
+     * <p/>The recommended way to get the list of {@link IDevice} objects is to create a
+     * {@link IDeviceChangeListener} object.
+     */
+    public boolean hasInitialDeviceList() {
+        if (mDeviceMonitor != null) {
+            return mDeviceMonitor.hasInitialDeviceList();
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether the {@link SmartDevelopmentBridge} object is still connected to the sdb daemon.
+     */
+    public boolean isConnected() {
+        if (mDeviceMonitor != null) {
+            return mDeviceMonitor.isMonitoring();
+        }
+        return false;
+    }
+
+    /**
+     * Returns the number of times the {@link SmartDevelopmentBridge} object attempted to connect
+     * to the sdb daemon.
+     */
+    public int getConnectionAttemptCount() {
+        if (mDeviceMonitor != null) {
+            return mDeviceMonitor.getConnectionAttemptCount();
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the number of times the {@link SmartDevelopmentBridge} object attempted to restart
+     * the sdb daemon.
+     */
+    public int getRestartAttemptCount() {
+        if (mDeviceMonitor != null) {
+            return mDeviceMonitor.getRestartAttemptCount();
+        }
+        return -1;
+    }
+
+    /**
+     * Creates a new bridge.
+     * @param osLocation the location of the command line tool
+     * @throws InvalidParameterException
+     */
+    private SmartDevelopmentBridge(String osLocation) throws InvalidParameterException {
+        if (osLocation == null || osLocation.length() == 0) {
+            throw new InvalidParameterException();
+        }
+        mSdbOsLocation = osLocation;
+    }
+
+    /**
+     * Creates a new bridge not linked to any particular sdb executable.
+     */
+    private SmartDevelopmentBridge() {
+    }
+
+    /**
+     * Starts the debug bridge.
+     * @return true if success.
+     */
+    boolean start() {
+
+        mStarted = true;
+        // now that the bridge is connected, we start the underlying services.
+        mDeviceMonitor = new DeviceMonitor(this);
+        mDeviceMonitor.start();
+
+        return true;
+    }
+
+   /**
+     * Kills the debug bridge, and the sdb host server.
+     * @return true if success
+     */
+    boolean stop() {
+        // if we haven't started we return false;
+        if (mStarted == false) {
+            return false;
+        }
+
+        // kill the monitoring services
+        mDeviceMonitor.stop();
+        mDeviceMonitor = null;
+
+        if (stopSdb() == false) {
+            return false;
+        }
+
+        mStarted = false;
+        return true;
+    }
+
+    /**
+     * Restarts sdb, but not the services around it.
+     * @return true if success.
+     */
+    public boolean restart() {
+        if (mSdbOsLocation == null) {
+            Log.e(SDB,
+                    "Cannot restart sdb when SmartDevelopmentBridge is created without the location of sdb."); //$NON-NLS-1$
+            return false;
+        }
+
+        synchronized (this) {
+            stopSdb();
+
+            boolean restart = startSdb();
+
+            if (restart && mDeviceMonitor == null) {
+                mDeviceMonitor = new DeviceMonitor(this);
+                mDeviceMonitor.start();
+            }
+
+            return restart;
+        }
+    }
+
+    /**
+     * Notify the listener of a new {@link IDevice}.
+     * <p/>
+     * The notification of the listeners is done in a synchronized block. It is important to
+     * expect the listeners to potentially access various methods of {@link IDevice} as well as
+     * {@link #getDevices()} which use internal locks.
+     * <p/>
+     * For this reason, any call to this method from a method of {@link DeviceMonitor},
+     * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+     * the {@link SmartDevelopmentBridge} lock. Access to this lock is done through {@link #getLock()}.
+     * @param device the new <code>IDevice</code>.
+     * @see #getLock()
+     */
+    void deviceConnected(IDevice device) {
+        // because the listeners could remove themselves from the list while processing
+        // their event callback, we make a copy of the list and iterate on it instead of
+        // the main list.
+        // This mostly happens when the application quits.
+        IDeviceChangeListener[] listenersCopy = null;
+        synchronized (sLock) {
+            listenersCopy = sDeviceListeners.toArray(
+                    new IDeviceChangeListener[sDeviceListeners.size()]);
+        }
+
+        // Notify the listeners
+        for (IDeviceChangeListener listener : listenersCopy) {
+            // we attempt to catch any exception so that a bad listener doesn't kill our
+            // thread
+            try {
+                listener.deviceConnected(device);
+            } catch (Exception e) {
+                Log.e(SDBLIB, e);
+            }
+        }
+    }
+
+    /**
+     * Notify the listener of a disconnected {@link IDevice}.
+     * <p/>
+     * The notification of the listeners is done in a synchronized block. It is important to
+     * expect the listeners to potentially access various methods of {@link IDevice} as well as
+     * {@link #getDevices()} which use internal locks.
+     * <p/>
+     * For this reason, any call to this method from a method of {@link DeviceMonitor},
+     * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+     * the {@link SmartDevelopmentBridge} lock. Access to this lock is done through {@link #getLock()}.
+     * @param device the disconnected <code>IDevice</code>.
+     * @see #getLock()
+     */
+    void deviceDisconnected(IDevice device) {
+        // because the listeners could remove themselves from the list while processing
+        // their event callback, we make a copy of the list and iterate on it instead of
+        // the main list.
+        // This mostly happens when the application quits.
+        IDeviceChangeListener[] listenersCopy = null;
+        synchronized (sLock) {
+            listenersCopy = sDeviceListeners.toArray(
+                    new IDeviceChangeListener[sDeviceListeners.size()]);
+        }
+
+        // Notify the listeners
+        for (IDeviceChangeListener listener : listenersCopy) {
+            // we attempt to catch any exception so that a bad listener doesn't kill our
+            // thread
+            try {
+                listener.deviceDisconnected(device);
+            } catch (Exception e) {
+                Log.e(SDBLIB, e);
+            }
+        }
+    }
+
+    /**
+     * Notify the listener of a modified {@link IDevice}.
+     * <p/>
+     * The notification of the listeners is done in a synchronized block. It is important to
+     * expect the listeners to potentially access various methods of {@link IDevice} as well as
+     * {@link #getDevices()} which use internal locks.
+     * <p/>
+     * For this reason, any call to this method from a method of {@link DeviceMonitor},
+     * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+     * the {@link SmartDevelopmentBridge} lock. Access to this lock is done through {@link #getLock()}.
+     * @param device the modified <code>IDevice</code>.
+     * @see #getLock()
+     */
+    void deviceChanged(IDevice device, int changeMask) {
+        // because the listeners could remove themselves from the list while processing
+        // their event callback, we make a copy of the list and iterate on it instead of
+        // the main list.
+        // This mostly happens when the application quits.
+        IDeviceChangeListener[] listenersCopy = null;
+        synchronized (sLock) {
+            listenersCopy = sDeviceListeners.toArray(
+                    new IDeviceChangeListener[sDeviceListeners.size()]);
+        }
+
+        // Notify the listeners
+        for (IDeviceChangeListener listener : listenersCopy) {
+            // we attempt to catch any exception so that a bad listener doesn't kill our
+            // thread
+            try {
+                listener.deviceChanged(device, changeMask);
+            } catch (Exception e) {
+                Log.e(SDBLIB, e);
+            }
+        }
+    }
+
+    /**
+     * Returns the {@link DeviceMonitor} object.
+     */
+    DeviceMonitor getDeviceMonitor() {
+        return mDeviceMonitor;
+    }
+
+    /**
+     * Starts the sdb host side server.
+     * @return true if success
+     */
+    synchronized boolean startSdb() {
+        if (mSdbOsLocation == null) {
+            Log.e(SDB,
+                "Cannot start sdb when SmartDevelopmentBridge is created without the location of sdb."); //$NON-NLS-1$
+            return false;
+        }
+
+        Process proc;
+        int status = -1;
+
+        try {
+            String[] command = new String[2];
+            command[0] = mSdbOsLocation;
+            command[1] = "start-server"; //$NON-NLS-1$
+            Log.d(SDBLIB,
+                    String.format("Launching '%1$s %2$s' to ensure SDB is running.", //$NON-NLS-1$
+                    mSdbOsLocation, command[1]));
+            proc = Runtime.getRuntime().exec(command);
+
+            ArrayList<String> errorOutput = new ArrayList<String>();
+            ArrayList<String> stdOutput = new ArrayList<String>();
+            status = grabProcessOutput(proc, errorOutput, stdOutput,
+                    false /* waitForReaders */);
+
+        } catch (IOException ioe) {
+            Log.d(SDBLIB, "Unable to run 'sdb': " + ioe.getMessage()); //$NON-NLS-1$
+            // we'll return false;
+        } catch (InterruptedException ie) {
+            Log.d(SDBLIB, "Unable to run 'sdb': " + ie.getMessage()); //$NON-NLS-1$
+            // we'll return false;
+        }
+
+        if (status != 0) {
+            Log.w(SDBLIB,
+                    "'sdb start-server' failed -- run manually if necessary"); //$NON-NLS-1$
+            return false;
+        }
+
+        Log.d(SDBLIB, "'sdb start-server' succeeded"); //$NON-NLS-1$
+        return true;
+    }
+
+    /**
+     * Stops the sdb host side server.
+     * @return true if success
+     */
+    private synchronized boolean stopSdb() {
+        if (mSdbOsLocation == null) {
+            Log.e(SDB,
+                "Cannot stop sdb when SmartDevelopmentBridge is created without the location of sdb."); //$NON-NLS-1$
+            return false;
+        }
+
+        Process proc;
+        int status = -1;
+
+        try {
+            String[] command = new String[2];
+            command[0] = mSdbOsLocation;
+            command[1] = "kill-server"; //$NON-NLS-1$
+            proc = Runtime.getRuntime().exec(command);
+            status = proc.waitFor();
+        }
+        catch (IOException ioe) {
+            // we'll return false;
+        }
+        catch (InterruptedException ie) {
+            // we'll return false;
+        }
+
+        if (status != 0) {
+            Log.w(SDBLIB,
+                    "'sdb kill-server' failed -- run manually if necessary"); //$NON-NLS-1$
+            return false;
+        }
+
+        Log.d(SDBLIB, "'sdb kill-server' succeeded"); //$NON-NLS-1$
+        return true;
+    }
+
+    /**
+     * Get the stderr/stdout outputs of a process and return when the process is done.
+     * Both <b>must</b> be read or the process will block on windows.
+     * @param process The process to get the ouput from
+     * @param errorOutput The array to store the stderr output. cannot be null.
+     * @param stdOutput The array to store the stdout output. cannot be null.
+     * @param displayStdOut If true this will display stdout as well
+     * @param waitforReaders if true, this will wait for the reader threads.
+     * @return the process return code.
+     * @throws InterruptedException
+     */
+    private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
+            final ArrayList<String> stdOutput, boolean waitforReaders)
+            throws InterruptedException {
+        assert errorOutput != null;
+        assert stdOutput != null;
+        // read the lines as they come. if null is returned, it's
+        // because the process finished
+        Thread t1 = new Thread("SdbReadThread1") { //$NON-NLS-1$
+            @Override
+            public void run() {
+                // create a buffer to read the stderr output
+                InputStreamReader is = new InputStreamReader(process.getErrorStream());
+                BufferedReader errReader = new BufferedReader(is);
+
+                try {
+                    while (true) {
+                        String line = errReader.readLine();
+                        if (line != null) {
+                            Log.e(SDB, line);
+                            errorOutput.add(line);
+                        } else {
+                            break;
+                        }
+                    }
+                } catch (IOException e) {
+                    // do nothing.
+                }
+            }
+        };
+
+        Thread t2 = new Thread("SdbReadThread2") { //$NON-NLS-1$
+            @Override
+            public void run() {
+                InputStreamReader is = new InputStreamReader(process.getInputStream());
+                BufferedReader outReader = new BufferedReader(is);
+
+                try {
+                    while (true) {
+                        String line = outReader.readLine();
+                        if (line != null) {
+                            Log.d(SDB, line);
+                            stdOutput.add(line);
+                        } else {
+                            break;
+                        }
+                    }
+                } catch (IOException e) {
+                    // do nothing.
+                }
+            }
+        };
+
+        t1.start();
+        t2.start();
+
+        // it looks like on windows process#waitFor() can return
+        // before the thread have filled the arrays, so we wait for both threads and the
+        // process itself.
+        if (waitforReaders) {
+            try {
+                t1.join();
+            } catch (InterruptedException e) {
+            }
+            try {
+                t2.join();
+            } catch (InterruptedException e) {
+            }
+        }
+
+        // get the return code from the process
+        return process.waitFor();
+    }
+
+    /**
+     * Returns the singleton lock used by this class to protect any access to the listener.
+     * <p/>
+     * This includes adding/removing listeners, but also notifying listeners of new bridges,
+     * devices, and clients.
+     */
+    static Object getLock() {
+        return sLock;
+    }
+
+    /**
+     * Instantiates sSocketAddr with the address of the host's sdb process.
+     */
+    private static void initsdbSocketAddr() {
+        try {
+            int sdb_port = determineAndValidateSdbPort();
+            sHostAddr = InetAddress.getByName(SDB_HOST);
+            sSocketAddr = new InetSocketAddress(sHostAddr, sdb_port);
+        } catch (UnknownHostException e) {
+            // localhost should always be known.
+        }
+    }
+
+    /**
+     * Determines port where SDB is expected by looking at an env variable.
+     * <p/>
+     * The value for the environment variable SDB_SERVER_PORT is validated,
+     * IllegalArgumentException is thrown on illegal values.
+     * <p/>
+     * @return The port number where the host's sdb should be expected or started.
+     * @throws IllegalArgumentException if SDB_SERVER_PORT has a non-numeric value.
+     */
+    private static int determineAndValidateSdbPort() {
+        String sdb_env_var;
+        int result = SDB_PORT;
+        try {
+            sdb_env_var = System.getenv(SERVER_PORT_ENV_VAR);
+
+            if (sdb_env_var != null) {
+                sdb_env_var = sdb_env_var.trim();
+            }
+
+            if (sdb_env_var != null && sdb_env_var.length() > 0) {
+                // C tools (sdb, emulator) accept hex and octal port numbers, so need to accept
+                // them too.
+                result = Integer.decode(sdb_env_var);
+
+                if (result <= 0) {
+                    String errMsg = "env var " + SERVER_PORT_ENV_VAR //$NON-NLS-1$
+                            + ": must be >=0, got " //$NON-NLS-1$
+                            + System.getenv(SERVER_PORT_ENV_VAR);
+                    throw new IllegalArgumentException(errMsg);
+                }
+            }
+        } catch (NumberFormatException nfEx) {
+            String errMsg = "env var " + SERVER_PORT_ENV_VAR //$NON-NLS-1$
+                    + ": illegal value '" //$NON-NLS-1$
+                    + System.getenv(SERVER_PORT_ENV_VAR) + "'"; //$NON-NLS-1$
+            throw new IllegalArgumentException(errMsg);
+        } catch (SecurityException secEx) {
+            // A security manager has been installed that doesn't allow access to env vars.
+            // So an environment variable might have been set, but we can't tell.
+            // Let's log a warning and continue with SDB's default port.
+            // The issue is that sdb would be started (by the forked process having access
+            // to the env vars) on the desired port, but within this process, we can't figure out
+            // what that port is. However, a security manager not granting access to env vars
+            // but allowing to fork is a rare and interesting configuration, so the right
+            // thing seems to be to continue using the default port, as forking is likely to
+            // fail later on in the scenario of the security manager.
+            Log.w(SDBLIB,
+                    "No access to env variables allowed by current security manager. " //$NON-NLS-1$
+                    + "If you've set SDB_SERVER_PORT: it's being ignored."); //$NON-NLS-1$
+        }
+        return result;
+    }
+
+}
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/SyncService.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/SyncService.java
new file mode 100644 (file)
index 0000000..9463cfa
--- /dev/null
@@ -0,0 +1,1378 @@
+/*
+ * Copyright (C) 2007 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 org.tizen.sdblib;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+
+import org.tizen.sdblib.FileListingService.FileEntry;
+import org.tizen.sdblib.SdbHelper.SdbResponse;
+import org.tizen.sdblib.SyncService.ISyncProgressMonitor;
+
+
+/**
+ * Sync service class to push/pull to/from devices/emulators, through the debug bridge.
+ * <p/>
+ * To get a {@link SyncService} object, use {@link Device#getSyncService()}.
+ */
+public final class SyncService {
+
+    private final static byte[] ID_OKAY = { 'O', 'K', 'A', 'Y' };
+    private final static byte[] ID_FAIL = { 'F', 'A', 'I', 'L' };
+    private final static byte[] ID_RECV = { 'R', 'E', 'C', 'V' };
+    private final static byte[] ID_DATA = { 'D', 'A', 'T', 'A' };
+    private final static byte[] ID_DONE = { 'D', 'O', 'N', 'E' };
+    private final static byte[] ID_SEND = { 'S', 'E', 'N', 'D' };
+
+    private final static NullSyncProgressMonitor sNullSyncProgressMonitor =
+            new NullSyncProgressMonitor();
+
+    private final static int SYNC_DATA_MAX = 64*1024;
+    private final static int REMOTE_PATH_MAX_LENGTH = 1024;
+
+    /** Result code for transfer success. */
+    public static final int RESULT_OK = 0;
+    /** Result code for canceled transfer */
+    public static final int RESULT_CANCELED = 1;
+    /** Result code for unknown error */
+    public static final int RESULT_UNKNOWN_ERROR = 2;
+    /** Result code for network connection error */
+    public static final int RESULT_CONNECTION_ERROR = 3;
+    /** Result code for unknown remote object during a pull */
+    public static final int RESULT_NO_REMOTE_OBJECT = 4;
+    /** Result code when attempting to pull multiple files into a file */
+    public static final int RESULT_TARGET_IS_FILE = 5;
+    /** Result code when attempting to pull multiple into a directory that does not exist. */
+    public static final int RESULT_NO_DIR_TARGET = 6;
+    /** Result code for wrong encoding on the remote path. */
+    public static final int RESULT_REMOTE_PATH_ENCODING = 7;
+    /** Result code for remote path that is too long. */
+    public static final int RESULT_REMOTE_PATH_LENGTH = 8;
+    /** Result code for error while writing local file. */
+    public static final int RESULT_FILE_WRITE_ERROR = 9;
+    /** Result code for error while reading local file. */
+    public static final int RESULT_FILE_READ_ERROR = 10;
+    /** Result code for attempting to push a file that does not exist. */
+    public static final int RESULT_NO_LOCAL_FILE = 11;
+    /** Result code for attempting to push a directory. */
+    public static final int RESULT_LOCAL_IS_DIRECTORY = 12;
+    /** Result code for when the target path of a multi file push is a file. */
+    public static final int RESULT_REMOTE_IS_FILE = 13;
+    /** Result code for receiving too much data from the remove device at once */
+    public static final int RESULT_BUFFER_OVERRUN = 14;
+    /** Result code for network connection timeout */
+    public static final int RESULT_CONNECTION_TIMEOUT = 15;
+    /** Result code for file exist */
+    public static final int RESULT_FILE_EXIST = 16;
+    /** Result code for fail delete file */
+    public static final int RESULT_FAIL_DELETE_FILE = 17;
+    /** Result code for fail delete file */
+    public static final int RESULT_FAIL_CREATE_FILE = 18;
+
+    /**
+     * A file transfer result.
+     * <p/>
+     * This contains a code, and an optional string
+     */
+    public static class SyncResult {
+        private final int mCode;
+        private final String mMessage;
+        SyncResult(int code, String message) {
+            mCode = code;
+            mMessage = message;
+        }
+
+        SyncResult(int code, Exception e) {
+            this(code, e.getMessage());
+        }
+
+        SyncResult(int code) {
+            this(code, errorCodeToString(code));
+        }
+
+        public int getCode() {
+            return mCode;
+        }
+
+        public String getMessage() {
+            return mMessage;
+        }
+    }
+
+    /**
+     * Classes which implement this interface provide methods that deal
+     * with displaying transfer progress.
+     */
+    public interface ISyncProgressMonitor {
+        /**
+         * Sent when the transfer starts
+         * @param totalWork the total amount of work.
+         */
+        public void start(int totalWork);
+        /**
+         * Sent when the transfer is finished or interrupted.
+         */
+        public void stop();
+        /**
+         * Sent to query for possible cancellation.
+         * @return true if the transfer should be stopped.
+         */
+        public boolean isCanceled();
+        /**
+         * Sent when a sub task is started.
+         * @param name the name of the sub task.
+         */
+        public void startSubTask(String name);
+        /**
+         * Sent when some progress have been made.
+         * @param work the amount of work done.
+         */
+        public void advance(int work);
+    }
+
+    /**
+     * A Sync progress monitor that does nothing
+     */
+    private static class NullSyncProgressMonitor implements ISyncProgressMonitor {
+        @Override
+        public void advance(int work) {
+        }
+        @Override
+        public boolean isCanceled() {
+            return false;
+        }
+
+        @Override
+        public void start(int totalWork) {
+        }
+        @Override
+        public void startSubTask(String name) {
+        }
+        @Override
+        public void stop() {
+        }
+    }
+
+    private final InetSocketAddress mAddress;
+    private final Device mDevice;
+    private SocketChannel mChannel;
+
+    /**
+     * Buffer used to send data. Allocated when needed and reused afterward.
+     */
+    private byte[] mBuffer;
+
+    /**
+     * Creates a Sync service object.
+     * @param address The address to connect to
+     * @param device the {@link Device} that the service connects to.
+     */
+    SyncService(InetSocketAddress address, Device device) {
+        mAddress = address;
+        mDevice = device;
+    }
+
+    /**
+     * Opens the sync connection. This must be called before any calls to push[File] / pull[File].
+     * @return true if the connection opened, false if sdb refuse the connection. This can happen
+     * if the {@link Device} is invalid.
+     * @throws TimeoutException in case of timeout on the connection.
+     * @throws sdbCommandRejectedException if sdb rejects the command
+     * @throws IOException If the connection to sdb failed.
+     */
+    boolean openSync() throws TimeoutException, SdbCommandRejectedException, IOException {
+        try {
+            mChannel = SocketChannel.open(mAddress);
+            mChannel.configureBlocking(false);
+
+            // target a specific device
+            SdbHelper.setDevice(mChannel, mDevice);
+
+            byte[] request = SdbHelper.formSdbRequest("sync:"); // $NON-NLS-1$
+            SdbHelper.write(mChannel, request, -1, SdbPreferences.getTimeOut());
+
+            SdbResponse resp = SdbHelper.readSdbResponse(mChannel, false /* readDiagString */);
+
+            if (resp.okay == false) {
+                Log.w("sdb", "Got unhappy response from sdb sync req: " + resp.message);
+                mChannel.close();
+                mChannel = null;
+                return false;
+            }
+        } catch (TimeoutException e) {
+            if (mChannel != null) {
+                try {
+                    mChannel.close();
+                } catch (IOException e2) {
+                    // we want to throw the original exception, so we ignore this one.
+                }
+                mChannel = null;
+            }
+
+            throw e;
+        } catch (IOException e) {
+            if (mChannel != null) {
+                try {
+                    mChannel.close();
+                } catch (IOException e2) {
+                    // we want to throw the original exception, so we ignore this one.
+                }
+                mChannel = null;
+            }
+
+            throw e;
+        }
+
+        return true;
+    }
+
+    /**
+     * Closes the connection.
+     */
+    public void close() {
+        if (mChannel != null) {
+            try {
+                mChannel.close();
+            } catch (IOException e) {
+                // nothing to be done really...
+            }
+            mChannel = null;
+        }
+    }
+
+    /**
+     * Returns a sync progress monitor that does nothing. This allows background tasks that don't
+     * want/need to display ui, to pass a valid {@link ISyncProgressMonitor}.
+     * <p/>This object can be reused multiple times and can be used by concurrent threads.
+     */
+    public static ISyncProgressMonitor getNullProgressMonitor() {
+        return sNullSyncProgressMonitor;
+    }
+
+    /**
+     * Converts an error code into a non-localized string
+     * @param code the error code;
+     */
+    private static String errorCodeToString(int code) {
+        switch (code) {
+            case RESULT_OK:
+                return "Success.";
+            case RESULT_CANCELED:
+                return "Transfer canceled by the user.";
+            case RESULT_UNKNOWN_ERROR:
+                return "Unknown Error.";
+            case RESULT_CONNECTION_ERROR:
+                return "sdb Connection Error.";
+            case RESULT_NO_REMOTE_OBJECT:
+                return "Remote object doesn't exist!";
+            case RESULT_TARGET_IS_FILE:
+                return "Target object is a file.";
+            case RESULT_NO_DIR_TARGET:
+                return "Target directory doesn't exist.";
+            case RESULT_REMOTE_PATH_ENCODING:
+                return "Remote Path encoding is not supported.";
+            case RESULT_REMOTE_PATH_LENGTH:
+                return "Remove path is too long.";
+            case RESULT_FILE_WRITE_ERROR:
+                return "Writing local file failed!";
+            case RESULT_FILE_READ_ERROR:
+                return "Reading local file failed!";
+            case RESULT_NO_LOCAL_FILE:
+                return "Local file doesn't exist.";
+            case RESULT_LOCAL_IS_DIRECTORY:
+                return "Local path is a directory.";
+            case RESULT_REMOTE_IS_FILE:
+                return "Remote path is a file.";
+            case RESULT_BUFFER_OVERRUN:
+                return "Receiving too much data.";
+            case RESULT_CONNECTION_TIMEOUT:
+                return "timeout";
+            case RESULT_FILE_EXIST:
+                return "File exists.";
+            case RESULT_FAIL_DELETE_FILE:
+                return "Fail delete file.";
+            case RESULT_FAIL_CREATE_FILE:
+                return "Fail create file.";
+        }
+
+        throw new RuntimeException();
+    }
+
+    /**
+     * Pulls file(s) or folder(s).
+     * @param entries the remote item(s) to pull
+     * @param localPath The local destination. If the entries count is > 1 or
+     *      if the unique entry is a folder, this should be a folder.
+     * @param monitor The progress monitor. Cannot be null.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     *
+     * @see FileListingService.FileEntry
+     * @see #getNullProgressMonitor()
+     */
+    public SyncResult pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor) {
+
+        // first we check the destination is a directory and exists
+        File f = new File(localPath);
+        if (f.exists() == false) {
+            return new SyncResult(RESULT_NO_DIR_TARGET);
+        }
+        if (!f.isDirectory()) {
+            return new SyncResult(RESULT_TARGET_IS_FILE);
+        }
+
+        // get a FileListingService object
+        FileListingService fls = new FileListingService(mDevice);
+
+        // compute the number of file to move
+        int total = getTotalRemoteFileSize(entries, fls);
+
+        // start the monitor
+        monitor.start(total);
+
+        SyncResult result = doPull(entries, localPath, fls, monitor);
+
+        monitor.stop();
+
+        return result;
+    }
+    
+    /**
+     * Pulls file(s) or folder(s).
+     * @param entries the remote item(s) to pull
+     * @param localPath The local destination. If the entries count is > 1 or
+     *      if the unique entry is a folder, this should be a folder.
+     * @param monitor The progress monitor. Cannot be null.
+     * @param time interval while pulling the files
+     * @return a {@link SyncResult} object with a code and an optional message.
+     *
+     * @see FileListingService.FileEntry
+     * @see #getNullProgressMonitor()
+     */
+    public SyncResult pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor, int timeOut) {
+
+        // first we check the destination is a directory and exists
+        File f = new File(localPath);
+        if (f.exists() == false) {
+            return new SyncResult(RESULT_NO_DIR_TARGET);
+        }
+        if (!f.isDirectory()) {
+            return new SyncResult(RESULT_TARGET_IS_FILE);
+        }
+
+        // get a FileListingService object
+        FileListingService fls = new FileListingService(mDevice);
+
+        // compute the number of file to move
+        long total = getTotalRemoteFileSizeLong(entries, fls);
+        TransferInfo transferInfo = new TransferInfo();
+
+        // start the monitor
+        monitor.start(transferInfo.downSizing(total));
+
+        SyncResult result = doPull(entries, localPath, fls, monitor, transferInfo, timeOut);
+
+        monitor.stop();
+
+        return result;
+    }
+
+    /**
+     * Pulls a single file.
+     * @param remote the remote file
+     * @param localFilename The local destination.
+     * @param monitor The progress monitor. Cannot be null.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     *
+     * @see FileListingService.FileEntry
+     * @see #getNullProgressMonitor()
+     */
+    public SyncResult pullFile(FileEntry remote, String localFilename,
+            ISyncProgressMonitor monitor) {
+        int total = remote.getSizeValue();
+        monitor.start(total);
+
+        SyncResult result = doPullFile(remote.getFullEscapedPath(), localFilename, monitor);
+
+        monitor.stop();
+        return result;
+    }
+
+    /**
+     * Pulls a single file.
+     * <p/>Because this method just deals with a String for the remote file instead of a
+     * {@link FileEntry}, the size of the file being pulled is unknown and the
+     * {@link ISyncProgressMonitor} will not properly show the progress
+     * @param remoteFilepath the full path to the remote file
+     * @param localFilename The local destination.
+     * @param monitor The progress monitor. Cannot be null.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     *
+     * @see #getNullProgressMonitor()
+     */
+    public SyncResult pullFile(String remoteFilepath, String localFilename,
+            ISyncProgressMonitor monitor) {
+        monitor.start(0);
+        //TODO: use the {@link FileListingService} to get the file size.
+
+        SyncResult result = doPullFile(remoteFilepath, localFilename, monitor);
+
+        monitor.stop();
+        return result;
+    }
+
+    /**
+     * Pulls a single file.
+     * <p/>Because this method just deals with a String for the remote file instead of a
+     * {@link FileEntry}, the size of the file being pulled is unknown and the
+     * {@link ISyncProgressMonitor} will not properly show the progress
+     * @param remoteFilepath the full path to the remote file
+     * @param localFilename The local destination.
+     * @param forceOverwrite this decides overwrite, if local file is already exist.
+     * @param monitor The progress monitor. Cannot be null.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     *
+     * @see #getNullProgressMonitor()
+     */
+    public SyncResult pullFile(String remoteFile, String localFile, boolean forceOverwrite,
+            ISyncProgressMonitor monitor) {
+        File f = new File(localFile);
+        if (f.exists() == true) {
+            if (f.isDirectory()) {
+                return new SyncResult(RESULT_LOCAL_IS_DIRECTORY);
+            }
+
+            if (forceOverwrite == false) {
+                return new SyncResult(RESULT_FILE_EXIST);
+            } else {
+                boolean result = f.delete();
+                if (result == false) {
+                    return new SyncResult(RESULT_FAIL_DELETE_FILE);
+                }
+            }
+        }
+
+        try {
+            String filePath = f.getAbsolutePath();
+            String dirPath = filePath.substring(0, filePath.lastIndexOf(File.separator));
+            File d = new File(dirPath);
+            if (d.exists() == false) {
+                d.mkdirs();
+            }
+            boolean result = f.createNewFile();
+            if (result == false) {
+                return new SyncResult(RESULT_FAIL_CREATE_FILE);
+            }
+        } catch (IOException e) {
+            return new SyncResult(RESULT_UNKNOWN_ERROR, e);
+        }
+
+        monitor.start(0);
+        //TODO: use the {@link FileListingService} to get the file size.
+
+        SyncResult result = doPullFile(remoteFile, localFile, monitor);
+
+        monitor.stop();
+        return result;
+    }
+
+
+    /**
+     * Push several files.
+     * @param local An array of loca files to push
+     * @param remote the remote {@link FileEntry} representing a directory.
+     * @param monitor The progress monitor. Cannot be null.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    public SyncResult push(String[] local, FileEntry remote, ISyncProgressMonitor monitor) {
+        if (!remote.isDirectory()) {
+            return new SyncResult(RESULT_REMOTE_IS_FILE);
+        }
+
+        // make a list of File from the list of String
+        ArrayList<File> files = new ArrayList<File>();
+        for (String path : local) {
+            files.add(new File(path));
+        }
+
+        // get the total count of the bytes to transfer
+        File[] fileArray = files.toArray(new File[files.size()]);
+        int total = getTotalLocalFileSize(fileArray);
+
+        monitor.start(total);
+
+        SyncResult result = doPush(fileArray, remote.getFullPath(), monitor);
+
+        monitor.stop();
+
+        return result;
+    }
+
+    /**
+     * Push several files.
+     * @param local An array of loca files to push
+     * @param remote the remote {@link FileEntry} representing a directory.
+     * @param monitor The progress monitor. Cannot be null.
+     * @param time out value while push the files
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    public SyncResult push(String[] local, FileEntry remote, ISyncProgressMonitor monitor, int timeOut) {
+        if (!remote.isDirectory()) {
+            return new SyncResult(RESULT_REMOTE_IS_FILE);
+        }
+
+        // make a list of File from the list of String
+        ArrayList<File> files = new ArrayList<File>();
+        for (String path : local) {
+            files.add(new File(path));
+        }
+
+        // get the total count of the bytes to transfer
+        File[] fileArray = files.toArray(new File[files.size()]);
+        long total = getTotalLocalFileSizeLong(fileArray);
+        TransferInfo sizeInfo = new TransferInfo();
+        
+        monitor.start(sizeInfo.downSizing(total));
+
+        SyncResult result = doPush(fileArray, remote.getFullPath(), monitor, sizeInfo, timeOut);
+
+        monitor.stop();
+
+        return result;
+    }
+
+    /**
+     * Push a single file.
+     * @param local the local filepath.
+     * @param remote The remote filepath.
+     * @param monitor The progress monitor. Cannot be null.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    public SyncResult pushFile(String local, String remote, ISyncProgressMonitor monitor) {
+        File f = new File(local);
+        if (f.exists() == false) {
+            return new SyncResult(RESULT_NO_LOCAL_FILE);
+        }
+
+        if (f.isDirectory()) {
+            return new SyncResult(RESULT_LOCAL_IS_DIRECTORY);
+        }
+
+        monitor.start((int)f.length());
+
+        SyncResult result = doPushFile(local, remote, monitor);
+
+        monitor.stop();
+
+        return result;
+    }
+
+    /**
+     * compute the recursive file size of all the files in the list. Folder
+     * have a weight of 1.
+     * @param entries
+     * @param fls
+     * @return
+     */
+    private int getTotalRemoteFileSize(FileEntry[] entries, FileListingService fls) {
+        int count = 0;
+        for (FileEntry e : entries) {
+            int type = e.getType();
+            if (type == FileListingService.TYPE_DIRECTORY || type == FileListingService.TYPE_ROOT_DEVICE
+                    || type == FileListingService.TYPE_ROOT_EMULATOR) {
+                // get the children
+                FileEntry[] children = fls.getChildren(e, false, null);
+                count += getTotalRemoteFileSize(children, fls) + 1;
+            } else if (type == FileListingService.TYPE_FILE) {
+                count += e.getSizeValue();
+            }
+        }
+
+        return count;
+    }
+    
+    /**
+     * compute the recursive file size of all the files in the list. Folder
+     * have a weight of 1.
+     * @param entries
+     * @param fls
+     * @return long typed file size
+     */
+    private long getTotalRemoteFileSizeLong(FileEntry[] entries, FileListingService fls) {
+        long count = 0;
+        for (FileEntry e : entries) {
+            int type = e.getType();
+            if (type == FileListingService.TYPE_DIRECTORY || type == FileListingService.TYPE_ROOT_DEVICE
+                    || type == FileListingService.TYPE_ROOT_EMULATOR) {
+                // get the children
+                FileEntry[] children = fls.getChildren(e, false, null);
+                count += getTotalRemoteFileSizeLong(children, fls) + 1;
+            } else if (type == FileListingService.TYPE_FILE) {
+                count += e.getSizeValue();
+            }
+        }
+
+        return count;
+    }
+
+    /**
+     * compute the recursive file size of all the files in the list. Folder
+     * have a weight of 1.
+     * This does not check for circular links.
+     * @param files
+     * @return
+     */
+    private int getTotalLocalFileSize(File[] files) {
+        int count = 0;
+
+        for (File f : files) {
+            if (f.exists()) {
+                if (f.isDirectory()) {
+                    return getTotalLocalFileSize(f.listFiles()) + 1;
+                } else if (f.isFile()) {
+                    count += f.length();
+                }
+            }
+        }
+
+        return count;
+    }
+    
+    /**
+     * compute the recursive file size of all the files in the list. Folder
+     * have a weight of 1.
+     * This does not check for circular links.
+     * @param files
+     * @return long typed file size
+     */
+    private long getTotalLocalFileSizeLong(File[] files) {
+        long count = 0;
+
+        for (File f : files) {
+            if (f.exists()) {
+                if (f.isDirectory()) {
+                    return getTotalLocalFileSizeLong(f.listFiles()) + 1;
+                } else if (f.isFile()) {
+                    count += f.length();
+                }
+            }
+        }
+
+        return count;
+    }
+
+    /**
+     * Pulls multiple files/folders recursively.
+     * @param entries The list of entry to pull
+     * @param localPath the localpath to a directory
+     * @param fileListingService a FileListingService object to browse through remote directories.
+     * @param monitor the progress monitor. Must be started already.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPull(FileEntry[] entries, String localPath,
+            FileListingService fileListingService,
+            ISyncProgressMonitor monitor) {
+
+        for (FileEntry e : entries) {
+            // check if we're cancelled
+            if (monitor.isCanceled() == true) {
+                return new SyncResult(RESULT_CANCELED);
+            }
+
+            // get type (we only pull directory and files for now)
+            monitor.startSubTask(e.getFullPath());
+            String dest = localPath + File.separator + e.getName();
+            SyncResult result;
+            if (e.isDirectory()) {
+
+                // make the directory
+                File d = new File(dest);
+                d.mkdir();
+
+                // then recursively call the content. Since we did a ls command
+                // to get the number of files, we can use the cache
+                FileEntry[] children = fileListingService.getChildren(e, true, null);
+                result = doPull(children, dest, fileListingService, monitor);
+            } else{
+                result = doPullFile(e.getFullPath(), dest, monitor);
+            }
+
+            if (result.mCode != RESULT_OK) {
+                return result;
+            }else if(e.isDirectory())
+            {
+                monitor.advance(1);
+            }
+        }
+
+        return new SyncResult(RESULT_OK);
+    }
+    
+    /**
+     * Pulls multiple files/folders recursively.
+     * @param entries The list of entry to pull
+     * @param localPath the localpath to a directory
+     * @param fileListingService a FileListingService object to browse through remote directories.
+     * @param monitor the progress monitor. Must be started already.
+     * @param size information
+     * @param time interval while pulling the files
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPull(FileEntry[] entries, String localPath,
+            FileListingService fileListingService,
+            ISyncProgressMonitor monitor,
+            TransferInfo transferInfo,
+            int timeOut) {
+
+        for (FileEntry e : entries) {
+            // check if we're cancelled
+            if (monitor.isCanceled() == true) {
+                return new SyncResult(RESULT_CANCELED);
+            }
+
+            // get type (we only pull directory and files for now)
+//            monitor.startSubTask(e.getFullPath());
+            transferInfo.setDest(e.getFullPath());
+            String dest = localPath + File.separator + e.getName();
+            SyncResult result;
+            if (e.isDirectory()) {
+
+                // make the directory
+                File d = new File(dest);
+                d.mkdir();
+
+                // then recursively call the content. Since we did a ls command
+                // to get the number of files, we can use the cache
+                FileEntry[] children = fileListingService.getChildren(e, true, null);
+                result = doPull(children, dest, fileListingService, monitor, transferInfo, timeOut);
+            } else{
+                result = doPullFile(e.getFullPath(), dest, monitor, transferInfo, timeOut);
+            }
+
+            if (result.mCode != RESULT_OK) {
+                return result;
+            }else if(e.isDirectory())
+            {
+                monitor.advance(1);
+            }
+        }
+
+        return new SyncResult(RESULT_OK);
+    }
+
+    /**
+     * Pulls a remote file
+     * @param remotePath the remote file (length max is 1024)
+     * @param localPath the local destination
+     * @param monitor the monitor. The monitor must be started already.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPullFile(String remotePath, String localPath,
+            ISyncProgressMonitor monitor) {
+        return doPullFile(remotePath, localPath, monitor, null, SdbPreferences.getTimeOut());
+    }
+    
+    /**
+     * Pulls a remote file
+     * @param remotePath the remote file (length max is 1024)
+     * @param localPath the local destination
+     * @param monitor the monitor. The monitor must be started already.
+     * @param size information
+     * @param time out value while pulling the file
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPullFile(String remotePath, String localPath,
+            ISyncProgressMonitor monitor,
+            TransferInfo transferInfo,
+            int timeOut) {
+
+        byte[] msg = null;
+        byte[] pullResult = new byte[8];
+        if(transferInfo != null)
+        {
+            transferInfo.setSrc(localPath);
+        }
+
+        try {
+            byte[] remotePathContent = remotePath.getBytes(SdbHelper.UTF_ENCODING);
+
+            if (remotePathContent.length > REMOTE_PATH_MAX_LENGTH) {
+                return new SyncResult(RESULT_REMOTE_PATH_LENGTH);
+            }
+
+            // create the full request message
+            msg = createFileReq(ID_RECV, remotePathContent);
+
+            // and send it.
+            SdbHelper.write(mChannel, msg, -1, timeOut);
+
+            // read the result, in a byte array containing 2 ints
+            // (id, size)
+            SdbHelper.read(mChannel, pullResult, -1, timeOut);
+
+            // check we have the proper data back
+            if (checkResult(pullResult, ID_DATA) == false &&
+                    checkResult(pullResult, ID_DONE) == false) {
+                return new SyncResult(RESULT_CONNECTION_ERROR);
+            }
+        } catch (UnsupportedEncodingException e) {
+            return new SyncResult(RESULT_REMOTE_PATH_ENCODING, e);
+        } catch (TimeoutException e) {
+            return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
+        } catch (IOException e) {
+            return new SyncResult(RESULT_CONNECTION_ERROR, e);
+        }
+
+        // access the destination file
+        File f = new File(localPath);
+
+        // create the stream to write in the file. We use a new try/catch block to differentiate
+        // between file and network io exceptions.
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(f);
+        } catch (FileNotFoundException e) {
+            return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
+        }
+
+        // the buffer to read the data
+        byte[] data = new byte[SYNC_DATA_MAX];
+
+        // loop to get data until we're done.
+        while (true) {
+            // check if we're cancelled
+            if (monitor.isCanceled() == true) {
+                return new SyncResult(RESULT_CANCELED);
+            }
+
+            // if we're done, we stop the loop
+            if (checkResult(pullResult, ID_DONE)) {
+                break;
+            }
+            if (checkResult(pullResult, ID_DATA) == false) {
+                // hmm there's an error
+                return new SyncResult(RESULT_CONNECTION_ERROR);
+            }
+            int length = ArrayHelper.swap32bitFromArray(pullResult, 4);
+            if (length > SYNC_DATA_MAX) {
+                // buffer overrun!
+                // error and exit
+                return new SyncResult(RESULT_BUFFER_OVERRUN);
+            }
+
+            try {
+                // now read the length we received
+                SdbHelper.read(mChannel, data, length, timeOut);
+
+                // get the header for the next packet.
+                SdbHelper.read(mChannel, pullResult, -1, timeOut);
+            } catch (TimeoutException e) {
+                return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
+            } catch (IOException e) {
+                return new SyncResult(RESULT_CONNECTION_ERROR, e);
+            }
+
+            // write the content in the file
+            try {
+                fos.write(data, 0, length);
+            } catch (IOException e) {
+                return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
+            }
+            
+            if(transferInfo != null)
+            {
+                transferInfo.monitorAdvance(length, monitor);
+            }
+            else
+            {
+                monitor.advance(length);
+            }
+        }
+
+        try {
+            fos.flush();
+            fos.close();
+        } catch (IOException e) {
+            return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
+        }
+        return new SyncResult(RESULT_OK);
+    }
+
+
+    /**
+     * Push multiple files
+     * @param fileArray
+     * @param remotePath
+     * @param monitor
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor) {
+        for (File f : fileArray) {
+            // check if we're canceled
+            if (monitor.isCanceled() == true) {
+                return new SyncResult(RESULT_CANCELED);
+            }
+            if (f.exists()) {
+                if (f.isDirectory()) {
+                    // append the name of the directory to the remote path
+                    String dest = remotePath + "/" + f.getName(); // $NON-NLS-1S
+                    monitor.startSubTask(dest);
+                    SyncResult result = doPush(f.listFiles(), dest, monitor);
+
+                    if (result.mCode != RESULT_OK) {
+                        return result;
+                    }
+
+                    monitor.advance(1);
+                } else if (f.isFile()) {
+                    // append the name of the file to the remote path
+                    String remoteFile = remotePath + "/" + f.getName(); // $NON-NLS-1S
+                    monitor.startSubTask(remoteFile);
+                    SyncResult result = doPushFile(f.getAbsolutePath(), remoteFile, monitor);
+                    if (result.mCode != RESULT_OK) {
+                        return result;
+                    }
+                }
+            }
+        }
+
+        return new SyncResult(RESULT_OK);
+    }
+
+    /**
+     * Push multiple files
+     * @param fileArray
+     * @param remotePath
+     * @param monitor
+     * @param size information
+     * @param time out value while pushing the files
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor, TransferInfo transferInfo, int timeOut) {
+        for (File f : fileArray) {
+            // check if we're canceled
+            if (monitor.isCanceled() == true) {
+                return new SyncResult(RESULT_CANCELED);
+            }
+            if (f.exists()) {
+                String remoteFile;
+                if("/".equals(remotePath))
+                {
+                    remoteFile = remotePath + f.getName();
+                }
+                else
+                {
+                    remoteFile = remotePath + "/" + f.getName();
+                }
+                transferInfo.setDest(remoteFile);
+                
+                if (f.isDirectory()) {
+                    // append the name of the directory to the remote path
+//                    String dest = remotePath + "/" + f.getName(); // $NON-NLS-1S
+//                    monitor.startSubTask(dest);
+                    SyncResult result = doPush(f.listFiles(), remoteFile, monitor, transferInfo, timeOut);
+
+                    if (result.mCode != RESULT_OK) {
+                        return result;
+                    }
+
+                    monitor.advance(1);
+                } else if (f.isFile()) {
+                    // append the name of the file to the remote path
+//                    String remoteFile = remotePath + "/" + f.getName(); // $NON-NLS-1S
+//                    monitor.startSubTask(remoteFile);
+//                    transferInfo.fileName = remoteFile;
+                    SyncResult result = doPushFile(f.getAbsolutePath(), remoteFile, monitor, transferInfo, timeOut);
+                    if (result.mCode != RESULT_OK) {
+                        return result;
+                    }
+                }
+            }
+        }
+
+        return new SyncResult(RESULT_OK);
+    }
+
+    /**
+     * Push a single file
+     * @param localPath the local file to push
+     * @param remotePath the remote file (length max is 1024)
+     * @param monitor the monitor. The monitor must be started already.
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPushFile(String localPath, String remotePath,
+            ISyncProgressMonitor monitor) {
+        return doPushFile( localPath, remotePath, monitor, null, SdbPreferences.getTimeOut());
+    }
+    
+    /**
+     * Push a single file with permission 755
+     * @param localPath the local file to push
+     * @param remotePath the remote file (length max is 1024)
+     * @param monitor the monitor. The monitor must be started already.
+     * @param size information
+     * @param time out value while pushing the file
+     * @return a {@link SyncResult} object with a code and an optional message.
+     */
+    private SyncResult doPushFile(String localPath, String remotePath,
+            ISyncProgressMonitor monitor,
+            TransferInfo transferInfo,
+            int timeOut) {
+        remotePath = remotePath.replace("\\ ", " ");
+        if(transferInfo != null)
+        {
+            transferInfo.setSrc(localPath);
+        }
+
+        FileInputStream fis = null;
+        byte[] msg;
+
+        try {
+            byte[] remotePathContent = remotePath.getBytes(SdbHelper.UTF_ENCODING);
+
+            if (remotePathContent.length > REMOTE_PATH_MAX_LENGTH) {
+                return new SyncResult(RESULT_REMOTE_PATH_LENGTH);
+            }
+
+            File f = new File(localPath);
+
+            // this shouldn't happen but still...
+            if (f.exists() == false) {
+                return new SyncResult(RESULT_NO_LOCAL_FILE);
+            }
+
+            // create the stream to read the file
+            fis = new FileInputStream(f);
+
+            // create the header for the action
+            msg = createSendFileReq(ID_SEND, remotePathContent, 0755);
+        } catch (UnsupportedEncodingException e) {
+            return new SyncResult(RESULT_REMOTE_PATH_ENCODING, e);
+        } catch (FileNotFoundException e) {
+            return new SyncResult(RESULT_FILE_READ_ERROR, e);
+        }
+
+        // and send it. We use a custom try/catch block to make the difference between
+        // file and network IO exceptions.
+        try {
+            SdbHelper.write(mChannel, msg, -1, timeOut);
+        } catch (TimeoutException e) {
+            return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
+        } catch (IOException e) {
+            return new SyncResult(RESULT_CONNECTION_ERROR, e);
+        }
+
+        // create the buffer used to read.
+        // we read max SYNC_DATA_MAX, but we need 2 4 bytes at the beginning.
+        if (mBuffer == null) {
+            mBuffer = new byte[SYNC_DATA_MAX + 8];
+        }
+        System.arraycopy(ID_DATA, 0, mBuffer, 0, ID_DATA.length);
+
+        // look while there is something to read
+        while (true) {
+            // check if we're canceled
+            if (monitor.isCanceled() == true) {
+                return new SyncResult(RESULT_CANCELED);
+            }
+
+            // read up to SYNC_DATA_MAX
+            int readCount = 0;
+            try {
+                readCount = fis.read(mBuffer, 8, SYNC_DATA_MAX);
+            } catch (IOException e) {
+                return new SyncResult(RESULT_FILE_READ_ERROR, e);
+            }
+
+            if (readCount == -1) {
+                // we reached the end of the file
+                break;
+            }
+
+            // now send the data to the device
+            // first write the amount read
+            ArrayHelper.swap32bitsToArray(readCount, mBuffer, 4);
+
+            // now write it
+            try {
+                SdbHelper.write(mChannel, mBuffer, readCount+8, timeOut);
+            } catch (TimeoutException e) {
+                return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
+            } catch (IOException e) {
+                return new SyncResult(RESULT_CONNECTION_ERROR, e);
+            }
+
+            if(transferInfo != null)
+            {
+                transferInfo.monitorAdvance(readCount, monitor);
+            }
+            else
+            {
+                monitor.advance(readCount);
+            }
+        }
+        // close the local file
+        try {
+            fis.close();
+        } catch (IOException e) {
+            return new SyncResult(RESULT_FILE_READ_ERROR, e);
+        }
+
+        try {
+            // create the DONE message
+            long time = System.currentTimeMillis() / 1000;
+            msg = createReq(ID_DONE, (int)time);
+
+            // and send it.
+            SdbHelper.write(mChannel, msg, -1, timeOut);
+
+            // read the result, in a byte array containing 2 ints
+            // (id, size)
+            byte[] result = new byte[8];
+            SdbHelper.read(mChannel, result, -1 /* full length */, timeOut);
+
+            if (checkResult(result, ID_OKAY) == false) {
+                if (checkResult(result, ID_FAIL)) {
+                    // read some error message...
+                    int len = ArrayHelper.swap32bitFromArray(result, 4);
+
+                    SdbHelper.read(mChannel, mBuffer, len, timeOut);
+
+                    // output the result?
+                    String message = new String(mBuffer, 0, len);
+                    Log.e("sdb", "transfer error: " + message);
+                    return new SyncResult(RESULT_UNKNOWN_ERROR, message);
+                }
+
+                return new SyncResult(RESULT_UNKNOWN_ERROR);
+            }
+        } catch (TimeoutException e) {
+            return new SyncResult(RESULT_CONNECTION_TIMEOUT, e);
+        } catch (IOException e) {
+            return new SyncResult(RESULT_CONNECTION_ERROR, e);
+        }
+
+        return new SyncResult(RESULT_OK);
+    }
+
+    /**
+     * Create a command with a code and an int values
+     * @param command
+     * @param value
+     * @return
+     */
+    private static byte[] createReq(byte[] command, int value) {
+        byte[] array = new byte[8];
+
+        System.arraycopy(command, 0, array, 0, 4);
+        ArrayHelper.swap32bitsToArray(value, array, 4);
+
+        return array;
+    }
+
+    /**
+     * Creates the data array for a file request. This creates an array with a 4 byte command + the
+     * remote file name.
+     * @param command the 4 byte command (ID_STAT, ID_RECV, ...).
+     * @param path The path, as a byte array, of the remote file on which to
+     *      execute the command.
+     * @return the byte[] to send to the device through sdb
+     */
+    private static byte[] createFileReq(byte[] command, byte[] path) {
+        byte[] array = new byte[8 + path.length];
+
+        System.arraycopy(command, 0, array, 0, 4);
+        ArrayHelper.swap32bitsToArray(path.length, array, 4);
+        System.arraycopy(path, 0, array, 8, path.length);
+
+        return array;
+    }
+
+    private static byte[] createSendFileReq(byte[] command, byte[] path, int mode) {
+        // make the mode into a string
+        String modeStr = "," + (mode & 0777); // $NON-NLS-1S
+        byte[] modeContent = null;
+        try {
+            modeContent = modeStr.getBytes(SdbHelper.DEFAULT_ENCODING);
+        } catch (UnsupportedEncodingException e) {
+            return null;
+        }
+
+        byte[] array = new byte[8 + path.length + modeContent.length];
+
+        System.arraycopy(command, 0, array, 0, 4);
+        ArrayHelper.swap32bitsToArray(path.length + modeContent.length, array, 4);
+        System.arraycopy(path, 0, array, 8, path.length);
+        System.arraycopy(modeContent, 0, array, 8 + path.length, modeContent.length);
+
+        return array;
+
+
+    }
+
+    /**
+     * Checks the result array starts with the provided code
+     * @param result The result array to check
+     * @param code The 4 byte code.
+     * @return true if the code matches.
+     */
+    private static boolean checkResult(byte[] result, byte[] code) {
+        if (result[0] != code[0] ||
+                result[1] != code[1] ||
+                result[2] != code[2] ||
+                result[3] != code[3]) {
+            return false;
+        }
+
+        return true;
+
+    }
+
+}
+
+//to process size is bigger than integer maximum value
+class TransferInfo{
+
+    private String from = null;
+    private String to = null;
+    //to show total file size and unit
+    private String total_sizeAndunit = null;
+    private int rate = 0;
+    private int remains = 0;
+    private long total_size = 0;
+    private long transfered_size = 0;
+    
+    TransferInfo(){}
+
+    /**
+     * Getting size down until size is smaller than integer maximum size
+     * @param origin
+     * @return down size
+     */
+    public int downSizing(long origin)
+    {
+        total_size = origin;
+        int times = 0;
+        while(origin > Integer.MAX_VALUE)
+        {
+            origin /= 10;
+            times++;
+        }
+
+        rate = (int) Math.pow(10, times);
+        total_sizeAndunit = getSizeAndUnit(total_size);
+        return (int)origin;
+    }
+
+    public void setDest(String dest)
+    {
+        to = dest;
+    }
+    
+    public void setSrc(String src)
+    {
+        from = src;
+    }
+
+    public void monitorAdvance(int count, ISyncProgressMonitor monitor)
+    {
+        transfered_size += count;
+        
+        int size = remains + count ;
+
+        if(size > rate)
+        {
+            monitor.advance(size/rate);
+            remains = size%rate;
+        }
+        else
+        {
+            remains = size;
+        }
+        
+        if(from != null)
+        {
+            monitor.startSubTask(String.format("From\t\"%s\"\tto\t\"%s\"\n( %s / %s ) ", from, to, getSizeAndUnit(transfered_size), total_sizeAndunit));
+        }
+    }
+
+    public String getSizeAndUnit(long size)
+    {
+        int index = 0;
+        int i = 0;
+        int short_size = 0;
+        double size_for_GB = 0;
+
+        while(i < 3)
+        {
+            if(size >= 1000)
+            {
+                //case of b, kb, mb : xxx b/kb/mb
+                //case of gb : xxx.xx gb
+                if(index == 2)
+                {
+                    size_for_GB = (double)size/1000.00;
+                }else
+                {
+                    size/=1000;
+                }
+
+                if(size > 0)
+                {
+                    index++;
+                }
+                else
+                {
+                    break;
+                }
+                i++;
+            }
+            else
+            {
+                break;
+            }
+        }
+
+        short_size = (int)size % 1000;
+
+        String unit = null;
+
+        switch(index)
+        {
+            case 0:
+                unit = "B";
+                break;
+            case 1:
+                unit = "KB";
+                break;
+            case 2:
+                unit = "MB";
+                break;
+            case 3:
+                unit = "GB";
+                return String.format("%.2f %s", size_for_GB, unit);
+        }
+
+        return String.format("%d %s", short_size, unit);
+    }
+}
\ No newline at end of file
diff --git a/org.tizen.common.sdblib/src/org/tizen/sdblib/TimeoutException.java b/org.tizen.common.sdblib/src/org/tizen/sdblib/TimeoutException.java
new file mode 100644 (file)
index 0000000..b0ae737
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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 org.tizen.sdblib;
+
+import java.io.IOException;
+
+/**
+ * Exception thrown when a connection to sdb failed with a timeout.
+ *
+ */
+public class TimeoutException extends IOException {
+    private static final long serialVersionUID = 1L;
+}
diff --git a/org.tizen.common.sdblib/test/lib/asm-4.0.jar b/org.tizen.common.sdblib/test/lib/asm-4.0.jar
new file mode 100644 (file)
index 0000000..6d63075
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/asm-4.0.jar differ
diff --git a/org.tizen.common.sdblib/test/lib/asm-tree-4.0.jar b/org.tizen.common.sdblib/test/lib/asm-tree-4.0.jar
new file mode 100644 (file)
index 0000000..aa99d3a
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/asm-tree-4.0.jar differ
diff --git a/org.tizen.common.sdblib/test/lib/javassist-3.16.1-GA.jar b/org.tizen.common.sdblib/test/lib/javassist-3.16.1-GA.jar
new file mode 100644 (file)
index 0000000..c29da0f
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/javassist-3.16.1-GA.jar differ
diff --git a/org.tizen.common.sdblib/test/lib/junit-4.10-src.jar b/org.tizen.common.sdblib/test/lib/junit-4.10-src.jar
new file mode 100644 (file)
index 0000000..1449d28
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/junit-4.10-src.jar differ
diff --git a/org.tizen.common.sdblib/test/lib/junit-4.10.jar b/org.tizen.common.sdblib/test/lib/junit-4.10.jar
new file mode 100644 (file)
index 0000000..bf5c0b9
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/junit-4.10.jar differ
diff --git a/org.tizen.common.sdblib/test/lib/mockito-all-1.9.0.jar b/org.tizen.common.sdblib/test/lib/mockito-all-1.9.0.jar
new file mode 100644 (file)
index 0000000..273fd50
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/mockito-all-1.9.0.jar differ
diff --git a/org.tizen.common.sdblib/test/lib/powermock-mockito-1.4.12-full.jar b/org.tizen.common.sdblib/test/lib/powermock-mockito-1.4.12-full.jar
new file mode 100644 (file)
index 0000000..b70358d
Binary files /dev/null and b/org.tizen.common.sdblib/test/lib/powermock-mockito-1.4.12-full.jar differ
diff --git a/org.tizen.common.sdblib/test/src/org/tizen/sdblib/TransferInfoTest.java b/org.tizen.common.sdblib/test/src/org/tizen/sdblib/TransferInfoTest.java
new file mode 100644 (file)
index 0000000..5182357
--- /dev/null
@@ -0,0 +1,30 @@
+package org.tizen.sdblib;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class TransferInfoTest
+{
+    @Test
+    public void test_getSizeAndUnit()
+    {
+        final Object[][] TEST_CASES = new Object[][] {
+                new Object[] { 100L, new String("100 B") },
+                new Object[] { 1000L, new String("1 KB") },
+                new Object[] { 10000L, new String("10 KB") },
+                new Object[] { 100000L, new String("100 KB") },
+                new Object[] { 1000000L, new String("1 MB") },
+                new Object[] { 10000000L, new String("10 MB") },
+                new Object[] { 100000000L, new String("100 MB") },
+                new Object[] { 1000000000L, new String("1.00 GB") },
+            };
+            TransferInfo info = new TransferInfo();
+            for ( final Object[] TEST_CASE : TEST_CASES )
+            {
+                final long input = (Long) TEST_CASE[0];
+                final String expected = (String) TEST_CASE[1];
+                assertEquals( expected, info.getSizeAndUnit(input));
+            }
+    }
+}
index 3657b0f..13f5c5b 100755 (executable)
@@ -1,25 +1,24 @@
-<?xml version="1.0" encoding="UTF-8"?>\r
-<classpath>\r
-       <classpathentry exported="true" kind="lib" path="lib/commons-logging-1.1.1.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/commons-collections-3.2.1.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/commons-pool-1.6.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/commons-lang3-3.1.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/commons-io-2.4.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/freemarker.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/log4j-1.2.17.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/sdblib.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/slf4j-api-1.6.4.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="lib/slf4j-log4j12-1.6.4.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="test/lib/junit-4.10.jar" sourcepath="test/lib/junit-4.10-src.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="test/lib/mockito-all-1.9.0.jar"/>\r
-       <classpathentry kind="lib" path="test/lib/asm-4.0.jar"/>\r
-       <classpathentry kind="lib" path="test/lib/asm-tree-4.0.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="test/lib/powermock-mockito-1.4.12-full.jar"/>\r
-       <classpathentry exported="true" kind="lib" path="test/lib/javassist-3.16.1-GA.jar"/>\r
-       <classpathentry kind="lib" path="lib/ant.jar"/>\r
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>\r
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
-       <classpathentry kind="src" path="src"/>\r
-       <classpathentry kind="src" path="test/src"/>\r
-       <classpathentry kind="output" path="bin"/>\r
-</classpath>\r
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry exported="true" kind="lib" path="lib/commons-logging-1.1.1.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/commons-collections-3.2.1.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/commons-pool-1.6.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/commons-lang3-3.1.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/commons-io-2.4.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/freemarker.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/log4j-1.2.17.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/slf4j-api-1.6.4.jar"/>
+       <classpathentry exported="true" kind="lib" path="lib/slf4j-log4j12-1.6.4.jar"/>
+       <classpathentry exported="true" kind="lib" path="test/lib/junit-4.10.jar" sourcepath="test/lib/junit-4.10-src.jar"/>
+       <classpathentry exported="true" kind="lib" path="test/lib/mockito-all-1.9.0.jar"/>
+       <classpathentry kind="lib" path="test/lib/asm-4.0.jar"/>
+       <classpathentry kind="lib" path="test/lib/asm-tree-4.0.jar"/>
+       <classpathentry exported="true" kind="lib" path="test/lib/powermock-mockito-1.4.12-full.jar"/>
+       <classpathentry exported="true" kind="lib" path="test/lib/javassist-3.16.1-GA.jar"/>
+       <classpathentry kind="lib" path="lib/ant.jar"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="test/src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
index 845a526..501d4ca 100755 (executable)
@@ -194,17 +194,16 @@ Export-Package:
  org.tizen.common.util.log,
  org.tizen.common.util.url.classpath,
  org.tizen.common.util.url.cp,
- org.tizen.common.util.url.vf,
- org.tizen.sdblib
+ org.tizen.common.util.url.vf
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Import-Package: 
  org.eclipse.jface.text,
- org.eclipse.ui.console
+ org.eclipse.ui.console,
+ org.tizen.sdblib
 Bundle-Activator: org.tizen.common.CommonPlugin
 Bundle-ActivationPolicy: lazy
 Bundle-ClassPath: .,
  lib/log4j-1.2.17.jar,
- lib/sdblib.jar,
  lib/slf4j-api-1.6.4.jar,
  lib/slf4j-log4j12-1.6.4.jar,
  lib/freemarker.jar,
diff --git a/org.tizen.common/lib/sdblib.jar b/org.tizen.common/lib/sdblib.jar
deleted file mode 100644 (file)
index d40c7c7..0000000
Binary files a/org.tizen.common/lib/sdblib.jar and /dev/null differ
index 16f6596..2d1d9e7 100755 (executable)
@@ -58,11 +58,27 @@ install()
 
         if [ "x${TARGET_OS}" = "x${platform}" ]
         then
-            __set_parameter
-               INSTALL_DIR=${SRCDIR}/package/${package_name}.package.${TARGET_OS}/data
-            mkdir -p ${INSTALL_DIR} 
-            __copy_necessary_binaries
-            ${build_script_path}/install.sh ${package_name}
+
+            if [ "x${package_name}" = "xcommon-eplugin" ]
+            then
+                INSTALL_DIR=${SRCDIR}/package/${package_name}.package.${TARGET_OS}/data
+                mkdir -p ${INSTALL_DIR} 
+                __set_parameter
+                __copy_necessary_binaries
+                ${build_script_path}/install.sh ${package_name}
+                sdblib_path="${INSTALL_DIR}/ide/plugins"
+                echo "sdblib path : ${sdblib_path}"
+                sdblib_name=`basename $(ls ${sdblib_path}/org.tizen.common.sdblib*.jar)`
+                echo "sdblib name : ${sdblib_name}"
+            elif [ "x${package_name}"  = "xsdblib" ]
+            then
+               if [ -e ${sdblib_path}/${sdblib_name} ]
+                then
+                    INSTALL_DIR=${SRCDIR}/package/${package_name}.package.${TARGET_OS}/data/tools/ide/lib
+                    mkdir -p ${INSTALL_DIR} 
+                    cp ${sdblib_path}/${sdblib_name} ${INSTALL_DIR}/${sdblib_name}
+                fi
+            fi
         else
             echo ""
         fi
index e631116..a9a1d70 100644 (file)
@@ -37,3 +37,28 @@ Build-dependency:base-ide-product [macos-64], pde-build [macos-64]
 Install-dependency:base-ide-product [macos-64]
 Description:Common plugin
 
+Package:sdblib
+OS:ubuntu-32
+Build-host-os:ubuntu-32, ubuntu-64
+Description:sdblib
+
+Package:sdblib
+OS:ubuntu-64
+Build-host-os:ubuntu-32, ubuntu-64
+Description:sdblib
+
+Package:sdblib
+OS:windows-32
+Build-host-os:ubuntu-32, ubuntu-64
+Description:sdblib
+
+Package:sdblib
+OS:windows-64
+Build-host-os:ubuntu-32, ubuntu-64
+Description:sdblib
+
+Package:sdblib
+OS:macos-64
+Build-host-os:ubuntu-32, ubuntu-64
+Description:sdblib
+