VM aborting Getting error while running in emulator - android

I am using phonegap with socket programming.
When I check on emulator I am getting VM aborting error.
I am just calling my server and checking it to my emulator.
Here is my code.
package com.example.test;
import org.apache.cordova.DroidGap;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends DroidGap {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
super.loadUrl("file:///android_asset/www/index.html");
appView.addJavascriptInterface(new WebSocketFactory(appView), "WebSocketFactory");
}
}
---------------------------------------------------------------------
package com.example.test;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import android.util.Log;
import android.webkit.WebView;
public class WebSocket implements Runnable {
/**
* Enum for WebSocket Draft
*/
public enum Draft {
DRAFT75, DRAFT76
}
// //////////////// CONSTANT
/**
* The connection has not yet been established.
*/
public final static int WEBSOCKET_STATE_CONNECTING = 0;
/**
* The WebSocket connection is established and communication is possible.
*/
public final static int WEBSOCKET_STATE_OPEN = 1;
/**
* The connection is going through the closing handshake.
*/
public final static int WEBSOCKET_STATE_CLOSING = 2;
/**
* The connection has been closed or could not be opened.
*/
public final static int WEBSOCKET_STATE_CLOSED = 3;
/**
* An empty string
*/
private static String BLANK_MESSAGE = "";
/**
* The javascript method name for onOpen event.
*/
private static String EVENT_ON_OPEN = "onopen";
/**
* The javascript method name for onMessage event.
*/
private static String EVENT_ON_MESSAGE = "onmessage";
/**
* The javascript method name for onClose event.
*/
private static String EVENT_ON_CLOSE = "onclose";
/**
* The javascript method name for onError event.
*/
private static String EVENT_ON_ERROR = "onerror";
/**
* The default port of WebSockets, as defined in the spec.
*/
public static final int DEFAULT_PORT = 80;
/**
* The WebSocket protocol expects UTF-8 encoded bytes.
*/
public static final String UTF8_CHARSET = "UTF-8";
/**
* The byte representing Carriage Return, or \r
*/
public static final byte DATA_CR = (byte) 0x0D;
/**
* The byte representing Line Feed, or \n
*/
public static final byte DATA_LF = (byte) 0x0A;
/**
* The byte representing the beginning of a WebSocket text frame.
*/
public static final byte DATA_START_OF_FRAME = (byte) 0x00;
/**
* The byte representing the end of a WebSocket text frame.
*/
public static final byte DATA_END_OF_FRAME = (byte) 0xFF;
// //////////////// INSTANCE Variables
/**
* The WebView instance from Phonegap DroidGap
*/
private final WebView appView;
/**
* The unique id for this instance (helps to bind this to javascript events)
*/
private String id;
/**
* The URI this client is supposed to connect to.
*/
private URI uri;
/**
* The port of the websocket server
*/
private int port;
/**
* The Draft of the WebSocket protocol the Client is adhering to.
*/
private Draft draft;
/**
* The <tt>SocketChannel</tt> instance to use for this server connection.
* This is used to read and write data to.
*/
private SocketChannel socketChannel;
/**
* The 'Selector' used to get event keys from the underlying socket.
*/
private Selector selector;
/**
* Keeps track of whether or not the client thread should continue running.
*/
private boolean running;
/**
* Internally used to determine whether to recieve data as part of the
* remote handshake, or as part of a text frame.
*/
private boolean handshakeComplete;
/**
* The 1-byte buffer reused throughout the WebSocket connection to read
* data.
*/
private ByteBuffer buffer;
/**
* The bytes that make up the remote handshake.
*/
private ByteBuffer remoteHandshake;
/**
* The bytes that make up the current text frame being read.
*/
private ByteBuffer currentFrame;
/**
* Queue of buffers that need to be sent to the client.
*/
private BlockingQueue<ByteBuffer> bufferQueue;
/**
* Lock object to ensure that data is sent from the bufferQueue in the
* proper order
*/
private Object bufferQueueMutex = new Object();
/**
* Number 1 used in handshake
*/
private int number1 = 0;
/**
* Number 2 used in handshake
*/
private int number2 = 0;
/**
* Key3 used in handshake
*/
private byte[] key3 = null;
/**
* The readyState attribute represents the state of the connection.
*/
private int readyState = WEBSOCKET_STATE_CONNECTING;
private final WebSocket instance;
/**
* Constructor.
*
* Note: this is protected because it's supposed to be instantiated from {#link WebSocketFactory} only.
*
* #param appView
* {#link android.webkit.WebView}
* #param uri
* websocket server {#link URI}
* #param draft
* websocket server {#link Draft} implementation (75/76)
* #param id
* unique id for this instance
*/
protected WebSocket(WebView appView, URI uri, Draft draft, String id) {
this.appView = appView;
this.uri = uri;
this.draft = draft;
// port
port = uri.getPort();
if (port == -1) {
port = DEFAULT_PORT;
}
// Id
this.id = id;
this.bufferQueue = new LinkedBlockingQueue<ByteBuffer>();
this.handshakeComplete = false;
this.remoteHandshake = this.currentFrame = null;
this.buffer = ByteBuffer.allocate(1);
this.instance = this;
}
// //////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////// WEB SOCKET API Methods
// ///////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////
/**
* Starts a new Thread and connects to server
*
* #throws IOException
*/
public Thread connect() throws IOException {
this.running = true;
this.readyState = WEBSOCKET_STATE_CONNECTING;
// open socket
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
// set address
socketChannel.connect(new InetSocketAddress(uri.getHost(), port));
// start a thread to make connection
// More info:
// http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82
// http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
Log.v("websocket", "Starting a new thread to manage data reading/writing");
Thread th = new Thread(this);
th.start();
// return thread object for explicit closing, if needed
return th;
}
public void run() {
while (this.running) {
try {
_connect();
} catch (IOException e) {
this.onError(e);
}
}
}
/**
* Closes connection with server
*/
public void close() {
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSING;
// close socket channel
try {
this.socketChannel.close();
} catch (IOException e) {
this.onError(e);
}
this.running = false;
selector.wakeup();
// fire onClose method
this.onClose();
this.readyState = WebSocket.WEBSOCKET_STATE_CLOSED;
}
/**
* Sends <var>text</var> to server
*
* #param text
* String to send to server
*/
public void send(final String text) {
new Thread(new Runnable() {
#Override
public void run() {
if (instance.readyState == WEBSOCKET_STATE_OPEN) {
try {
instance._send(text);
} catch (IOException e) {
instance.onError(e);
}
} else {
instance.onError(new NotYetConnectedException());
}
}
}).start();
}
/**
* Called when an entire text frame has been received.
*
* #param msg
* Message from websocket server
*/
public void onMessage(final String msg) {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_MESSAGE, msg));
}
});
}
public void onOpen() {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_OPEN, BLANK_MESSAGE));
}
});
}
public void onClose() {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_CLOSE, BLANK_MESSAGE));
}
});
}
public void onError(final Throwable t) {
appView.post(new Runnable() {
#Override
public void run() {
appView.loadUrl(buildJavaScriptData(EVENT_ON_ERROR, t.getMessage()));
}
});
}
public String getId() {
return id;
}
/**
* #return the readyState
*/
public int getReadyState() {
return readyState;
}
/**
* Builds text for javascript engine to invoke proper event method with
* proper data.
*
* #param event
* websocket event (onOpen, onMessage etc.)
* #param msg
* Text message received from websocket server
* #return
*/
private String buildJavaScriptData(String event, String msg) {
String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\"," + "\"data\":'" + msg.replaceAll("'", "\\\\'")
+ "'" + "}" + ")";
return _d;
}
// //////////////////////////////////////////////////////////////////////////////////////
// /////////////////////////// WEB SOCKET Internal Methods
// //////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////
private boolean _send(String text) throws IOException {
if (!this.handshakeComplete) {
throw new NotYetConnectedException();
}
if (text == null) {
throw new NullPointerException("Cannot send 'null' data to a WebSocket.");
}
// Get 'text' into a WebSocket "frame" of bytes
byte[] textBytes = text.getBytes(UTF8_CHARSET.toString());
ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2);
b.put(DATA_START_OF_FRAME);
b.put(textBytes);
b.put(DATA_END_OF_FRAME);
b.rewind();
// See if we have any backlog that needs to be sent first
if (_write()) {
// Write the ByteBuffer to the socket
this.socketChannel.write(b);
}
// If we didn't get it all sent, add it to the buffer of buffers
if (b.remaining() > 0) {
if (!this.bufferQueue.offer(b)) {
throw new IOException("Buffers are full, message could not be sent to"
+ this.socketChannel.socket().getRemoteSocketAddress());
}
return false;
}
return true;
}
// actual connection logic
private void _connect() throws IOException {
// Continuous loop that is only supposed to end when "close" is called.
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> i = keys.iterator();
while (i.hasNext()) {
SelectionKey key = i.next();
i.remove();
if (key.isConnectable()) {
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
socketChannel.register(selector, SelectionKey.OP_READ);
_writeHandshake();
}
if (key.isReadable()) {
try {
_read();
} catch (NoSuchAlgorithmException nsa) {
this.onError(nsa);
}
}
}
}
private void _writeHandshake() throws IOException {
String path = this.uri.getPath();
if (path.indexOf("/") != 0) {
path = "/" + path;
}
String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : "");
String origin = "*"; // TODO: Make 'origin' configurable
String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n"
+ "Host: " + host + "\r\n" + "Origin: " + origin + "\r\n";
// Add random keys for Draft76
if (this.draft == Draft.DRAFT76) {
request += "Sec-WebSocket-Key1: " + this._randomKey() + "\r\n";
request += "Sec-WebSocket-Key2: " + this._randomKey() + "\r\n";
request += "\r\n";
this.key3 = new byte[8];
(new Random()).nextBytes(this.key3);
// Convert to bytes early so last eight bytes don't get jacked
byte[] bRequest = request.getBytes(UTF8_CHARSET);
byte[] bToSend = new byte[bRequest.length + 8];
// Copy in the Request bytes
System.arraycopy(bRequest, 0, bToSend, 0, bRequest.length);
// Now tack on key3 bytes
System.arraycopy(this.key3, 0, bToSend, bRequest.length, this.key3.length);
// Now we can send all keys as a single frame
_write(bToSend);
return;
}
request += "\r\n";
_write(request.getBytes(UTF8_CHARSET));
}
private boolean _write() throws IOException {
synchronized (this.bufferQueueMutex) {
ByteBuffer buffer = this.bufferQueue.peek();
while (buffer != null) {
this.socketChannel.write(buffer);
if (buffer.remaining() > 0) {
return false; // Didn't finish this buffer. There's more to
// send.
} else {
this.bufferQueue.poll(); // Buffer finished. Remove it.
buffer = this.bufferQueue.peek();
}
}
return true;
}
}
private void _write(byte[] bytes) throws IOException {
this.socketChannel.write(ByteBuffer.wrap(bytes));
}
private void _read() throws IOException, NoSuchAlgorithmException {
this.buffer.rewind();
int bytesRead = -1;
try {
bytesRead = this.socketChannel.read(this.buffer);
} catch (Exception ex) {
}
if (bytesRead == -1) {
close();
} else if (bytesRead > 0) {
this.buffer.rewind();
if (!this.handshakeComplete) {
_readHandshake();
} else {
_readFrame();
}
}
}
private void _readFrame() throws UnsupportedEncodingException {
byte newestByte = this.buffer.get();
if (newestByte == DATA_START_OF_FRAME) { // Beginning of Frame
this.currentFrame = null;
} else if (newestByte == DATA_END_OF_FRAME) { // End of Frame
String textFrame = null;
// currentFrame will be null if END_OF_FRAME was send directly after
// START_OF_FRAME, thus we will send 'null' as the sent message.
if (this.currentFrame != null) {
textFrame = new String(this.currentFrame.array(), "ISO-8859-1");
}
// fire onMessage method
this.onMessage(textFrame);
} else { // Regular frame data, add to current frame buffer
ByteBuffer frame = ByteBuffer.allocate((this.currentFrame != null ? this.currentFrame.capacity() : 0)
+ this.buffer.capacity());
if (this.currentFrame != null) {
this.currentFrame.rewind();
frame.put(this.currentFrame);
}
frame.put(newestByte);
this.currentFrame = frame;
String textFrame = new String(this.currentFrame.array(),"ISO-8859-1");
this.onMessage(textFrame);
}
}
private void _readHandshake() throws IOException, NoSuchAlgorithmException {
ByteBuffer ch = ByteBuffer.allocate((this.remoteHandshake != null ? this.remoteHandshake.capacity() : 0)
+ this.buffer.capacity());
if (this.remoteHandshake != null) {
this.remoteHandshake.rewind();
ch.put(this.remoteHandshake);
}
ch.put(this.buffer);
this.remoteHandshake = ch;
byte[] h = this.remoteHandshake.array();
// If the ByteBuffer contains 16 random bytes, and ends with
// 0x0D 0x0A 0x0D 0x0A (or two CRLFs), then the client
// handshake is complete for Draft 76 Client.
if ((h.length >= 20 && h[h.length - 20] == DATA_CR && h[h.length - 19] == DATA_LF
&& h[h.length - 18] == DATA_CR && h[h.length - 17] == DATA_LF)) {
_readHandshake(new byte[] { h[h.length - 16], h[h.length - 15], h[h.length - 14], h[h.length - 13],
h[h.length - 12], h[h.length - 11], h[h.length - 10], h[h.length - 9], h[h.length - 8],
h[h.length - 7], h[h.length - 6], h[h.length - 5], h[h.length - 4], h[h.length - 3],
h[h.length - 2], h[h.length - 1] });
// If the ByteBuffer contains 8 random bytes,ends with
// 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response
// contains Sec-WebSocket-Key1 then the client
// handshake is complete for Draft 76 Server.
} else if ((h.length >= 12 && h[h.length - 12] == DATA_CR && h[h.length - 11] == DATA_LF
&& h[h.length - 10] == DATA_CR && h[h.length - 9] == DATA_LF)
&& new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec-WebSocket-Key1")) {// ************************
_readHandshake(new byte[] { h[h.length - 8], h[h.length - 7], h[h.length - 6], h[h.length - 5],
h[h.length - 4], h[h.length - 3], h[h.length - 2], h[h.length - 1] });
// Consider Draft 75, and the Flash Security Policy
// Request edge-case.
} else if ((h.length >= 4 && h[h.length - 4] == DATA_CR && h[h.length - 3] == DATA_LF
&& h[h.length - 2] == DATA_CR && h[h.length - 1] == DATA_LF)
&& !(new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec"))
|| (h.length == 23 && h[h.length - 1] == 0)) {
_readHandshake(null);
}else{
_readHandshake(null);
}
}
private void _readHandshake(byte[] handShakeBody) throws IOException, NoSuchAlgorithmException {
// byte[] handshakeBytes = this.remoteHandshake.array();
// String handshake = new String(handshakeBytes, UTF8_CHARSET);
// TODO: Do some parsing of the returned handshake, and close connection
// in received anything unexpected!
this.handshakeComplete = true;
boolean isConnectionReady = true;
if (this.draft == WebSocket.Draft.DRAFT76) {
if (handShakeBody == null) {
isConnectionReady = true;
} else{
byte[] challenge = new byte[] { (byte) (this.number1 >> 24), (byte) ((this.number1 << 8) >> 24),
(byte) ((this.number1 << 16) >> 24), (byte) ((this.number1 << 24) >> 24),
(byte) (this.number2 >> 24), (byte) ((this.number2 << 8) >> 24),
(byte) ((this.number2 << 16) >> 24), (byte) ((this.number2 << 24) >> 24), this.key3[0],
this.key3[1], this.key3[2], this.key3[3], this.key3[4], this.key3[5], this.key3[6], this.key3[7] };
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] expected = md5.digest(challenge);
for (int i = 0; i < handShakeBody.length; i++) {
if (expected[i] != handShakeBody[i]) {
isConnectionReady = true;
}
}
}
}
if (isConnectionReady) {
this.readyState = WEBSOCKET_STATE_OPEN;
// fire onOpen method
this.onOpen();
} else {
close();
}
}
private String _randomKey() {
Random r = new Random();
long maxNumber = 4294967295L;
long spaces = r.nextInt(12) + 1;
int max = new Long(maxNumber / spaces).intValue();
max = Math.abs(max);
int number = r.nextInt(max) + 1;
if (this.number1 == 0) {
this.number1 = number;
} else {
this.number2 = number;
}
long product = number * spaces;
String key = Long.toString(product);
int numChars = r.nextInt(12);
for (int i = 0; i < numChars; i++) {
int position = r.nextInt(key.length());
position = Math.abs(position);
char randChar = (char) (r.nextInt(95) + 33);
// exclude numbers here
if (randChar >= 48 && randChar <= 57) {
randChar -= 15;
}
key = new StringBuilder(key).insert(position, randChar).toString();
}
for (int i = 0; i < spaces; i++) {
int position = r.nextInt(key.length() - 1) + 1;
position = Math.abs(position);
key = new StringBuilder(key).insert(position, "\u0020").toString();
}
return key;
}
}
----------------------------------------------------------------------------
package com.example.test;
import java.net.URI;
import java.util.Random;
import android.webkit.WebView;
/**
* The <tt>WebSocketFactory</tt> is like a helper class to instantiate new
* WebSocket instaces especially from Javascript side. It expects a valid
* "ws://" URI.
*
* #author Animesh Kumar
*/
public class WebSocketFactory {
/** The app view. */
WebView appView;
/**
* Instantiates a new web socket factory.
*
* #param appView
* the app view
*/
public WebSocketFactory(WebView appView) {
this.appView = appView;
}
public WebSocket getInstance(String url) {
// use Draft75 by default
return getInstance(url, WebSocket.Draft.DRAFT76);
}
public WebSocket getInstance(String url, WebSocket.Draft draft) {
WebSocket socket = null;
Thread th = null;
try {
socket = new WebSocket(appView, new URI(url), draft, getRandonUniqueId());
th = socket.connect();
return socket;
} catch (Exception e) {
//Log.v("websocket", e.toString());
if(th != null) {
th.interrupt();
}
}
return null;
}
/**
* Generates random unique ids for WebSocket instances
*
* #return String
*/
private String getRandonUniqueId() {
return "WEBSOCKET." + new Random().nextInt(100);
}
}
-------------------------------------------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Hello World</title>
</head>
<body>
<script type="text/javascript" src="cordova-2.7.0.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript" src="js/websocket.js"></script>
<script type="text/javascript">
var socket = new WebSocket('ws://192.168.1.3:8101/');
// push a message after the connection is established.
socket.onopen = function() {
socket.send('Hello World')
};
// alerts message pushed from server
socket.onmessage = function(msg) {
console.log("TTTTT"+JSON.stringify(msg));
};
// alert close event
socket.onclose = function() {
alert('closed');
};
</script>
</body>
</html>
From the script I am calling my functions.
But when I check it on emulator it is showing that error.
Please help me removing this error.
Thanks.

