diff --git a/.gitmodules b/.gitmodules
index a65ddbf..126fab3 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,8 +1,8 @@
[submodule "externals/x-ray"]
path = externals/x-ray
- url = git@github.com:inckie/x-ray.git
+ url = https://github.com/inckie/x-ray.git
shallow = true
[submodule "externals/glass-enterprise-samples"]
path = externals/glass-enterprise-samples
- url = git@github.com:inckie/glass-enterprise-samples.git
+ url = https://github.com/inckie/glass-enterprise-samples.git
branch = feat/update-project
diff --git a/glass-ee/src/main/AndroidManifest.xml b/glass-ee/src/main/AndroidManifest.xml
index 0000140..c6dbf1b 100644
--- a/glass-ee/src/main/AndroidManifest.xml
+++ b/glass-ee/src/main/AndroidManifest.xml
@@ -23,6 +23,10 @@
tools:ignore="MockLocation,ProtectedPermissions" />
+
+
+
+
diff --git a/glass-shared/src/main/java/com/damn/glass/shared/rpc/WiFiClient.kt b/glass-shared/src/main/java/com/damn/glass/shared/rpc/WiFiClient.kt
index d6488b4..53fa0fa 100644
--- a/glass-shared/src/main/java/com/damn/glass/shared/rpc/WiFiClient.kt
+++ b/glass-shared/src/main/java/com/damn/glass/shared/rpc/WiFiClient.kt
@@ -92,9 +92,9 @@ class WiFiClient(private val hostIP: String? = null) : IRPCClient {
return
}
}
- while (inputStream.available() > 0) {
+ while (serializer.isReady || inputStream.available() > 0) {
val message = serializer.readMessage()
- if (message.service == null) {
+ if (message?.service == null) {
return
}
handler.onDataReceived(message)
diff --git a/glass-xe/src/main/AndroidManifest.xml b/glass-xe/src/main/AndroidManifest.xml
index a7f6298..c31df3b 100644
--- a/glass-xe/src/main/AndroidManifest.xml
+++ b/glass-xe/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
diff --git a/glass-xe/src/main/java/com/damn/anotherglass/glass/host/bluetooth/BluetoothClient.java b/glass-xe/src/main/java/com/damn/anotherglass/glass/host/bluetooth/BluetoothClient.java
index 3337113..16370f4 100644
--- a/glass-xe/src/main/java/com/damn/anotherglass/glass/host/bluetooth/BluetoothClient.java
+++ b/glass-xe/src/main/java/com/damn/anotherglass/glass/host/bluetooth/BluetoothClient.java
@@ -20,6 +20,7 @@
import com.damn.anotherglass.shared.utility.DisconnectReceiver;
import com.damn.anotherglass.shared.utility.Sleep;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
@@ -48,6 +49,7 @@ public Connection(Context context, RPCMessageListener listener) {
mHandler = new RPCHandler(listener);
}
+ @SuppressLint("MissingPermission")
@Override
public void run() {
try {
@@ -56,6 +58,7 @@ public void run() {
// return;
// }
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
+ bt.cancelDiscovery();
Set pairedDevices = bt.getBondedDevices();
if (null == pairedDevices || pairedDevices.isEmpty()) {
Log.e(TAG, "No paired devices found, aborting the connection");
@@ -68,6 +71,8 @@ public void run() {
break;
}
}
+ } catch (IOException e) {
+ Log.w(TAG, "Connection lost: " + e.getMessage());
} catch (Exception e) {
Log.e(TAG, "Connection exception", e);
} finally {
@@ -97,35 +102,52 @@ public void shutdown() {
@SuppressLint("MissingPermission")
private void runLoop(@NonNull BluetoothDevice device) throws Exception {
- try (BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(Constants.uuid)) {
- socket.connect();
+ BluetoothSocket socket = null;
+ try {
+ try {
+ socket = device.createInsecureRfcommSocketToServiceRecord(Constants.uuid);
+ socket.connect();
+ } catch (Exception e) {
+ Log.w(TAG, "Standard connection failed, trying fallback: " + e.getMessage());
+ if (socket != null)
+ socket.close();
+ Sleep.sleep(500);
+ socket = (BluetoothSocket) device.getClass().getMethod("createRfcommSocket", int.class).invoke(device, 1);
+ socket.connect();
+ }
Log.i(TAG, "Client has connected to " + device.getName());
AtomicBoolean active = new AtomicBoolean(true);
- try (DisconnectReceiver ignored = new DisconnectReceiver(mContext, device, () -> active.getAndSet(false))) {
- try (OutputStream outputStream = socket.getOutputStream();
- InputStream inputStream = socket.getInputStream()) {
- IMessageSerializer serializer = SerializerProvider.getSerializer(inputStream, outputStream);
- mConnected = true;
- mHandler.onConnectionStarted(device.getName());
- while (active.get()) {
- while (null != mQueue.peek()) {
- RPCMessage message = mQueue.take();
- serializer.writeMessage(message);
- Log.v(TAG, "Message " + message.service + "/" + message.type + " was sent");
- if (null == message.service) {
- Log.d(TAG, "Shutdown requested");
- return;
- }
+ try (DisconnectReceiver ignored = new DisconnectReceiver(mContext, device, () -> active.getAndSet(false));
+ OutputStream outputStream = socket.getOutputStream();
+ InputStream inputStream = socket.getInputStream()) {
+ IMessageSerializer serializer = SerializerProvider.getSerializer(inputStream, outputStream);
+ mConnected = true;
+ mHandler.onConnectionStarted(device.getName());
+ while (active.get()) {
+ while (null != mQueue.peek()) {
+ RPCMessage message = mQueue.take();
+ serializer.writeMessage(message);
+ Log.v(TAG, "Message " + message.service + "/" + message.type + " was sent");
+ if (null == message.service) {
+ Log.d(TAG, "Shutdown requested");
+ return;
}
- while (inputStream.available() > 0) {
- RPCMessage objectReceived = serializer.readMessage();
- mHandler.onDataReceived(objectReceived);
- Log.v(TAG, "Message " + objectReceived.service + "/" + objectReceived.type + " was received");
+ }
+ while (serializer.isReady() || inputStream.available() > 0) {
+ RPCMessage objectReceived = serializer.readMessage();
+ if (null == objectReceived || null == objectReceived.service) {
+ Log.d(TAG, "Remote shutdown or connection lost");
+ return;
}
- Sleep.sleep(100);
+ mHandler.onDataReceived(objectReceived);
+ Log.v(TAG, "Message " + objectReceived.service + "/" + objectReceived.type + " was received");
}
+ Sleep.sleep(100);
}
}
+ } finally {
+ if (socket != null)
+ socket.close();
}
}
diff --git a/glass-xe/src/main/java/com/damn/anotherglass/glass/host/notifications/NotificationViewBuilder.java b/glass-xe/src/main/java/com/damn/anotherglass/glass/host/notifications/NotificationViewBuilder.java
index 266e709..60c641f 100644
--- a/glass-xe/src/main/java/com/damn/anotherglass/glass/host/notifications/NotificationViewBuilder.java
+++ b/glass-xe/src/main/java/com/damn/anotherglass/glass/host/notifications/NotificationViewBuilder.java
@@ -14,16 +14,78 @@
public class NotificationViewBuilder {
public static CardBuilder buildView(Context context, NotificationData data) {
- // basic
- CardBuilder builder = new CardBuilder(context, CardBuilder.Layout.AUTHOR)
- .setHeading(data.title)
- .setSubheading(data.packageName) // todo: should be application name
- .setText(data.text);
+ // basic layout selection
+ CardBuilder.Layout layout;
+ if (null != data.image && null != data.image.bytes) {
+ layout = CardBuilder.Layout.CAPTION;
+ } else if (null != data.icon && null != data.icon.bytes) {
+ layout = CardBuilder.Layout.COLUMNS;
+ } else if (null != data.messages && !data.messages.isEmpty()) {
+ layout = CardBuilder.Layout.COLUMNS;
+ } else {
+ layout = CardBuilder.Layout.TEXT;
+ }
+
+ CardBuilder builder = new CardBuilder(context, layout);
+
+ // handle text / conversation
+ StringBuilder text = new StringBuilder();
+ if (null != data.conversationTitle) {
+ text.append(data.conversationTitle).append("\n");
+ if (null != data.title && !data.isGroupConversation) {
+ // For 1-on-1, title is redundant if conversationTitle is present
+ } else if (null != data.title) {
+ text.append(data.title).append(": ");
+ }
+ } else if (null != data.title) {
+ text.append(data.title).append("\n");
+ }
- // icon
+ if (null != data.messages && !data.messages.isEmpty()) {
+ for (NotificationData.Message msg : data.messages) {
+ if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
+ text.append("\n");
+ }
+ if (null != msg.sender && (data.isGroupConversation || !msg.sender.equals(data.title))) {
+ text.append(msg.sender).append(": ");
+ }
+ text.append(msg.text);
+ }
+ } else if (null != data.text) {
+ if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
+ text.append("\n");
+ }
+ text.append(data.text);
+ }
+ builder.setText(text.toString());
+ builder.setFootnote(null != data.appName ? data.appName : data.packageName);
+
+ // icon (App Icon)
if (null != data.icon && null != data.icon.bytes) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data.icon.bytes, 0, data.icon.bytes.length);
builder.setIcon(bitmap);
+
+ // In COLUMNS layout, if we have no images but have an icon,
+ // putting the icon in addImage makes it a nice large side-image
+ if (layout == CardBuilder.Layout.COLUMNS && (null == data.messages || data.messages.isEmpty())) {
+ builder.addImage(bitmap);
+ }
+ }
+
+ // image (Background or Mosaic)
+ if (null != data.image && null != data.image.bytes) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(data.image.bytes, 0, data.image.bytes.length);
+ builder.addImage(bitmap);
+ } else if (null != data.messages && !data.messages.isEmpty()) {
+ // Mosaic of senders
+ int added = 0;
+ for (NotificationData.Message msg : data.messages) {
+ if (null != msg.senderIcon && null != msg.senderIcon.bytes) {
+ Bitmap bitmap = BitmapFactory.decodeByteArray(msg.senderIcon.bytes, 0, msg.senderIcon.bytes.length);
+ builder.addImage(bitmap);
+ if (++added >= 5) break; // Glass mosaic limit
+ }
+ }
}
// time
diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties
new file mode 100644
index 0000000..5c34300
--- /dev/null
+++ b/gradle/gradle-daemon-jvm.properties
@@ -0,0 +1,13 @@
+#This file is generated by updateDaemonJvm
+toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
+toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
+toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
+toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
+toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect
+toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect
+toolchainVendor=JETBRAINS
+toolchainVersion=21
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index ad3b6d4..b031e2d 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
diff --git a/mobile/src/main/java/com/damn/anotherglass/core/BluetoothHost.java b/mobile/src/main/java/com/damn/anotherglass/core/BluetoothHost.java
index 8192bf2..30eac43 100644
--- a/mobile/src/main/java/com/damn/anotherglass/core/BluetoothHost.java
+++ b/mobile/src/main/java/com/damn/anotherglass/core/BluetoothHost.java
@@ -151,9 +151,9 @@ private void runLoop(BluetoothSocket socket) throws Exception {
OutputStream outputStream = socket.getOutputStream()) {
IMessageSerializer serializer = SerializerProvider.getSerializer(inputStream, outputStream);
while (mActive) {
- while (inputStream.available() > 0) {
+ while (serializer.isReady() || inputStream.available() > 0) {
RPCMessage objectReceived = serializer.readMessage();
- if (null == objectReceived.service)
+ if (null == objectReceived || null == objectReceived.service)
return; // shutdown requested
mHandler.onDataReceived(objectReceived);
}
diff --git a/mobile/src/main/java/com/damn/anotherglass/core/WiFiHost.kt b/mobile/src/main/java/com/damn/anotherglass/core/WiFiHost.kt
index f21cd42..dc4e6db 100644
--- a/mobile/src/main/java/com/damn/anotherglass/core/WiFiHost.kt
+++ b/mobile/src/main/java/com/damn/anotherglass/core/WiFiHost.kt
@@ -110,9 +110,9 @@ class WiFiHost(listener: RPCMessageListener) : IRPCHost {
return // disconnect requested
}
}
- while (mActive && inputStream.available() > 0) {
+ while (mActive && (serializer.isReady || inputStream.available() > 0)) {
val message = serializer.readMessage()
- if (message.service == null) {
+ if (message?.service == null) {
return // client disconnected
}
mHandler.onDataReceived(message)
diff --git a/mobile/src/main/java/com/damn/anotherglass/extensions/notifications/Converter.kt b/mobile/src/main/java/com/damn/anotherglass/extensions/notifications/Converter.kt
index 4a600a7..1a7695c 100644
--- a/mobile/src/main/java/com/damn/anotherglass/extensions/notifications/Converter.kt
+++ b/mobile/src/main/java/com/damn/anotherglass/extensions/notifications/Converter.kt
@@ -6,6 +6,8 @@ import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Bundle
import android.service.notification.StatusBarNotification
import androidx.core.graphics.createBitmap
import com.applicaster.xray.core.Logger
@@ -16,13 +18,14 @@ import com.damn.anotherglass.utility.toPngBinaryData
object Converter {
private const val TAG = "IconConverter"
private val log = ALog(Logger.get(TAG))
+
fun convert(
context: Context,
- acton: NotificationData.Action,
+ action: NotificationData.Action,
sbn: StatusBarNotification
): NotificationData {
val data = NotificationData()
- data.action = acton
+ data.action = action
// parse basic data
data.id = sbn.id
@@ -30,28 +33,104 @@ object Converter {
data.postedTime = sbn.postTime
data.isOngoing = sbn.isOngoing
- // todo: code below this point is not really needed for NotificationData.Action.Removed
+ val pm = context.packageManager
+ try {
+ val ai = pm.getApplicationInfo(data.packageName, 0)
+ data.appName = pm.getApplicationLabel(ai).toString()
+ } catch (e: Exception) {
+ data.appName = data.packageName
+ }
- // todo: extract app name
+ if (action == NotificationData.Action.Removed) return data
- // parse Notification data
val notification = sbn.notification
- data.title = notification.extras.getString(Notification.EXTRA_TITLE)
- data.text = notification.extras.getString(Notification.EXTRA_TEXT)
+ val extras = notification.extras
+
+ data.title = extras.getString(Notification.EXTRA_TITLE)
+ data.text = extras.getCharSequence(Notification.EXTRA_TEXT)?.toString()
+
+ // handle BigText
+ extras.getCharSequence(Notification.EXTRA_BIG_TEXT)?.let {
+ data.text = it.toString()
+ }
+
+ // handle InboxStyle
+ extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES)?.let { lines ->
+ if (lines.isNotEmpty()) {
+ data.text = lines.joinToString("\n")
+ }
+ }
+
+ // handle MessagingStyle (Manual Bundle Parsing for maximum compatibility and avoiding GDK compile issues)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE)?.let {
+ data.conversationTitle = it.toString()
+ }
+
+ // Check for group conversation (API 28+)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ data.isGroupConversation = extras.getBoolean("android.isGroupConversation")
+ }
+
+ val messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES)
+ if (messages != null) {
+ for (m in messages) {
+ if (m is Bundle) {
+ val msg = NotificationData.Message()
+ msg.text = m.getCharSequence("text")?.toString()
+ msg.time = m.getLong("time")
+
+ // Extract sender info (API 28+ Person or legacy String)
+ val senderPerson = m.get("sender_person")
+ var icon: android.graphics.drawable.Icon? = null
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && senderPerson is android.app.Person) {
+ msg.sender = senderPerson.name?.toString()
+ icon = senderPerson.icon
+ } else if (senderPerson is Bundle) {
+ msg.sender = senderPerson.getCharSequence("name")?.toString()
+ @Suppress("DEPRECATION")
+ icon = senderPerson.getParcelable("icon")
+ } else {
+ msg.sender = m.getCharSequence("sender")?.toString()
+ }
+
+ icon?.let {
+ try {
+ it.loadDrawable(context)?.let { drawable ->
+ msg.senderIcon = drawableToBitmap(drawable).toPngBinaryData()
+ }
+ } catch (e: Exception) {
+ log.e(TAG, "Failed to load person icon", e)
+ }
+ }
+ data.messages.add(msg)
+ }
+ }
+ }
+ }
- // todo: https://stackoverflow.com/questions/29363770/how-to-get-text-of-stacked-notifications-in-android/29364414
if (null != notification.tickerText) {
data.tickerText = notification.tickerText.toString()
}
+
try {
extractIcon(context, data, notification)
+ extractImage(data, notification)
} catch (e: Exception) {
- // todo: new Android version do not allow that, add required permission
- log.e(TAG, "Failed to extract icon from notification: " + e.message, e)
+ log.e(TAG, "Failed to extract icon or image from notification", e)
}
return data
}
+ private fun extractImage(data: NotificationData, notification: Notification) {
+ val extras = notification.extras
+ // Try BigPicture
+ (extras.getParcelable(Notification.EXTRA_PICTURE)
+ ?: extras.getParcelable("android.pictureIcon"))?.let {
+ data.image = it.toPngBinaryData()
+ }
+ }
+
private fun extractIcon(
context: Context,
data: NotificationData,
@@ -60,17 +139,20 @@ object Converter {
var icon = notification.getLargeIcon()
if (null == icon) icon = notification.smallIcon
if (null != icon) {
- val drawable = icon.loadDrawable(context)
- if (null != drawable) {
- val bitmap = drawableToBitmap(drawable)
- setIconData(data, bitmap)
+ try {
+ val drawable = icon.loadDrawable(context)
+ if (null != drawable) {
+ val bitmap = drawableToBitmap(drawable)
+ setIconData(data, bitmap)
+ }
+ } catch (e: Exception) {
+ log.e(TAG, "Failed to load icon drawable", e)
}
}
if (null != data.icon) return
+
+ @Suppress("DEPRECATION")
if (null != notification.largeIcon) setIconData(data, notification.largeIcon)
- if (null != data.icon) return
-
- // todo: retrieve default icon from the package
}
private fun setIconData(data: NotificationData, bitmap: Bitmap) {
@@ -84,7 +166,6 @@ object Converter {
}
}
val bitmap: Bitmap = if (drawable.intrinsicWidth <= 0 || drawable.intrinsicHeight <= 0) {
- // Single color bitmap will be created of 1x1 pixel
createBitmap(1, 1)
} else {
createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight)
diff --git a/settings.gradle b/settings.gradle
index 3cdf1d1..2648d9f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,6 @@
+plugins {
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
+}
include ':mobile', ':glass-xe', ':glass-ee', ':shared', ':glass-shared'
def xray = [
diff --git a/shared/src/main/java/com/damn/anotherglass/shared/notifications/NotificationData.java b/shared/src/main/java/com/damn/anotherglass/shared/notifications/NotificationData.java
index 5eae5e8..e216660 100644
--- a/shared/src/main/java/com/damn/anotherglass/shared/notifications/NotificationData.java
+++ b/shared/src/main/java/com/damn/anotherglass/shared/notifications/NotificationData.java
@@ -5,6 +5,8 @@
import com.damn.anotherglass.shared.BinaryData;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
public class NotificationData implements Serializable {
@@ -17,15 +19,29 @@ public enum DeliveryMode implements Serializable {
Sound // also turns on screen
}
+ public static class Message implements Serializable {
+ public String sender;
+ public String text;
+ public long time;
+ public BinaryData senderIcon;
+ }
+
@NonNull
public Action action;
public int id;
public String packageName;
+ public String appName;
public long postedTime;
public boolean isOngoing;
public String title;
public String text;
public String tickerText;
public BinaryData icon;
+ public BinaryData image;
public DeliveryMode deliveryMode;
+
+ // MessagingStyle / Conversation data
+ public String conversationTitle;
+ public boolean isGroupConversation;
+ public List messages = new ArrayList<>();
}
diff --git a/shared/src/main/java/com/damn/anotherglass/shared/rpc/IMessageSerializer.java b/shared/src/main/java/com/damn/anotherglass/shared/rpc/IMessageSerializer.java
index 6fd20b8..14575e7 100644
--- a/shared/src/main/java/com/damn/anotherglass/shared/rpc/IMessageSerializer.java
+++ b/shared/src/main/java/com/damn/anotherglass/shared/rpc/IMessageSerializer.java
@@ -3,4 +3,5 @@
public interface IMessageSerializer {
void writeMessage(RPCMessage message) throws Exception;
RPCMessage readMessage() throws Exception;
+ boolean isReady() throws Exception;
}
\ No newline at end of file
diff --git a/shared/src/main/java/com/damn/anotherglass/shared/rpc/JsonMessageSerializer.kt b/shared/src/main/java/com/damn/anotherglass/shared/rpc/JsonMessageSerializer.kt
index 73e4ca8..fdf83a8 100644
--- a/shared/src/main/java/com/damn/anotherglass/shared/rpc/JsonMessageSerializer.kt
+++ b/shared/src/main/java/com/damn/anotherglass/shared/rpc/JsonMessageSerializer.kt
@@ -59,6 +59,11 @@ internal class JsonMessageSerializer(inputStream: InputStream, outputStream: Out
}
}
+ @Throws(Exception::class)
+ override fun isReady(): Boolean {
+ return reader.ready()
+ }
+
private class RPCMessageDeserializer : JsonDeserializer {
@Throws(JsonParseException::class)
override fun deserialize(
diff --git a/shared/src/main/java/com/damn/anotherglass/shared/rpc/ObjectMessageSerializer.java b/shared/src/main/java/com/damn/anotherglass/shared/rpc/ObjectMessageSerializer.java
index 36394d2..af56236 100644
--- a/shared/src/main/java/com/damn/anotherglass/shared/rpc/ObjectMessageSerializer.java
+++ b/shared/src/main/java/com/damn/anotherglass/shared/rpc/ObjectMessageSerializer.java
@@ -26,4 +26,9 @@ public void writeMessage(RPCMessage message) throws Exception {
public RPCMessage readMessage() throws Exception {
return (RPCMessage) ois.readObject();
}
+
+ @Override
+ public boolean isReady() throws Exception {
+ return ois.available() > 0;
+ }
}