SyncServicepublic final class SyncService extends Object Sync service class to push/pull to/from devices/emulators, through the debug bridge.
To get a {@link SyncService} object, use {@link Device#getSyncService()}. |
Fields Summary |
---|
private static final byte[] | ID_OKAY | private static final byte[] | ID_FAIL | private static final byte[] | ID_STAT | private static final byte[] | ID_RECV | private static final byte[] | ID_DATA | private static final byte[] | ID_DONE | private static final byte[] | ID_SEND | private static final NullSyncProgresMonitor | sNullSyncProgressMonitor | private static final int | S_ISOCK | private static final int | S_IFLNK | private static final int | S_IFREG | private static final int | S_IFBLK | private static final int | S_IFDIR | private static final int | S_IFCHR | private static final int | S_IFIFO | private static final int | SYNC_DATA_MAX | private static final int | REMOTE_PATH_MAX_LENGTH | public static final int | RESULT_OKResult code for transfer success. | public static final int | RESULT_CANCELEDResult code for canceled transfer | public static final int | RESULT_UNKNOWN_ERRORResult code for unknown error | public static final int | RESULT_CONNECTION_ERRORResult code for network connection error | public static final int | RESULT_NO_REMOTE_OBJECTResult code for unknown remote object during a pull | public static final int | RESULT_TARGET_IS_FILEResult code when attempting to pull multiple files into a file | public static final int | RESULT_NO_DIR_TARGETResult code when attempting to pull multiple into a directory that does not exist. | public static final int | RESULT_REMOTE_PATH_ENCODINGResult code for wrong encoding on the remote path. | public static final int | RESULT_REMOTE_PATH_LENGTHResult code for remote path that is too long. | public static final int | RESULT_FILE_WRITE_ERRORResult code for error while writing local file. | public static final int | RESULT_FILE_READ_ERRORResult code for error while reading local file. | public static final int | RESULT_NO_LOCAL_FILEResult code for attempting to push a file that does not exist. | public static final int | RESULT_LOCAL_IS_DIRECTORYResult code for attempting to push a directory. | public static final int | RESULT_REMOTE_IS_FILEResult code for when the target path of a multi file push is a file. | public static final int | RESULT_BUFFER_OVERRUNResult code for receiving too much data from the remove device at once | private InetSocketAddress | mAddress | private Device | mDevice | private SocketChannel | mChannel | private byte[] | mBufferBuffer used to send data. Allocated when needed and reused afterward. |
Constructors Summary |
---|
SyncService(InetSocketAddress address, Device device)Creates a Sync service object.
mAddress = address;
mDevice = device;
|
Methods Summary |
---|
private static boolean | checkResult(byte[] result, byte[] code)Checks the result array starts with the provided code
if (result[0] != code[0] ||
result[1] != code[1] ||
result[2] != code[2] ||
result[3] != code[3]) {
return false;
}
return true;
| public void | close()Closes the connection.
if (mChannel != null) {
try {
mChannel.close();
} catch (IOException e) {
// nothing to be done really...
}
mChannel = null;
}
| private static byte[] | createFileReq(byte[] command, java.lang.String path)Creates the data array for a stat request.
byte[] pathContent = null;
try {
pathContent = path.getBytes(AdbHelper.DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
return null;
}
return createFileReq(command, pathContent);
| private static byte[] | createFileReq(byte[] command, byte[] path)Creates the data array for a file request. This creates an array with a 4 byte command + the
remote file name.
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[] | createReq(byte[] command, int value)Create a command with a code and an int values
byte[] array = new byte[8];
System.arraycopy(command, 0, array, 0, 4);
ArrayHelper.swap32bitsToArray(value, array, 4);
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(AdbHelper.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;
| private com.android.ddmlib.SyncService$SyncResult | doPull(com.android.ddmlib.FileListingService.FileEntry[] entries, java.lang.String localPath, FileListingService fileListingService, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Pulls multiple files/folders recursively.
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)
int type = e.getType();
if (type == FileListingService.TYPE_DIRECTORY) {
monitor.startSubTask(e.getFullPath());
String dest = localPath + File.separator + e.getName();
// 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);
SyncResult result = doPull(children, dest, fileListingService, monitor);
if (result.mCode != RESULT_OK) {
return result;
}
monitor.advance(1);
} else if (type == FileListingService.TYPE_FILE) {
monitor.startSubTask(e.getFullPath());
String dest = localPath + File.separator + e.getName();
SyncResult result = doPullFile(e.getFullPath(), dest, monitor);
if (result.mCode != RESULT_OK) {
return result;
}
}
}
return new SyncResult(RESULT_OK);
| private com.android.ddmlib.SyncService$SyncResult | doPullFile(java.lang.String remotePath, java.lang.String localPath, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Pulls a remote file
byte[] msg = null;
byte[] pullResult = new byte[8];
try {
byte[] remotePathContent = remotePath.getBytes(AdbHelper.DEFAULT_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.
AdbHelper.write(mChannel, msg, -1, AdbHelper.STD_TIMEOUT);
// read the result, in a byte array containing 2 ints
// (id, size)
AdbHelper.read(mChannel, pullResult, -1, AdbHelper.STD_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 (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
AdbHelper.read(mChannel, data, length, AdbHelper.STD_TIMEOUT);
// get the header for the next packet.
AdbHelper.read(mChannel, pullResult, -1, AdbHelper.STD_TIMEOUT);
} 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);
}
monitor.advance(length);
}
try {
fos.flush();
} catch (IOException e) {
return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
}
return new SyncResult(RESULT_OK);
| private com.android.ddmlib.SyncService$SyncResult | doPush(java.io.File[] fileArray, java.lang.String remotePath, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Push multiple files
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);
| private com.android.ddmlib.SyncService$SyncResult | doPushFile(java.lang.String localPath, java.lang.String remotePath, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Push a single file
FileInputStream fis = null;
byte[] msg;
try {
byte[] remotePathContent = remotePath.getBytes(AdbHelper.DEFAULT_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, 0644);
} 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 {
AdbHelper.write(mChannel, msg, -1, AdbHelper.STD_TIMEOUT);
} 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 {
AdbHelper.write(mChannel, mBuffer, readCount+8, AdbHelper.STD_TIMEOUT);
} catch (IOException e) {
return new SyncResult(RESULT_CONNECTION_ERROR, e);
}
// and advance the monitor
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.
AdbHelper.write(mChannel, msg, -1, AdbHelper.STD_TIMEOUT);
// read the result, in a byte array containing 2 ints
// (id, size)
byte[] result = new byte[8];
AdbHelper.read(mChannel, result, -1 /* full length */, AdbHelper.STD_TIMEOUT);
if (checkResult(result, ID_OKAY) == false) {
if (checkResult(result, ID_FAIL)) {
// read some error message...
int len = ArrayHelper.swap32bitFromArray(result, 4);
AdbHelper.read(mChannel, mBuffer, len, AdbHelper.STD_TIMEOUT);
// output the result?
String message = new String(mBuffer, 0, len);
Log.e("ddms", "transfer error: " + message);
return new SyncResult(RESULT_UNKNOWN_ERROR, message);
}
return new SyncResult(RESULT_UNKNOWN_ERROR);
}
} catch (IOException e) {
return new SyncResult(RESULT_CONNECTION_ERROR, e);
}
return new SyncResult(RESULT_OK);
| private static java.lang.String | errorCodeToString(int code)Converts an error code into a non-localized string
switch (code) {
case RESULT_OK:
return "Success.";
case RESULT_CANCELED:
return "Tranfert canceled by the user.";
case RESULT_UNKNOWN_ERROR:
return "Unknown Error.";
case RESULT_CONNECTION_ERROR:
return "Adb 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.";
}
throw new RuntimeException();
| private static int | getFileType(int mode)
if ((mode & S_ISOCK) == S_ISOCK) {
return FileListingService.TYPE_SOCKET;
}
if ((mode & S_IFLNK) == S_IFLNK) {
return FileListingService.TYPE_LINK;
}
if ((mode & S_IFREG) == S_IFREG) {
return FileListingService.TYPE_FILE;
}
if ((mode & S_IFBLK) == S_IFBLK) {
return FileListingService.TYPE_BLOCK;
}
if ((mode & S_IFDIR) == S_IFDIR) {
return FileListingService.TYPE_DIRECTORY;
}
if ((mode & S_IFCHR) == S_IFCHR) {
return FileListingService.TYPE_CHARACTER;
}
if ((mode & S_IFIFO) == S_IFIFO) {
return FileListingService.TYPE_FIFO;
}
return FileListingService.TYPE_OTHER;
| public static com.android.ddmlib.SyncService$ISyncProgressMonitor | getNullProgressMonitor()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}.
This object can be reused multiple times and can be used by concurrent threads.
return sNullSyncProgressMonitor;
| private int | getTotalLocalFileSize(java.io.File[] files)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.
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;
| private int | getTotalRemoteFileSize(com.android.ddmlib.FileListingService.FileEntry[] entries, FileListingService fls)compute the recursive file size of all the files in the list. Folder
have a weight of 1.
int count = 0;
for (FileEntry e : entries) {
int type = e.getType();
if (type == FileListingService.TYPE_DIRECTORY) {
// 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;
| boolean | openSync()Opens the sync connection. This must be called before any calls to push[File] / pull[File].
try {
mChannel = SocketChannel.open(mAddress);
mChannel.configureBlocking(false);
// target a specific device
AdbHelper.setDevice(mChannel, mDevice);
byte[] request = AdbHelper.formAdbRequest("sync:"); // $NON-NLS-1$
AdbHelper.write(mChannel, request, -1, AdbHelper.STD_TIMEOUT);
AdbResponse resp = AdbHelper.readAdbResponse(mChannel, false /* readDiagString */);
if (!resp.ioSuccess || !resp.okay) {
Log.w("ddms",
"Got timeout or unhappy response from ADB sync req: "
+ resp.message);
mChannel.close();
mChannel = null;
return false;
}
} catch (IOException e) {
if (mChannel != null) {
try {
mChannel.close();
} catch (IOException e1) {
// we do nothing, since we'll return false just below
}
mChannel = null;
return false;
}
}
return true;
| public com.android.ddmlib.SyncService$SyncResult | pull(com.android.ddmlib.FileListingService.FileEntry[] entries, java.lang.String localPath, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Pulls file(s) or folder(s).
// 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() == false) {
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;
| public com.android.ddmlib.SyncService$SyncResult | pullFile(com.android.ddmlib.FileListingService.FileEntry remote, java.lang.String localFilename, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Pulls a single file.
int total = remote.getSizeValue();
monitor.start(total);
SyncResult result = doPullFile(remote.getFullPath(), localFilename, monitor);
monitor.stop();
return result;
| public com.android.ddmlib.SyncService$SyncResult | push(java.lang.String[] local, com.android.ddmlib.FileListingService.FileEntry remote, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Push several files.
if (remote.isDirectory() == false) {
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;
| public com.android.ddmlib.SyncService$SyncResult | pushFile(java.lang.String local, java.lang.String remote, com.android.ddmlib.SyncService$ISyncProgressMonitor monitor)Push a single file.
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;
| private java.lang.Integer | readMode(java.lang.String path)Returns the mode of the remote file.
try {
// create the stat request message.
byte[] msg = createFileReq(ID_STAT, path);
AdbHelper.write(mChannel, msg, -1 /* full length */, AdbHelper.STD_TIMEOUT);
// read the result, in a byte array containing 4 ints
// (id, mode, size, time)
byte[] statResult = new byte[16];
AdbHelper.read(mChannel, statResult, -1 /* full length */, AdbHelper.STD_TIMEOUT);
// check we have the proper data back
if (checkResult(statResult, ID_STAT) == false) {
return null;
}
// we return the mode (2nd int in the array)
return ArrayHelper.swap32bitFromArray(statResult, 4);
} catch (IOException e) {
return null;
}
|
|