Related

Android how to handle retrofit response when Content-Type = application/octet-stream?

retrofit request interface:
public interface IDataService {
// Request URL: https://demo.testdata.com/getData/order?timeStamp=1491986181670&callerId=android_platform
// Request Method: GET
// Response Content-Type: application/octet-stream
#GET("https://demo.testdata.com/getData/" + "{type}")
Observable<Response> getTestData(#Path(value = "type", encode = false) String type
, #Query("callerId") String callerId, #Query("timeStamp") long timeStamp);
}
Use the retrofit request interface:
In TestActivity.java
public Observable<String> getTestData(IDataService dataService) {
final BehaviorSubject<String> subject = BehaviorSubject.create();
if (null == dataService) {
subject.onError(new NullPointerException());
return subject;
}
bindObservable(dataService.getTestData("order", "android_platform", new Date().getTime())).subscribeOn(Schedulers.io())
.subscribe(new Subscriber<Response>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
subject.onError(e);
}
#Override
public void onNext(retrofit.client.Response response) {
// TODO: how to handle the response body content?
// In general, response is json string. Then parse and convert json string to Object.
// Now Response Content-Type = application/octet-stream, How to handle it?
}
});
return subject.asObservable();
}
Question: In general, response Content-Type=application/json, parse JSON string to Object. Now,Response Content-Type = application/octet-stream, How to handle it?
Ref:
com.squareup.retrofit:retrofit:1.9.0
io.reactivex:rxjava:1.1.0
io.reactivex:rxandroid:1.1.0
Moved solution from question to answer:
Solution:
I had solved the problem. The brief description for the solution is below :
Retrofit Response -> InputStream ->java bytes[] -> treat byte as short or int -> int[] or short[].
Code
Below is my detail demo code.
Android App client, which receiver 8-bit unsigned integer array.
IRetrofitService.java
public class IRetrofitService {
// Local Node.JS Server
/**
* FAQ: Retrofit: java.net.ConnectException: Connection refused
* Reason:Node.js server PI is http://127.0.0.1:8888/.
* If you are referring your localhost on your system from the Android emulator then you have to use http://10.0.2.2:8888/ .
Because Android emulator runs in a Virtual Machine therefore here 127.0.0.1 or localhost will be emulator's own loopback address
Ref: https://stackoverflow.com/questions/5495534/java-net-connectexception-localhost-127-0-0-18080-connection-refused
*/
private static final String PATH = "http://10.0.2.2:8888";
public interface ApiManagerService {
#GET("/")
#Headers("Content-Type:application/octet-stream")
Observable<Response> getUnsignedBytes();
}
private static final RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(PATH).setLogLevel(RestAdapter.LogLevel.FULL).build();
public static final ApiManagerService apiManager = restAdapter.create(ApiManagerService.class);
}
RetrofitDemoActivity.java
import android.util.Log;
import android.widget.Toast;
import com.hades.android.example.R;
import com.hades.android.example.framework.BaseActivity;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import butterknife.OnClick;
import retrofit.client.Response;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* RxJava and RxAndroid , Retrofit to request data from server.
* Need <uses-permission android:name="android.permission.INTERNET" />
*/
public class RetrofitDemoActivity extends BaseActivity {
public static final String TAG = RetrofitDemoActivity.class.getSimpleName();
private int[] singedBytes;
// TODO: IRetrofitService.apiManager.getUnsignedBytes()
private Observable<Response> getUnsignedBytes() {
return IRetrofitService.apiManager.getUnsignedBytes();
}
#OnClick(R.id.requestUnsignedBytes)
public void requestUnsignedBytes() {
Log.d(TAG, "requestUnsignedBytes,----->");
bindObservable(getUnsignedBytes()).observeOn(Schedulers.io()).subscribe(new Subscriber<Response>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
/**
* Response content is 8 bites unsigned byte
* [246, 254, 255, 0, 10, 126, 127, 128, 129, 253, 254, 255, 0, 1, 232]
*/
/**
s,buf_size=15,read_buf_size=15
start parse
origin = -10, afterTransValue = 246
origin = -2, afterTransValue = 254
origin = -1, afterTransValue = 255
origin = 0, afterTransValue = 0
origin = 10, afterTransValue = 10
origin = 126, afterTransValue = 126
origin = 127, afterTransValue = 127
origin = -128, afterTransValue = 128
origin = -127, afterTransValue = 129
origin = -3, afterTransValue = 253
origin = -2, afterTransValue = 254
origin = -1, afterTransValue = 255
origin = 0, afterTransValue = 0
origin = 1, afterTransValue = 1
origin = -24, afterTransValue = 232
end parse
*/
#Override
public void onNext(Response response) {
InputStream inputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
inputStream = response.getBody().in();
bufferedInputStream = new BufferedInputStream(inputStream);
int buf_size = inputStream.available();
byte[] bytes = new byte[buf_size];
int read_buf_size = bufferedInputStream.read(bytes, 0, buf_size);
Log.d(TAG, "requestUnsignedBytes,buf_size=" + buf_size + ",read_buf_size=" + read_buf_size);
if (null == bytes) {
return;
}
if (null != singedBytes) {
singedBytes = null;
}
singedBytes = new int[buf_size];
Log.d(TAG, "requestUnsignedBytes, start parse");
for (int i = 0; i < bytes.length; i++) {
/**
* First, read as the 8 bites signed byte.
* Second, get the signed tag as the value.
*/
byte origin = bytes[i];
int afterTransValue = 0xFF & origin;
Log.d(TAG, "requestUnsignedBytes,origin = " + bytes[i] + ", afterTransValue = " + afterTransValue);
singedBytes[i] = afterTransValue;
}
Log.d(TAG, "requestUnsignedBytes, end parse");
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
});
}
}
Node.JS is as demo server,which reponse 8-bit unsigned integer array.
server.js
var http = require('http');
var server = http.createServer(function (request, response) {
var sampleBytes = new Uint8Array(15);
sampleBytes[0] = -10;
sampleBytes[1] = -2;
sampleBytes[2] = -1;
sampleBytes[3] = 0;
sampleBytes[4] = 10;
sampleBytes[5] = 126;
sampleBytes[6] = 127;
sampleBytes[7] = 128;
sampleBytes[8] = 129;
sampleBytes[9] = 253;
sampleBytes[10] = 254;
sampleBytes[11] = 255;
sampleBytes[12] = 256;
sampleBytes[13] = 257;
sampleBytes[14] = 1000;
console.log(sampleBytes);
var buffer = new Buffer(sampleBytes);
response.writeHead(200,{'Access-Control-Allow-Origin':'*','Access-Control-Allow-Method':'GET,POST','Content-Type':'application/octet-stream'});
response.write(buffer);
response.end();
});
server.listen(8888,"localhost",function(){
console.log("start monitor...");
});
console.log('Server running at http://127.0.0.1:8888/');
Ref:
com.squareup.retrofit:retrofit:1.9.0
io.reactivex:rxjava:1.1.0
io.reactivex:rxandroid:1.1.0

