*.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
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,
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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+#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
--- /dev/null
+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
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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();
+};
--- /dev/null
+/*
+ * 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);
+ }
+}
+
+
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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*/);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+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));
+ }
+ }
+}
-<?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>
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,
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
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
+