Android Bad UDP length > IP PAYLOAD length

I write a Android program to receive Multicast packets.
The code is in below.
The question is that I can receive the normal packets,
but I can't receive the packets with Bad UDP length > IP PAYLOAD length.
I guess that the bogus packets are drooped by someone.
How Can I receive the bogus packets in Android ,Thanks!
public class SSDP extends Thread {
/**
* Default IPv4 multicast address for SSDP messages
*/
public static final String ADDRESS = "239.100.10.100";
public static final String LOG_TAG = "SSDP";
public static final boolean DEBUG = false;
private SocketAddress mMulticastGroupAddress = new InetSocketAddress("239.100.10.100", 2060);
private MulticastSocket mMulticastSocket;
private DatagramSocket mUnicastSocket;
private NetworkInterface mNetIf;
private Context mContext;
private boolean mRunning = false;
private byte[] mFrameNumber, mNowFrameNumber, mSequenceNumber, mNowSequenceNumber;
private ByteArrayBuffer mReadBuffer;
private boolean mLast = false;
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public SSDP(Context ctx) throws IOException {
mContext = ctx;
mNetIf = Utils.getActiveNetworkInterface();
}
#Override
public synchronized void start() {
mRunning = true;
super.start();
}
#Override
public void run() {
try {
mMulticastSocket = new MulticastSocket(2060);
mMulticastSocket.setLoopbackMode(true);
mMulticastSocket.joinGroup(mMulticastGroupAddress, mNetIf);
mUnicastSocket = new DatagramSocket(null);
mUnicastSocket.setReuseAddress(true);
mUnicastSocket.bind(new InetSocketAddress(Utils.getLocalV4Address(mNetIf),2060));
} catch (IOException e) {
UtilLog("Setup SSDP failed: " + e);
}
while(mRunning)
{
DatagramPacket dp = null;
try
{
dp = receive();
//do somethins
}
catch (IOException e)
{
UtilLog("SSDP fail: " + e);
}
}
UtilLog("SSDP shutdown.");
}
public synchronized void shutdown() {
mRunning = false;
}
private void UtilLog(String message)
{
if(DEBUG)
Log.d(LOG_TAG, message);
}
private DatagramPacket receive() throws IOException {
byte[] buf = new byte[1500];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
mMulticastSocket.receive(dp);
return dp;
}
}
OK, I solved it.
It's a short packet issue in the kernel.
I modified the kernel code.
Here is the patch.
Index: net/ipv4/ip_input.c
===================================================================
--- net/ipv4/ip_input.c (revision 10665)
+++ net/ipv4/ip_input.c (working copy)
## -420,8 +420,8 ##
len = ntohs(iph->tot_len);
if (skb->len < len) {
- IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
- goto drop;
+ //IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INTRUNCATEDPKTS);
+ //goto drop;
} else if (len < (iph->ihl*4))
goto inhdr_error;
Index: net/ipv4/udp.c
===================================================================
--- net/ipv4/udp.c (revision 10665)
+++ net/ipv4/udp.c (working copy)
## -1631,13 +1631,13 ##
saddr = ip_hdr(skb)->saddr;
daddr = ip_hdr(skb)->daddr;
- if (ulen > skb->len)
- goto short_packet;
+// if (ulen > skb->len)
+// goto short_packet;
if (proto == IPPROTO_UDP) {
/* UDP validates ulen. */
- if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
- goto short_packet;
+// if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
+// goto short_packet;
uh = udp_hdr(skb);
}

BLE tempature sensor service detection

I have source code which is able to scan for BLE devies which are temp sensors, but when scanning it will not show any other BLE decies. I want to modify this code attached so that I can scan and view all BLE devices, regardless if it is a temp sensor. Can someone please explain me or show me how i can do this. here are the codes snippets to grab BLE Temp sensor data.
package no.nordicsemi.android.nrftemp.ble;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import no.nordicsemi.android.nrftemp.ble.parser.TemperatureData;
import no.nordicsemi.android.nrftemp.ble.parser.TemperatureDataParser;
import no.nordicsemi.android.nrftemp.database.DatabaseHelper;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.os.Handler;
public class TemperatureManager implements BluetoothAdapter.LeScanCallback {
/** An minimal interval between each data row in the database for a single device */
private static final long DATA_INTERVAL = 60 * 5 * 1000L; // ms
private BluetoothAdapter mBluetoothAdapter;
private DatabaseHelper mDatabaseHelper;
private List<TemperatureManagerCallback> mListeners;
public TemperatureManager(final Context context, final DatabaseHelper databaseHelper) {
final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
mDatabaseHelper = databaseHelper;
mListeners = new ArrayList<TemperatureManagerCallback>(2);
}
public void addCallback(final TemperatureManagerCallback callback) {
mListeners.add(callback);
}
public void removeCallback(final TemperatureManagerCallback callback) {
mListeners.remove(callback);
}
private void fireOnDevicesScanned() {
for (TemperatureManagerCallback callback : mListeners)
callback.onDevicesScaned();
}
private void fireOnRssiUpdate(final long sensorId, final int rssi) {
for (TemperatureManagerCallback callback : mListeners)
callback.onRssiUpdate(sensorId, rssi);
}
/**
* Starts scanning for temperature data. Call {#link #stopScan()} when done to save the power.
*/
public void startScan() {
mBluetoothAdapter.startLeScan(this);
}
/**
* Starts scanning for temperature data. Call {#link #stopScan()} when done to save the power.
*/
public void startScan(final long period) {
mBluetoothAdapter.startLeScan(this);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, period);
}
/**
* Stops scanning for temperature data from BLE sensors.
*/
public void stopScan() {
mBluetoothAdapter.stopLeScan(this);
}
#Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
final TemperatureData td = TemperatureDataParser.parse(device, scanRecord);
if (!td.isValid())
return;
final long now = Calendar.getInstance().getTimeInMillis();
final long then = mDatabaseHelper.getLastTimestamp(td.getAddress());
if (now - then > DATA_INTERVAL) {
mDatabaseHelper.addNewSensorData(device.getAddress(), device.getName(), now,
td.getTemp(), td.getBattery());
fireOnDevicesScanned();
}
final long sensorId = mDatabaseHelper.findSensor(device.getAddress());
final int rssiPercent = (int) (100.0f * (127.0f + rssi) / 127.0f + 20.0f);
mDatabaseHelper.updateSensorRssi(sensorId, rssiPercent);
fireOnRssiUpdate(sensorId, rssiPercent);
}
/**
* Return <code>true</code> if Bluetooth is currently enabled and ready for use.
*
* #return <code>true</code> if the local adapter is turned on
*/
public boolean isEnabled() {
return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
}
}
package no.nordicsemi.android.nrftemp.ble.parser;
import android.bluetooth.BluetoothDevice;
/**
* Domain class contains data obtained from a single advertising package from a temperature sensor.
*/
public class TemperatureData {
/** The device address */
private final String address;
/** The device name */
private String name;
/** The temperature value */
private double temp;
/** Battery status (number 0-100 in percent) */
private int battery = 0xFF; // unknown value
/**
* The flag whether the data are valid (holds real temperature measurement or not)
*/
private boolean valid;
public TemperatureData(BluetoothDevice device) {
address = device.getAddress();
name = device.getName();
}
public String getAddress() {
return address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getTemp() {
return temp;
}
public void setTemp(double temp) {
this.temp = temp;
this.valid = true;
}
public int getBattery() {
return battery;
}
public void setBattery(int battery) {
this.battery = battery;
}
public boolean isValid() {
return valid;
}
}
package no.nordicsemi.android.nrftemp.ble.parser;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import android.bluetooth.BluetoothDevice;
import android.util.Log;
public class TemperatureDataParser {
private static final String TAG = "TemperatureDataParser";
private static final int FLAGS = 0x02; // "Flags" Data Type (See section 18.1 of Core_V4.0.pdf)
private static final int LOCAL_NAME = 0x09; // "Complete Local Name" Data Type (See section 18.3 of Core_V4.0.pdf)
private static final int SERVICE_DATA = 0x16; // "Service Data" Data Type (See section 18.10 of Core_V4.0.pdf)
private static final short TEMPERATURE_SERVICE_UUID = 0x1809;
private static final short BATTERY_SERVICE_UUID = 0x180F;
private static final short DEVICE_INFORMATION_SERVICE_UUID = 0x180A;
/**
* Parses the advertising package obtained by BLE device
*
* #param data
* the data obtained (EIR data). Read Bluetooth Core Specification v4.0 (Core_V4.0.pdf -> Vol.3 -> Part C -> Section 8) for details
* #return the parsed temperature data
* #throws ParseException
* thrown when the given data does not fit the expected format
*/
public static TemperatureData parse(final BluetoothDevice device, final byte[] data) {
final TemperatureData td = new TemperatureData(device);
/*
* First byte of each EIR Data Structure has it's length (1 octet).
* There comes the EIR Data Type (n bytes) and (length - n bytes) of data.
* See Core_V4.0.pdf -> Vol.3 -> Part C -> Section 8 for details
*/
for (int i = 0; i < data.length;) {
final int eirLength = data[i];
// check whether there is no more to read
if (eirLength == 0)
break;
final int eirDataType = data[++i];
switch (eirDataType) {
case FLAGS:
// do nothing
break;
case LOCAL_NAME:
/*
* Local name data structure contains length - 1 bytes of the device name (1 byte for the data type)
*/
final String name = decodeLocalName(data, i + 1, eirLength - 1);
td.setName(name);
break;
case SERVICE_DATA:
/*
* First 2 bytes of service data are the 16-bit Service UUID in reverse order. The rest is service specific data.
*/
final short serviceUUID = decodeServiceUUID(data, i + 1);
switch (serviceUUID) {
case BATTERY_SERVICE_UUID:
final int battery = decodeBatteryLevel(data, i + 3);
td.setBattery(battery);
break;
case TEMPERATURE_SERVICE_UUID:
final double temp = decodeTempLevel(data, i + 3);
td.setTemp(temp);
break;
case DEVICE_INFORMATION_SERVICE_UUID:
// do nothing
break;
}
break;
default:
break;
}
i += eirLength;
}
return td;
}
private static String decodeLocalName(final byte[] data, final int start, final int length) {
try {
return new String(data, start, length, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unable to convert the local name to UTF-8", e);
return null;
}
}
private static short decodeServiceUUID(final byte[] data, final int start) {
return (short) ((data[start + 1] << 8) | data[start]);
}
private static int decodeBatteryLevel(final byte[] data, final int start) {
/*
* Battery level is a 1 byte number 0-100 (0x64). Value 255 (0xFF) is used for illegal measurement values
*/
return data[start];
}
private static float decodeTempLevel(final byte[] data, final int start) {
return bytesToFloat(data[start], data[start + 1], data[start + 2], data[start + 3]);
}
/**
* Convert signed bytes to a 32-bit short float value.
*/
private static float bytesToFloat(byte b0, byte b1, byte b2, byte b3) {
int mantissa = unsignedToSigned(unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8) + (unsignedByteToInt(b2) << 16), 24);
return (float) (mantissa * Math.pow(10, b3));
}
/**
* Convert a signed byte to an unsigned int.
*/
private static int unsignedByteToInt(byte b) {
return b & 0xFF;
}
/**
* Convert an unsigned integer value to a two's-complement encoded signed value.
*/
private static int unsignedToSigned(int unsigned, int size) {
if ((unsigned & (1 << size - 1)) != 0) {
unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
}
return unsigned;
}
}
As far as I can see, the code is scanning for every BLE device already, there are no service uuid restrictions in the scanning mBluetoothAdapter.startLeScan(this);
The issue with this code (as with many examples) is that it assumes every device to be of their own type (Temperature sensor in this case).
The assumption is here:
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
final TemperatureData td = TemperatureDataParser.parse(device, scanRecord);
The only way to know what services a device support is by connecting to it, do service discovery and the check for the supported services.
Regards, Rob.

Performance issue with Volley's DiskBasedCache

In my Photo Collage app for Android I'm using Volley for loading images.
I'm using the DiskBasedCache (included with volley) with 50 mb storage to prevent re-downloading the same images multiple times.
Last time I checked the DiskBasedCache contained about 1000 cache entries.
When my app starts Volley calls mCache.initialize() and it will spend about 10 seconds (!) on my Galaxy S4 to do the following:
List all files in cache folder
Open each and every file and read the header section.
I find that reading 1000+ files at startup is not a very efficient way to load the cache index! :-)
From volley/toolbox/DiskBasedCache.java:
#Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
I'm looking for a fast and scalable solution. Perhaps an alternative DiskBasedCache implementation or suggestions on how to improve the volley library.
Update: (2014-01-06)
Noticing that the Volley cache used a lot of small (1 byte) IO read/writes. I cloned DiskBasedCache.java and encapsulating all FileInputStreams and FileOutputStreams with BufferedInputStream and BufferedOutputStreams. I found that that this optimization gave me a 3-10 times speed up.
This modification has a low risks of bugs compared to writing a new disk cache with a central index file.
Update: (2014-01-10)
Here is new class BufferedDiskBasedCache.java that I'm using now.
package no.ludde.android.ds.android.volley;
/*
* Copyright (C) 2011 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.
*/
import android.os.SystemClock;
import com.android.volley.Cache;
import com.android.volley.VolleyLog;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Cache implementation that caches files directly onto the hard disk in the specified
* directory. The default disk usage size is 5MB, but is configurable.
*/
public class BufferedDiskBasedCache implements Cache {
/** Map of the Key, CacheHeader pairs */
private final Map<String, CacheHeader> mEntries =
new LinkedHashMap<String, CacheHeader>(16, .75f, true);
/** Total amount of space currently used by the cache in bytes. */
private long mTotalSize = 0;
/** The root directory to use for the cache. */
private final File mRootDirectory;
/** The maximum size of the cache in bytes. */
private final int mMaxCacheSizeInBytes;
/** Default maximum disk usage in bytes. */
private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
/** High water mark percentage for the cache */
private static final float HYSTERESIS_FACTOR = 0.9f;
/** Magic number for current version of cache file format. */
private static final int CACHE_MAGIC = 0x20120504;
/**
* Constructs an instance of the DiskBasedCache at the specified directory.
* #param rootDirectory The root directory of the cache.
* #param maxCacheSizeInBytes The maximum size of the cache in bytes.
*/
public BufferedDiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
mRootDirectory = rootDirectory;
mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
/**
* Constructs an instance of the DiskBasedCache at the specified directory using
* the default maximum cache size of 5MB.
* #param rootDirectory The root directory of the cache.
*/
public BufferedDiskBasedCache(File rootDirectory) {
this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
}
/**
* Clears the cache. Deletes all cached files from disk.
*/
#Override
public synchronized void clear() {
File[] files = mRootDirectory.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
mEntries.clear();
mTotalSize = 0;
VolleyLog.d("Cache cleared.");
}
/**
* Returns the cache entry with the specified key if it exists, null otherwise.
*/
#Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
// if the entry does not exist, return.
if (entry == null) {
return null;
}
File file = getFileForKey(key);
CountingInputStream cis = null;
try {
cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
CacheHeader.readHeader(cis); // eat header
byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
return entry.toCacheEntry(data);
} catch (IOException e) {
VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
remove(key);
return null;
} finally {
if (cis != null) {
try {
cis.close();
} catch (IOException ioe) {
return null;
}
}
}
}
/**
* Initializes the DiskBasedCache by scanning for all files currently in the
* specified root directory. Creates the root directory if necessary.
*/
#Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
BufferedInputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(file));
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
/**
* Invalidates an entry in the cache.
* #param key Cache key
* #param fullExpire True to fully expire the entry, false to soft expire
*/
#Override
public synchronized void invalidate(String key, boolean fullExpire) {
Entry entry = get(key);
if (entry != null) {
entry.softTtl = 0;
if (fullExpire) {
entry.ttl = 0;
}
put(key, entry);
}
}
/**
* Puts the entry with the specified key into the cache.
*/
#Override
public synchronized void put(String key, Entry entry) {
pruneIfNeeded(entry.data.length);
File file = getFileForKey(key);
try {
BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
CacheHeader e = new CacheHeader(key, entry);
e.writeHeader(fos);
fos.write(entry.data);
fos.close();
putEntry(key, e);
return;
} catch (IOException e) {
}
boolean deleted = file.delete();
if (!deleted) {
VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
}
}
/**
* Removes the specified key from the cache if it exists.
*/
#Override
public synchronized void remove(String key) {
boolean deleted = getFileForKey(key).delete();
removeEntry(key);
if (!deleted) {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
key, getFilenameForKey(key));
}
}
/**
* Creates a pseudo-unique filename for the specified cache key.
* #param key The key to generate a file name for.
* #return A pseudo-unique filename.
*/
private String getFilenameForKey(String key) {
int firstHalfLength = key.length() / 2;
String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
return localFilename;
}
/**
* Returns a file object for the given cache key.
*/
public File getFileForKey(String key) {
return new File(mRootDirectory, getFilenameForKey(key));
}
/**
* Prunes the cache to fit the amount of bytes specified.
* #param neededSpace The amount of bytes we are trying to fit into the cache.
*/
private void pruneIfNeeded(int neededSpace) {
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
return;
}
if (VolleyLog.DEBUG) {
VolleyLog.v("Pruning old cache entries.");
}
long before = mTotalSize;
int prunedFiles = 0;
long startTime = SystemClock.elapsedRealtime();
Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, CacheHeader> entry = iterator.next();
CacheHeader e = entry.getValue();
boolean deleted = getFileForKey(e.key).delete();
if (deleted) {
mTotalSize -= e.size;
} else {
VolleyLog.d("Could not delete cache entry for key=%s, filename=%s",
e.key, getFilenameForKey(e.key));
}
iterator.remove();
prunedFiles++;
if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
break;
}
}
if (VolleyLog.DEBUG) {
VolleyLog.v("pruned %d files, %d bytes, %d ms",
prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
}
}
/**
* Puts the entry with the specified key into the cache.
* #param key The key to identify the entry by.
* #param entry The entry to cache.
*/
private void putEntry(String key, CacheHeader entry) {
if (!mEntries.containsKey(key)) {
mTotalSize += entry.size;
} else {
CacheHeader oldEntry = mEntries.get(key);
mTotalSize += (entry.size - oldEntry.size);
}
mEntries.put(key, entry);
}
/**
* Removes the entry identified by 'key' from the cache.
*/
private void removeEntry(String key) {
CacheHeader entry = mEntries.get(key);
if (entry != null) {
mTotalSize -= entry.size;
mEntries.remove(key);
}
}
/**
* Reads the contents of an InputStream into a byte[].
* */
private static byte[] streamToBytes(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
int count;
int pos = 0;
while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) {
pos += count;
}
if (pos != length) {
throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
}
return bytes;
}
/**
* Handles holding onto the cache headers for an entry.
*/
// Visible for testing.
static class CacheHeader {
/** The size of the data identified by this CacheHeader. (This is not
* serialized to disk. */
public long size;
/** The key that identifies the cache entry. */
public String key;
/** ETag for cache coherence. */
public String etag;
/** Date of this response as reported by the server. */
public long serverDate;
/** TTL for this record. */
public long ttl;
/** Soft TTL for this record. */
public long softTtl;
/** Headers from the response resulting in this cache entry. */
public Map<String, String> responseHeaders;
private CacheHeader() { }
/**
* Instantiates a new CacheHeader object
* #param key The key that identifies the cache entry
* #param entry The cache entry.
*/
public CacheHeader(String key, Entry entry) {
this.key = key;
this.size = entry.data.length;
this.etag = entry.etag;
this.serverDate = entry.serverDate;
this.ttl = entry.ttl;
this.softTtl = entry.softTtl;
this.responseHeaders = entry.responseHeaders;
}
/**
* Reads the header off of an InputStream and returns a CacheHeader object.
* #param is The InputStream to read from.
* #throws IOException
*/
public static CacheHeader readHeader(InputStream is) throws IOException {
CacheHeader entry = new CacheHeader();
int magic = readInt(is);
if (magic != CACHE_MAGIC) {
// don't bother deleting, it'll get pruned eventually
throw new IOException();
}
entry.key = readString(is);
entry.etag = readString(is);
if (entry.etag.equals("")) {
entry.etag = null;
}
entry.serverDate = readLong(is);
entry.ttl = readLong(is);
entry.softTtl = readLong(is);
entry.responseHeaders = readStringStringMap(is);
return entry;
}
/**
* Creates a cache entry for the specified data.
*/
public Entry toCacheEntry(byte[] data) {
Entry e = new Entry();
e.data = data;
e.etag = etag;
e.serverDate = serverDate;
e.ttl = ttl;
e.softTtl = softTtl;
e.responseHeaders = responseHeaders;
return e;
}
/**
* Writes the contents of this CacheHeader to the specified OutputStream.
*/
public boolean writeHeader(OutputStream os) {
try {
writeInt(os, CACHE_MAGIC);
writeString(os, key);
writeString(os, etag == null ? "" : etag);
writeLong(os, serverDate);
writeLong(os, ttl);
writeLong(os, softTtl);
writeStringStringMap(responseHeaders, os);
os.flush();
return true;
} catch (IOException e) {
VolleyLog.d("%s", e.toString());
return false;
}
}
}
private static class CountingInputStream extends FilterInputStream {
private int bytesRead = 0;
private CountingInputStream(InputStream in) {
super(in);
}
#Override
public int read() throws IOException {
int result = super.read();
if (result != -1) {
bytesRead++;
}
return result;
}
#Override
public int read(byte[] buffer, int offset, int count) throws IOException {
int result = super.read(buffer, offset, count);
if (result != -1) {
bytesRead += result;
}
return result;
}
}
/*
* Homebrewed simple serialization system used for reading and writing cache
* headers on disk. Once upon a time, this used the standard Java
* Object{Input,Output}Stream, but the default implementation relies heavily
* on reflection (even for standard types) and generates a ton of garbage.
*/
/**
* Simple wrapper around {#link InputStream#read()} that throws EOFException
* instead of returning -1.
*/
private static int read(InputStream is) throws IOException {
int b = is.read();
if (b == -1) {
throw new EOFException();
}
return b;
}
static void writeInt(OutputStream os, int n) throws IOException {
os.write((n >> 0) & 0xff);
os.write((n >> 8) & 0xff);
os.write((n >> 16) & 0xff);
os.write((n >> 24) & 0xff);
}
static int readInt(InputStream is) throws IOException {
int n = 0;
n |= (read(is) << 0);
n |= (read(is) << 8);
n |= (read(is) << 16);
n |= (read(is) << 24);
return n;
}
static void writeLong(OutputStream os, long n) throws IOException {
os.write((byte)(n >>> 0));
os.write((byte)(n >>> 8));
os.write((byte)(n >>> 16));
os.write((byte)(n >>> 24));
os.write((byte)(n >>> 32));
os.write((byte)(n >>> 40));
os.write((byte)(n >>> 48));
os.write((byte)(n >>> 56));
}
static long readLong(InputStream is) throws IOException {
long n = 0;
n |= ((read(is) & 0xFFL) << 0);
n |= ((read(is) & 0xFFL) << 8);
n |= ((read(is) & 0xFFL) << 16);
n |= ((read(is) & 0xFFL) << 24);
n |= ((read(is) & 0xFFL) << 32);
n |= ((read(is) & 0xFFL) << 40);
n |= ((read(is) & 0xFFL) << 48);
n |= ((read(is) & 0xFFL) << 56);
return n;
}
static void writeString(OutputStream os, String s) throws IOException {
byte[] b = s.getBytes("UTF-8");
writeLong(os, b.length);
os.write(b, 0, b.length);
}
static String readString(InputStream is) throws IOException {
int n = (int) readLong(is);
byte[] b = streamToBytes(is, n);
return new String(b, "UTF-8");
}
static void writeStringStringMap(Map<String, String> map, OutputStream os) throws IOException {
if (map != null) {
writeInt(os, map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
writeString(os, entry.getKey());
writeString(os, entry.getValue());
}
} else {
writeInt(os, 0);
}
}
static Map<String, String> readStringStringMap(InputStream is) throws IOException {
int size = readInt(is);
Map<String, String> result = (size == 0)
? Collections.<String, String>emptyMap()
: new HashMap<String, String>(size);
for (int i = 0; i < size; i++) {
String key = readString(is).intern();
String value = readString(is).intern();
result.put(key, value);
}
return result;
}
}
Yes, the way DiskBasedCache works it needs to open all the files in initialize(). Which is simply.... not a good idea :-(
You need to make a different implementation that doesent open all the files at startup.
Take a copy of DiskBasedCache and change initialize() to
#Override
public synchronized void initialize() {
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
}
}
And change get() so it makes an additional check for if the file exists on the file system, like
#Override
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
File file = getFileForKey(key);
if (entry == null && !file.exists()) { // EXTRA CHECK
// if the entry does not exist, return.
VolleyLog.d("DrVolleyDiskBasedCache miss for " + key);
return null;
}
...
I use this approach in https://play.google.com/store/apps/details?id=dk.dr.radio and it works fine - its robustness have been tested by ~300000 users :-)
You can download a full version of the file from https://code.google.com/p/dr-radio-android/source/browse/trunk/DRRadiov3/src/dk/dr/radio/net/volley/DrDiskBasedCache.java (you'll have to delete some DR Radio specific stuff)
In the streamToBytes(), first it will new bytes by the cache file length, does your cache file was too large than application maximum heap size ?
private static byte[] streamToBytes(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
...
}
public synchronized Entry get(String key) {
CacheHeader entry = mEntries.get(key);
File file = getFileForKey(key);
byte[] data = streamToBytes(..., file.length());
}
If you want to clear the cache, you could keep the DiskBasedCache reference, after clear time's came, use ClearCacheRequest and pass that cache instance in :
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
DiskBasedCache cache = new DiskBasedCache(cacheDir);
RequestQueue queue = new RequestQueue(cache, network);
queue.start();
// clear all volley caches.
queue.add(new ClearCacheRequest(cache, null));
this way will clear all caches, so I suggest you use it carefully. of course, you can doing conditional check, just iterating the cacheDir files, estimate which was too large then remove it.
for (File cacheFile : cacheDir.listFiles()) {
if (cacheFile.isFile() && cacheFile.length() > 10000000) cacheFile.delete();
}
Volley wasn't design as a big data cache solution, it's common request cache, don't storing large data anytime.
------------- Update at 2014-07-17 -------------
In fact, clear all caches is final way, also isn't wise way, we should suppressing these large request use cache when we sure it would be, and if not sure? we still can determine the response data size whether large or not, then call setShouldCache(false) to disable it.
public class TheRequest extends Request {
#Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
// if response data was too large, disable caching is still time.
if (response.data.length > 10000) setShouldCache(false);
...
}
}
My initial thought was to use the DiskLruCache written by Jake Wharton by writing a com.android.volley.Cache wrapper over it.
But I finally implemented a singleton pattern for the Volley, combined with the cache creation in an AsyncTask called from the Application context
public static synchronized VolleyClient getInstance(Context context)
{
if (mInstance == null)
{
mInstance = new VolleyClient(context);
}
return mInstance;
}
private VolleyClient(Context context)
{
this.context = context;
VolleyCacheInitializer volleyCacheInitializer = new VolleyCacheInitializer();
volleyCacheInitializer.execute();
}
private class VolleyCacheInitializer extends AsyncTask<Void, Void, Boolean>
{
#Override
protected Boolean doInBackground(Void... params)
{
// Instantiate the cache with 50MB Cache Size
Cache diskBasedCache = new DiskBasedCache(context.getCacheDir(), 50 * 1024 * 1024);
// Instantiate the RequestQueue with the cache and network.
mRequestQueue = new RequestQueue(diskBasedCache, network);
// Start the queue which calls the DiskBasedCache.initialize()
mRequestQueue.start();
return true;
}
#Override
protected void onPostExecute(Boolean aBoolean)
{
super.onPostExecute(aBoolean);
if(aBoolean)
Log.d(TAG, "Volley request queue initialized");
else
Log.d(TAG, "Volley request queue initialization failed");
}
}
Inside MyApplication class
#Override
public void onCreate()
{
super.onCreate();
// Initialize an application level volley request queue
VolleyClient volleyHttpClient = VolleyClient.getInstance(this);
}

How to convert the WAV/OGG file to FLAC file in Android?

EDIT: Incorporated the changed uv001's answer.
I can only find that ICS 4.0 support decoding of FLAC, but encode. I need some encoder to convert wav to flac, but currenty I can't find it. I find there is a jFlac avaible , but I don't know how to use this library, just simply convert the files.
Could anyone give me a hand on it?
Today, I just some idea by myself, with using the JavaFlacEncoder.
and it works for certain bitrates of WAV.
I changed the value into a hard coding value in which it is working now.
/*
* Copyright (C) 2010 Preston Lacey http://javaflacencoder.sourceforge.net/
* All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package javaFlacEncoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* FLAC_FileEncoder is a class to encode an input wav File to an output Flac
* file. It allows the EncodingConfiguration to be set only once, prior to
* encoding the entire File.
*
* #author Preston Lacey
* #author Bo Tan (Temple)
*/
public class FLAC_FileEncoder {
/** Maximum number of bytes to read from file at once */
private static final int MAX_READ = 16384;
/** Status enum for encode result */
public enum Status {
/** Unknown State. */
UNKNOWN,
/** Everything went well */
FULL_ENCODE,
/** Something unspecified went wrong...*/
GENERAL_ERROR,
/** internal error is something that went haywire that was discovered
* due to internal sanity checks. A problem in API. */
INTERNAL_ERROR,
/** File given was not able to be read */
UNSUPPORTED_FILE,
/** Generic file IO Error */
FILE_IO_ERROR,
/** Sample size unsupported */
UNSUPPORTED_SAMPLE_SIZE,
/** Error with output file */
OUTPUT_FILE_ERROR,
/** No errors found. */
OK
}
FLACEncoder flac = null;
StreamConfiguration sc = null;
EncodingConfiguration ec = null;
File outFile = null;
int lastTotalSamples = 0;
boolean useThreads;
/**
* Constructor creates a FLAC_FileEncoder object with default
* StreamConfiguration configuration and default EncodingConfiguration.
* Thread use defaults to true.
*/
public FLAC_FileEncoder() {
flac = new FLACEncoder();
sc = new StreamConfiguration();
ec = new EncodingConfiguration();
useThreads = true;
}
/**
* Specify whether to use multiple threads or not.
* #param val true to use threads, false otherwise.
*/
public void useThreads(boolean val) {
useThreads = val;
}
private void adjustConfigurations(){//(AudioFormat format) {
int sampleRate = 16000;//(int)format.getSampleRate();
int sampleSize = 16; //(int)format.getSampleSizeInBits();
int channels =1;// (int)format.getChannels();
//int blockSize = sc.getMaxBlockSize();
/*sc = new StreamConfiguration(channels, blockSize, blockSize,
sampleRate, sampleSize);*/
sc.setSampleRate(sampleRate);
sc.setBitsPerSample(sampleSize);
sc.setChannelCount(channels);
}
/**
* Set the stream configuration for this encoder to use. Note that the audio
* characteristics(number of channels, sample rate, and sample size), will
* be set to match the input file at encode time, so needn't be set in the
* given StreamConfiguration object.
*
* #param config StreamConfiguration to use for encoding.
*/
public void setStreamConfig(StreamConfiguration config) {sc = config; }
/**
* Set the EncodingConfiguration to use for encoding.
* #param config EncodingConfiguration to use.
*/
public void setEncodingConfig(EncodingConfiguration config){ec = config;}
private Status openStream() {
Status status = Status.OK;
boolean result = flac.setStreamConfiguration(sc);
result = result & flac.setEncodingConfiguration(ec);
if( !result)
status = Status.INTERNAL_ERROR;
else {
FLACFileOutputStream fout = null;
try {
fout = new FLACFileOutputStream(outFile.getPath());
} catch(IOException e) {
status = Status.OUTPUT_FILE_ERROR;
e.printStackTrace();
}
if( status == Status.OK) {
flac.setOutputStream(fout);
try {
flac.openFLACStream();
}catch(IOException e) {
status = Status.INTERNAL_ERROR;
}
}
else
status = Status.OUTPUT_FILE_ERROR;
}
return status;
}
/**
* Encode the given input wav file to an output file.
*
* #param inputFile Input wav file to encode.
* #param outputFile Output file to write FLAC stream to. If file exists, it
* will be overwritten without prompting.
*
* #return Status flag for encode
*/
public Status encode(File inputFile, File outputFile) {
Status status = Status.FULL_ENCODE;
this.outFile = outputFile;
//take file and initial configuration.
//open file
// AudioInputStream sin = null;
// AudioFormat format = null;
// //File inputFile = new File("encoderTest.wav");
// try {
// sin = AudioSystem.getAudioInputStream(inputFile);
// }catch(IOException e) {
// status = Status.FILE_IO_ERROR;
// }catch (UnsupportedAudioFileException e) {
// status = Status.UNSUPPORTED_FILE;
// }finally {
// if(status != Status.FULL_ENCODE)
// return status;
// }
FileInputStream sin=null;
try {
sin = new FileInputStream(inputFile);
} catch (FileNotFoundException e1) {
status = Status.FILE_IO_ERROR;
e1.printStackTrace();
}finally {
if (status != Status.FULL_ENCODE)
return status;
}
try {
// format = sin.getFormat();
//sanitize and optimize configurations
adjustConfigurations(); //adjustConfigurations(format);
//open stream
openStream();
int frameSize = 2;//format.getFrameSize();
int sampleSize = 16;//format.getSampleSizeInBits();
int bytesPerSample = sampleSize/8;
if(sampleSize %8 != 0) {
//end processing now
Exception newEx = new Exception(Status.UNSUPPORTED_SAMPLE_SIZE.name());
throw newEx;
}
int channels =1;// format.getChannels();
boolean bigEndian =false;// format.isBigEndian();
byte[] samplesIn = new byte[(int)MAX_READ];
int samplesRead;
int framesRead;
int[] sampleData = new int[MAX_READ*channels/frameSize];
int blockSize = sc.getMaxBlockSize();
int unencodedSamples = 0;
int totalSamples = 0;
while((samplesRead = sin.read(samplesIn, 0, MAX_READ)) != -1) {
//System.err.println("Read: " + read);
framesRead = samplesRead/(frameSize);
if(bigEndian) {
for(int i = 0; i < framesRead*channels; i++) {
int lower8Mask = 255;
int temp = 0;
int totalTemp = 0;
for(int x = bytesPerSample-1; x >= 0; x++) {
int upShift = 8*x;
if(x == 0)//don't mask...we want sign
temp = ((samplesIn[bytesPerSample*i+x]) << upShift);
else
temp = ((samplesIn[bytesPerSample*i+x] & lower8Mask) << upShift);
totalTemp = totalTemp | temp;
}
sampleData[i] = totalTemp;
}
}
else {
for(int i = 0; i < framesRead*channels; i++) {
int lower8Mask = 255;
int temp = 0;
int totalTemp = 0;
for(int x = 0; x < bytesPerSample; x++) {
int upShift = 8*x;
if(x == bytesPerSample-1)//don't mask...we want sign
temp = ((samplesIn[bytesPerSample*i+x]) << upShift);
else
temp = ((samplesIn[bytesPerSample*i+x] & lower8Mask) << upShift);
totalTemp = totalTemp | temp;
}
sampleData[i] = totalTemp;
}
}
if(framesRead > 0) {
flac.addSamples(sampleData, framesRead);
unencodedSamples += framesRead;
}
//if(unencodedSamples > blockSize*100) {
if(useThreads)//Thread.yield();//
unencodedSamples -= flac.t_encodeSamples(unencodedSamples, false, flac.getThreadCount());
else
unencodedSamples -= flac.encodeSamples(unencodedSamples, false);
totalSamples += unencodedSamples;
//unencodedSamples = 0;
//}
//System.err.println("read : "+ samplesRead);
}
totalSamples += unencodedSamples;
if(useThreads)
unencodedSamples -= flac.t_encodeSamples(unencodedSamples, true, flac.getThreadCount());
else
unencodedSamples -= flac.encodeSamples(unencodedSamples, true);
//unencodedSamples = 0;
lastTotalSamples = totalSamples;
}
catch(IOException e) {
status = Status.FILE_IO_ERROR;
}
catch(Exception e) {
status = Status.GENERAL_ERROR;
String message = e.getMessage();
if(message == null) {
e.printStackTrace();
}
else if(message.equals(Status.UNSUPPORTED_SAMPLE_SIZE.name()))
status = Status.UNSUPPORTED_SAMPLE_SIZE;
}
//System.err.print("LastTotalSamples: "+lastTotalSamples);
return status;
}
/**
* Get the total number of samples encoded in last encode. This is here
* primarily for use as a sanity check during debugging.
*
* #return Total number of samples encoded in last encode attempt.
*/
public int getLastTotalSamplesEncoded() {
return this.lastTotalSamples;
}
}
Temple, Thanks for the post and the response with the attached code for FLAC conversion. It works as you described with one minor issue.
The FLACFileOutputStream (fout) and the FileInputStream (sin) are not closed at the end of the function. Please update your post/code so others can benefit as well.
finally {
try {
if (sin != null) sin.close();
if (fout != null) fout.close();
} catch (IOException e) {}
}
PS: FLACFileOutputStream fout will need to be promoted to a class variable for this to work.
Change the following line in your code
unencodedSamples -= flac.t_encodeSamples(unencodedSamples, true);
to
unencodedSamples -= flac.t_encodeSamples(unencodedSamples, true,flac.getThreadCount());

Categories

Resources