Update maps, rework MCS

MCS rework related to #23 and #24
This commit is contained in:
mar-v-in 2015-08-04 13:05:47 +02:00
parent 61ede98580
commit 331813ce3c
17 changed files with 829 additions and 243 deletions

2
extern/GmsApi vendored

@ -1 +1 @@
Subproject commit f77b09dc0c3c750f7c99d901b6e5ced5f17d9465 Subproject commit 0890bf454651e90274949af9dca09fbcfbf50d36

View File

@ -67,6 +67,7 @@
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" /> <uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.google.android.c2dm.permission.SEND" /> <uses-permission android:name="com.google.android.c2dm.permission.SEND" />
<uses-permission android:name="org.microg.gms.STATUS_BROADCAST" /> <uses-permission android:name="org.microg.gms.STATUS_BROADCAST" />
@ -174,8 +175,8 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" /> <action android:name="android.intent.action.PACKAGE_REPLACED" />
<data <data
android:scheme="package" android:path="com.google.android.gms"
android:path="com.google.android.gms" /> android:scheme="package" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />

View File

@ -19,18 +19,24 @@ package com.google.android.gms.maps.internal;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper;
import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.GoogleMapOptions;
import org.microg.gms.maps.bitmap.BitmapDescriptorFactoryImpl;
import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate; import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate;
import org.microg.gms.maps.camera.CameraUpdateFactoryImpl;
import org.microg.gms.maps.MapFragmentImpl; import org.microg.gms.maps.MapFragmentImpl;
import org.microg.gms.maps.MapViewImpl; import org.microg.gms.maps.MapViewImpl;
import org.microg.gms.maps.ResourcesContainer; import org.microg.gms.maps.ResourcesContainer;
import org.microg.gms.maps.bitmap.BitmapDescriptorFactoryImpl;
import org.microg.gms.maps.camera.CameraUpdateFactoryImpl;
public class CreatorImpl extends ICreator.Stub { public class CreatorImpl extends ICreator.Stub {
private static final String TAG = "GmsMapCreator";
@Override @Override
public void init(IObjectWrapper resources) throws RemoteException { public void init(IObjectWrapper resources) throws RemoteException {
initV2(resources, 0); initV2(resources, 0);
@ -60,4 +66,11 @@ public class CreatorImpl extends ICreator.Stub {
public void initV2(IObjectWrapper resources, int flags) throws RemoteException { public void initV2(IObjectWrapper resources, int flags) throws RemoteException {
ResourcesContainer.set((Resources) ObjectWrapper.unwrap(resources)); ResourcesContainer.set((Resources) ObjectWrapper.unwrap(resources));
} }
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (super.onTransact(code, data, reply, flags)) return true;
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
return false;
}
} }

View File

@ -26,4 +26,13 @@ public class Constants {
public static final int MCS_DATA_MESSAGE_STANZA_TAG = 8; public static final int MCS_DATA_MESSAGE_STANZA_TAG = 8;
public static final int MCS_VERSION_CODE = 41; public static final int MCS_VERSION_CODE = 41;
public static final int MSG_INPUT = 10;
public static final int MSG_INPUT_ERROR = 11;
public static final int MSG_OUTPUT = 20;
public static final int MSG_OUTPUT_ERROR = 21;
public static final int MSG_OUTPUT_READY = 22;
public static final int MSG_TEARDOWN = 30;
public static final int MSG_CONNECT = 40;
public static final int MSG_HEARTBEAT = 41;
} }

View File

@ -16,6 +16,7 @@
package org.microg.gms.gcm; package org.microg.gms.gcm;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import com.squareup.wire.Message; import com.squareup.wire.Message;
@ -39,8 +40,10 @@ import static org.microg.gms.gcm.Constants.MCS_HEARTBEAT_PING_TAG;
import static org.microg.gms.gcm.Constants.MCS_IQ_STANZA_TAG; import static org.microg.gms.gcm.Constants.MCS_IQ_STANZA_TAG;
import static org.microg.gms.gcm.Constants.MCS_LOGIN_REQUEST_TAG; import static org.microg.gms.gcm.Constants.MCS_LOGIN_REQUEST_TAG;
import static org.microg.gms.gcm.Constants.MCS_LOGIN_RESPONSE_TAG; import static org.microg.gms.gcm.Constants.MCS_LOGIN_RESPONSE_TAG;
import static org.microg.gms.gcm.Constants.MSG_INPUT;
import static org.microg.gms.gcm.Constants.MSG_INPUT_ERROR;
public class McsInputStream { public class McsInputStream extends Thread {
private static final String TAG = "GmsGcmMcsInput"; private static final String TAG = "GmsGcmMcsInput";
private final InputStream is; private final InputStream is;
@ -48,14 +51,41 @@ public class McsInputStream {
private int version = -1; private int version = -1;
private int lastStreamIdReported = -1; private int lastStreamIdReported = -1;
private int streamId = 0; private int streamId = 0;
private long lastMsgTime = 0;
private Handler mainHandler;
public McsInputStream(InputStream is) { public McsInputStream(InputStream is, Handler mainHandler) {
this(is, false); this(is, mainHandler, false);
} }
public McsInputStream(InputStream is, boolean initialized) { public McsInputStream(InputStream is, Handler mainHandler, boolean initialized) {
this.is = is; this.is = is;
this.mainHandler = mainHandler;
this.initialized = initialized; this.initialized = initialized;
setName("McsInputStream");
}
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
Message message = read();
if (message != null) {
lastMsgTime = System.currentTimeMillis();
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_INPUT, message));
}
}
} catch (IOException e) {
try {
is.close();
} catch (IOException ignored) {
}
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_INPUT_ERROR, e));
}
}
public void close() {
interrupt();
} }
public int getStreamId() { public int getStreamId() {
@ -63,6 +93,10 @@ public class McsInputStream {
return streamId; return streamId;
} }
public long getLastMsgTime() {
return lastMsgTime;
}
public boolean newStreamIdAvailable() { public boolean newStreamIdAvailable() {
return lastStreamIdReported != streamId; return lastStreamIdReported != streamId;
} }
@ -88,14 +122,12 @@ public class McsInputStream {
ensureVersionRead(); ensureVersionRead();
int mcsTag = is.read(); int mcsTag = is.read();
int mcsSize = readVarint(); int mcsSize = readVarint();
Log.d(TAG, "Reading from MCS tag=" + mcsTag + " size=" + mcsSize);
byte[] bytes = new byte[mcsSize]; byte[] bytes = new byte[mcsSize];
int len = 0; int len = 0;
while (len < mcsSize) { while (len < mcsSize) {
len += is.read(bytes, len, mcsSize - len); len += is.read(bytes, len, mcsSize - len);
} }
Message read = read(mcsTag, bytes, len); Message read = read(mcsTag, bytes, len);
Log.d(TAG, "Read from MCS: " + read);
streamId++; streamId++;
return read; return read;
} }
@ -118,6 +150,7 @@ public class McsInputStream {
case MCS_DATA_MESSAGE_STANZA_TAG: case MCS_DATA_MESSAGE_STANZA_TAG:
return wire.parseFrom(bytes, 0, len, DataMessageStanza.class); return wire.parseFrom(bytes, 0, len, DataMessageStanza.class);
default: default:
Log.w(TAG, "Unknown tag: " + mcsTag);
return null; return null;
} }
} }

View File

@ -16,6 +16,8 @@
package org.microg.gms.gcm; package org.microg.gms.gcm;
import android.os.Handler;
import android.os.Looper;
import android.util.Log; import android.util.Log;
import com.squareup.wire.Message; import com.squareup.wire.Message;
@ -28,9 +30,17 @@ import org.microg.gms.gcm.mcs.LoginRequest;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import static org.microg.gms.gcm.Constants.*; import static org.microg.gms.gcm.Constants.MCS_DATA_MESSAGE_STANZA_TAG;
import static org.microg.gms.gcm.Constants.MCS_HEARTBEAT_ACK_TAG;
import static org.microg.gms.gcm.Constants.MCS_HEARTBEAT_PING_TAG;
import static org.microg.gms.gcm.Constants.MCS_LOGIN_REQUEST_TAG;
import static org.microg.gms.gcm.Constants.MCS_VERSION_CODE;
import static org.microg.gms.gcm.Constants.MSG_OUTPUT;
import static org.microg.gms.gcm.Constants.MSG_OUTPUT_ERROR;
import static org.microg.gms.gcm.Constants.MSG_OUTPUT_READY;
import static org.microg.gms.gcm.Constants.MSG_TEARDOWN;
public class McsOutputStream { public class McsOutputStream extends Thread implements Handler.Callback {
private static final String TAG = "GmsGcmMcsOutput"; private static final String TAG = "GmsGcmMcsOutput";
private final OutputStream os; private final OutputStream os;
@ -38,42 +48,73 @@ public class McsOutputStream {
private int version = MCS_VERSION_CODE; private int version = MCS_VERSION_CODE;
private int streamId = 0; private int streamId = 0;
public McsOutputStream(OutputStream os) { private Handler mainHandler;
this(os, false); private Handler myHandler;
public McsOutputStream(OutputStream os, Handler mainHandler) {
this(os, mainHandler, false);
} }
public McsOutputStream(OutputStream os, boolean initialized) { public McsOutputStream(OutputStream os, Handler mainHandler, boolean initialized) {
this.os = os; this.os = os;
this.mainHandler = mainHandler;
this.initialized = initialized; this.initialized = initialized;
setName("McsOutputStream");
} }
public int getStreamId() { public int getStreamId() {
return streamId; return streamId;
} }
public void write(DataMessageStanza message) throws IOException { @Override
write(message, MCS_DATA_MESSAGE_STANZA_TAG); public void run() {
Looper.prepare();
myHandler = new Handler(this);
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT_READY));
Looper.loop();
} }
public void write(LoginRequest loginRequest) throws IOException { @Override
write(loginRequest, MCS_LOGIN_REQUEST_TAG); public boolean handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_OUTPUT:
try {
Message message = (Message) msg.obj;
if (msg.obj instanceof DataMessageStanza) {
writeInternal(message, MCS_DATA_MESSAGE_STANZA_TAG);
} else if (msg.obj instanceof LoginRequest) {
writeInternal(message, MCS_LOGIN_REQUEST_TAG);
} else if (msg.obj instanceof HeartbeatAck) {
writeInternal(message, MCS_HEARTBEAT_ACK_TAG);
} else if (msg.obj instanceof HeartbeatPing) {
writeInternal(message, MCS_HEARTBEAT_PING_TAG);
} else {
Log.w(TAG, "Unknown message: " + msg.obj);
}
} catch (IOException e) {
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT_ERROR, e));
}
return true;
case MSG_TEARDOWN:
try {
os.close();
} catch (IOException ignored) {
}
try {
Looper.myLooper().quit();
} catch (Exception ignored) {
}
return true;
}
return false;
} }
public void write(HeartbeatAck ack) throws IOException { private synchronized void writeInternal(Message message, int tag) throws IOException {
write(ack, MCS_HEARTBEAT_ACK_TAG);
}
public void write(HeartbeatPing ping) throws IOException{
write(ping, MCS_HEARTBEAT_PING_TAG);
}
public synchronized void write(Message message, int tag) throws IOException {
if (!initialized) { if (!initialized) {
Log.d(TAG, "Write MCS version code: " + version); Log.d(TAG, "Write MCS version code: " + version);
os.write(version); os.write(version);
initialized = true; initialized = true;
} }
Log.d(TAG, "Write to MCS: " + message);
os.write(tag); os.write(tag);
writeVarint(os, message.getSerializedSize()); writeVarint(os, message.getSerializedSize());
os.write(message.toByteArray()); os.write(message.toByteArray());
@ -92,4 +133,8 @@ public class McsOutputStream {
} }
} }
} }
public Handler getHandler() {
return myHandler;
}
} }

View File

@ -16,11 +16,16 @@
package org.microg.gms.gcm; package org.microg.gms.gcm;
import android.app.AlarmManager;
import android.app.IntentService; import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.text.TextUtils; import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import com.squareup.wire.Message; import com.squareup.wire.Message;
@ -35,167 +40,132 @@ import org.microg.gms.gcm.mcs.LoginRequest;
import org.microg.gms.gcm.mcs.LoginResponse; import org.microg.gms.gcm.mcs.LoginResponse;
import org.microg.gms.gcm.mcs.Setting; import org.microg.gms.gcm.mcs.Setting;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static org.microg.gms.gcm.Constants.MSG_CONNECT;
import static org.microg.gms.gcm.Constants.MSG_HEARTBEAT;
import static org.microg.gms.gcm.Constants.MSG_INPUT;
import static org.microg.gms.gcm.Constants.MSG_INPUT_ERROR;
import static org.microg.gms.gcm.Constants.MSG_OUTPUT;
import static org.microg.gms.gcm.Constants.MSG_OUTPUT_ERROR;
import static org.microg.gms.gcm.Constants.MSG_OUTPUT_READY;
import static org.microg.gms.gcm.Constants.MSG_TEARDOWN;
public class McsService extends IntentService { public class McsService extends IntentService implements Handler.Callback {
private static final String TAG = "GmsGcmMcsSvc"; private static final String TAG = "GmsGcmMcsSvc";
public static String ACTION_CONNECT = "org.microg.gms.gcm.mcs.CONNECT";
public static String ACTION_HEARTBEAT = "org.microg.gms.gcm.mcs.HEARTBEAT";
public static final String PREFERENCES_NAME = "mcs"; public static final String PREFERENCES_NAME = "mcs";
public static final String PREF_LAST_PERSISTENT_ID = "last_persistent_id"; public static final String PREF_LAST_PERSISTENT_ID = "last_persistent_id";
public static final String SERVICE_HOST = "mtalk.google.com";
public static final int SERVICE_PORT = 5228;
public static final String SELF_CATEGORY = "com.google.android.gsf.gtalkservice"; public static final String SELF_CATEGORY = "com.google.android.gsf.gtalkservice";
public static final String IDLE_NOTIFICATION = "IdleNotification"; public static final String IDLE_NOTIFICATION = "IdleNotification";
public static final String FROM_FIELD = "gcm@android.com"; public static final String FROM_FIELD = "gcm@android.com";
public static final int HEARTBEAT_MS = 60000; public static final String SERVICE_HOST = "mtalk.google.com";
public static final int HEARTBEAT_ALLOWED_OFFSET_MS = 2000; public static final int SERVICE_PORT = 5228;
private static final AtomicBoolean connecting = new AtomicBoolean(false);
private static final AtomicBoolean pending = new AtomicBoolean(false);
private static Thread connectionThread;
private static Thread heartbeatThread;
private Socket sslSocket; public static final int HEARTBEAT_MS = 60000;
private McsInputStream inputStream;
private McsOutputStream outputStream; private static Socket sslSocket;
private long lastMsgTime; private static McsInputStream inputStream;
private static McsOutputStream outputStream;
private PendingIntent heartbeatIntent;
private static MainThread mainThread;
private static Handler mainHandler;
private boolean initialized = false;
private AlarmManager alarmManager;
private PowerManager powerManager;
private static PowerManager.WakeLock wakeLock;
public McsService() { public McsService() {
super(TAG); super(TAG);
} }
public static AtomicBoolean getPending() { private class MainThread extends Thread {
return pending; @Override
public void run() {
Looper.prepare();
mainHandler = new Handler(Looper.myLooper(), McsService.this);
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_CONNECT));
Looper.loop();
}
}
@Override
public void onCreate() {
super.onCreate();
if (mainThread == null) {
mainThread = new MainThread();
mainThread.start();
}
heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0);
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
powerManager = (PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mcs");
wakeLock.setReferenceCounted(false);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + HEARTBEAT_MS, HEARTBEAT_MS, heartbeatIntent);
}
public static boolean isConnected() {
return inputStream != null && inputStream.isAlive() && outputStream != null && outputStream.isAlive();
} }
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
if (!isConnected()) { wakeLock.acquire();
connectionThread = new Thread(new Runnable() { if (mainHandler != null) {
@Override if (ACTION_CONNECT.equals(intent.getAction())) {
public void run() { mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_CONNECT, intent));
connect(); } else if (ACTION_HEARTBEAT.equals(intent.getAction())) {
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_HEARTBEAT, intent));
} }
});
connectionThread.start();
} else {
Log.d(TAG, "MCS connection already started");
} }
pending.set(false);
} }
public static boolean isConnected() { private synchronized void connect() {
return connecting.get() || (connectionThread != null && connectionThread.isAlive());
}
private void heartbeatLoop() {
try {
while (!Thread.interrupted()) {
try {
long waitTime;
while ((waitTime = lastMsgTime + HEARTBEAT_MS - System.currentTimeMillis()) > HEARTBEAT_ALLOWED_OFFSET_MS) {
synchronized (heartbeatThread) {
Log.d(TAG, "Waiting for " + waitTime + "ms");
heartbeatThread.wait(waitTime);
}
}
HeartbeatPing.Builder ping = new HeartbeatPing.Builder();
if (inputStream.newStreamIdAvailable()) {
ping.last_stream_id_received(inputStream.getStreamId());
}
outputStream.write(ping.build());
lastMsgTime = System.currentTimeMillis();
} catch (InterruptedException ie) {
Log.w(TAG, ie);
return;
}
}
} catch (Exception e) {
Log.w(TAG, e);
connectionThread.interrupt();
}
if (heartbeatThread == Thread.currentThread()) {
heartbeatThread = null;
}
Log.d(TAG, "Heartbeating stopped");
}
private void connect() {
connecting.set(false);
try { try {
Log.d(TAG, "Starting MCS connection..."); Log.d(TAG, "Starting MCS connection...");
LastCheckinInfo info = LastCheckinInfo.read(this);
Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT); Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT);
Log.d(TAG, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT); Log.d(TAG, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT);
sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, "mtalk.google.com", 5228, true); sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, SERVICE_PORT, true);
Log.d(TAG, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT); Log.d(TAG, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT);
inputStream = new McsInputStream(sslSocket.getInputStream()); inputStream = new McsInputStream(sslSocket.getInputStream(), mainHandler);
outputStream = new McsOutputStream(sslSocket.getOutputStream()); outputStream = new McsOutputStream(sslSocket.getOutputStream(), mainHandler);
LoginRequest loginRequest = buildLoginRequest(info); inputStream.start();
Log.d(TAG, "Sending login request..."); outputStream.start();
outputStream.write(loginRequest);
while (!Thread.interrupted()) {
Message o = inputStream.read();
lastMsgTime = System.currentTimeMillis();
if (o instanceof DataMessageStanza) {
handleMessage((DataMessageStanza) o);
} else if (o instanceof HeartbeatPing) {
handleHearbeatPing((HeartbeatPing) o);
} else if (o instanceof Close) {
handleClose((Close) o);
} else if (o instanceof LoginResponse) {
handleLoginresponse((LoginResponse) o);
}
}
sslSocket.close();
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, e); Log.w(TAG, "Exception while connecting!", e);
try { mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, e));
sslSocket.close();
} catch (Exception ignored) {
} }
} }
if (heartbeatThread != null) {
heartbeatThread.interrupt();
heartbeatThread = null;
}
Log.d(TAG, "Connection closed");
sendBroadcast(new Intent("org.microg.gms.gcm.RECONNECT"), "org.microg.gms.STATUS_BROADCAST");
}
private void handleClose(Close close) throws IOException { private void handleClose(Close close) {
throw new IOException("Server requested close!"); throw new RuntimeException("Server requested close!");
} }
private void handleLoginresponse(LoginResponse loginResponse) throws IOException { private void handleLoginResponse(LoginResponse loginResponse) {
getSharedPreferences().edit().putString(PREF_LAST_PERSISTENT_ID, "").apply();
if (loginResponse.error == null) { if (loginResponse.error == null) {
getSharedPreferences().edit().putString(PREF_LAST_PERSISTENT_ID, "").apply();
Log.d(TAG, "Logged in"); Log.d(TAG, "Logged in");
wakeLock.release();
} else { } else {
throw new IOException("Could not login: " + loginResponse.error); throw new RuntimeException("Could not login: " + loginResponse.error);
}
if (heartbeatThread == null) {
heartbeatThread = new Thread(new Runnable() {
@Override
public void run() {
heartbeatLoop();
}
});
heartbeatThread.start();
} }
} }
private void handleMessage(DataMessageStanza message) throws IOException { private void handleCloudMessage(DataMessageStanza message) {
if (message.persistent_id != null) { if (message.persistent_id != null) {
String old = getSharedPreferences().getString(PREF_LAST_PERSISTENT_ID, ""); String old = getSharedPreferences().getString(PREF_LAST_PERSISTENT_ID, "");
if (!old.isEmpty()) { if (!old.isEmpty()) {
@ -211,15 +181,20 @@ public class McsService extends IntentService {
} }
} }
private void handleHearbeatPing(HeartbeatPing ping) throws IOException { private void handleHearbeatPing(HeartbeatPing ping) {
HeartbeatAck.Builder ack = new HeartbeatAck.Builder().status(ping.status); HeartbeatAck.Builder ack = new HeartbeatAck.Builder().status(ping.status);
if (inputStream.newStreamIdAvailable()) { if (inputStream.newStreamIdAvailable()) {
ack.last_stream_id_received(inputStream.getStreamId()); ack.last_stream_id_received(inputStream.getStreamId());
} }
outputStream.write(ack.build()); send(ack.build());
} }
private LoginRequest buildLoginRequest(LastCheckinInfo info) { private void handleHeartbeatAck(HeartbeatAck ack) {
wakeLock.release();
}
private LoginRequest buildLoginRequest() {
LastCheckinInfo info = LastCheckinInfo.read(this);
return new LoginRequest.Builder() return new LoginRequest.Builder()
.adaptive_heartbeat(false) .adaptive_heartbeat(false)
.auth_service(LoginRequest.AuthService.ANDROID_ID) .auth_service(LoginRequest.AuthService.ANDROID_ID)
@ -246,7 +221,7 @@ public class McsService extends IntentService {
sendOrderedBroadcast(intent, msg.category + ".permission.C2D_MESSAGE"); sendOrderedBroadcast(intent, msg.category + ".permission.C2D_MESSAGE");
} }
private void handleSelfMessage(DataMessageStanza msg) throws IOException { private void handleSelfMessage(DataMessageStanza msg) {
for (AppData appData : msg.app_data) { for (AppData appData : msg.app_data) {
if (IDLE_NOTIFICATION.equals(appData.key)) { if (IDLE_NOTIFICATION.equals(appData.key)) {
DataMessageStanza.Builder msgResponse = new DataMessageStanza.Builder() DataMessageStanza.Builder msgResponse = new DataMessageStanza.Builder()
@ -258,7 +233,7 @@ public class McsService extends IntentService {
if (inputStream.newStreamIdAvailable()) { if (inputStream.newStreamIdAvailable()) {
msgResponse.last_stream_id_received(inputStream.getStreamId()); msgResponse.last_stream_id_received(inputStream.getStreamId());
} }
outputStream.write(msgResponse.build()); send(msgResponse.build());
} }
} }
} }
@ -266,4 +241,98 @@ public class McsService extends IntentService {
private SharedPreferences getSharedPreferences() { private SharedPreferences getSharedPreferences() {
return getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); return getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
} }
private void send(Message message) {
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT, message));
}
private void sendOutputStream(int what, Object obj) {
McsOutputStream os = outputStream;
if (os != null) {
Handler outputHandler = os.getHandler();
if (outputHandler != null)
outputHandler.dispatchMessage(outputHandler.obtainMessage(what, obj));
}
}
@Override
public boolean handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_INPUT:
Log.d(TAG, "Incoming message: " + msg.obj);
handleInput((Message) msg.obj);
return true;
case MSG_OUTPUT:
Log.d(TAG, "Outgoing message: " + msg.obj);
sendOutputStream(MSG_OUTPUT, msg.obj);
return true;
case MSG_INPUT_ERROR:
case MSG_OUTPUT_ERROR:
Log.d(TAG, "I/O error: " + msg.obj);
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, msg.obj));
return true;
case MSG_TEARDOWN:
Log.d(TAG, "Teardown initiated, reason: " + msg.obj);
handleTeardown(msg);
return true;
case MSG_CONNECT:
Log.d(TAG, "Connect initiated, reason: " + msg.obj);
if (!isConnected()) {
connect();
}
return true;
case MSG_HEARTBEAT:
Log.d(TAG, "Heartbeat initiated, reason: " + msg.obj);
if (isConnected()) {
HeartbeatPing.Builder ping = new HeartbeatPing.Builder();
if (inputStream.newStreamIdAvailable()) {
ping.last_stream_id_received(inputStream.getStreamId());
}
send(ping.build());
} else {
Log.d(TAG, "Ignoring heartbeat, not connected!");
}
return true;
case MSG_OUTPUT_READY:
Log.d(TAG, "Sending login request...");
send(buildLoginRequest());
return true;
}
Log.w(TAG, "Unknown message: " + msg);
return false;
}
private void handleInput(Message message) {
try {
if (message instanceof DataMessageStanza) {
handleCloudMessage((DataMessageStanza) message);
} else if (message instanceof HeartbeatPing) {
handleHearbeatPing((HeartbeatPing) message);
} else if (message instanceof Close) {
handleClose((Close) message);
} else if (message instanceof LoginResponse) {
handleLoginResponse((LoginResponse) message);
} else if (message instanceof HeartbeatAck) {
handleHeartbeatAck((HeartbeatAck) message);
} else {
Log.w(TAG, "Unknown message: " + message);
}
} catch (Exception e) {
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, e));
}
}
private void handleTeardown(android.os.Message msg) {
sendOutputStream(MSG_TEARDOWN, msg.obj);
if (inputStream != null) {
inputStream.close();
}
try {
sslSocket.close();
} catch (Exception ignored) {
}
sendBroadcast(new Intent("org.microg.gms.gcm.RECONNECT"), "org.microg.gms.STATUS_BROADCAST");
alarmManager.cancel(heartbeatIntent);
wakeLock.release();
}
} }

View File

@ -34,7 +34,6 @@ public class TriggerReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction()); boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction());
if (McsService.getPending().compareAndSet(false, true)) {
if (!McsService.isConnected() || force) { if (!McsService.isConnected() || force) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_ENABLE_GCM, false) || force) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_ENABLE_GCM, false) || force) {
@ -42,13 +41,10 @@ public class TriggerReceiver extends BroadcastReceiver {
NetworkInfo networkInfo = cm.getActiveNetworkInfo(); NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected() || force) { if (networkInfo != null && networkInfo.isConnected() || force) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, new Intent(context, McsService.class), 0); PendingIntent pendingIntent = PendingIntent.getService(context, 0, new Intent(McsService.ACTION_CONNECT, null, context, McsService.class), 0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + pendingDelay, pendingIntent); alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + pendingDelay, pendingIntent);
return;
} }
} }
} }
McsService.getPending().set(false);
}
} }
} }

View File

@ -41,6 +41,7 @@ import org.oscim.map.Viewport;
import org.oscim.theme.VtmThemes; import org.oscim.theme.VtmThemes;
import org.oscim.tiling.source.oscimap4.OSciMap4TileSource; import org.oscim.tiling.source.oscimap4.OSciMap4TileSource;
import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
public class BackendMap implements ItemizedLayer.OnItemGestureListener<MarkerItem> { public class BackendMap implements ItemizedLayer.OnItemGestureListener<MarkerItem> {
@ -83,27 +84,27 @@ public class BackendMap implements ItemizedLayer.OnItemGestureListener<MarkerIte
} }
public void destroy() { public void destroy() {
//mapView.map().destroy(); mapView.map().destroy();
} }
public void onResume() { public void onResume() {
/*try { try {
Method onResume = MapView.class.getDeclaredMethod("onResume"); Method onResume = MapView.class.getDeclaredMethod("onResume");
onResume.setAccessible(true); onResume.setAccessible(true);
onResume.invoke(mapView); onResume.invoke(mapView);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
}*/ }
} }
public void onPause() { public void onPause() {
/*try { try {
Method onPause = MapView.class.getDeclaredMethod("onPause"); Method onPause = MapView.class.getDeclaredMethod("onPause");
onPause.setAccessible(true); onPause.setAccessible(true);
onPause.invoke(mapView); onPause.invoke(mapView);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
}*/ }
} }
public MapPosition getMapPosition() { public MapPosition getMapPosition() {

View File

@ -18,6 +18,7 @@ package org.microg.gms.maps;
import android.content.Context; import android.content.Context;
import android.location.Location; import android.location.Location;
import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -181,11 +182,13 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub
@Override @Override
public IPolylineDelegate addPolyline(PolylineOptions options) throws RemoteException { public IPolylineDelegate addPolyline(PolylineOptions options) throws RemoteException {
Log.d(TAG, "not yet usable: addPolyline");
return new PolylineImpl(options); // TODO return new PolylineImpl(options); // TODO
} }
@Override @Override
public IPolygonDelegate addPolygon(PolygonOptions options) throws RemoteException { public IPolygonDelegate addPolygon(PolygonOptions options) throws RemoteException {
Log.d(TAG, "not yet usable: addPolygon");
return new PolygonImpl(options); // TODO return new PolygonImpl(options); // TODO
} }
@ -197,17 +200,19 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub
@Override @Override
public IGroundOverlayDelegate addGroundOverlay(GroundOverlayOptions options) public IGroundOverlayDelegate addGroundOverlay(GroundOverlayOptions options)
throws RemoteException { throws RemoteException {
Log.d(TAG, "not yet usable: addGroundOverlay");
return new GroundOverlayImpl(options); // TODO return new GroundOverlayImpl(options); // TODO
} }
@Override @Override
public ITileOverlayDelegate addTileOverlay(TileOverlayOptions options) throws RemoteException { public ITileOverlayDelegate addTileOverlay(TileOverlayOptions options) throws RemoteException {
Log.d(TAG, "not yet usable: addTileOverlay");
return new TileOverlayImpl(); // TODO return new TileOverlayImpl(); // TODO
} }
@Override @Override
public void setInfoWindowAdapter(IInfoWindowAdapter adapter) throws RemoteException { public void setInfoWindowAdapter(IInfoWindowAdapter adapter) throws RemoteException {
Log.d(TAG, "not yet usable: setInfoWindowAdapter");
} }
@Override @Override
@ -368,7 +373,7 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub
@Override @Override
public void setOnMapLoadedCallback(IOnMapLoadedCallback callback) throws RemoteException { public void setOnMapLoadedCallback(IOnMapLoadedCallback callback) throws RemoteException {
Log.d(TAG, "not yet usable: setOnMapLoadedCallback");
} }
/* /*
@ -405,4 +410,11 @@ public class GoogleMapImpl extends IGoogleMapDelegate.Stub
public void onInputEvent(Event event, MotionEvent motionEvent) { public void onInputEvent(Event event, MotionEvent motionEvent) {
Log.d(TAG, "onInputEvent(" + event + ", " + motionEvent + ")"); Log.d(TAG, "onInputEvent(" + event + ", " + motionEvent + ")");
} }
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (super.onTransact(code, data, reply, flags)) return true;
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
return false;
}
} }

View File

@ -18,20 +18,25 @@ package org.microg.gms.maps;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper;
import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.GoogleMapOptions;
import com.google.android.gms.maps.internal.IGoogleMapDelegate; import com.google.android.gms.maps.internal.IGoogleMapDelegate;
import com.google.android.gms.maps.internal.IMapViewDelegate; import com.google.android.gms.maps.internal.IMapViewDelegate;
import com.google.android.gms.maps.internal.IOnMapReadyCallback;
public class MapViewImpl extends IMapViewDelegate.Stub { public class MapViewImpl extends IMapViewDelegate.Stub {
private static final String TAG = "GmsMapViewImpl";
private GoogleMapImpl map; private GoogleMapImpl map;
private GoogleMapOptions options; private GoogleMapOptions options;
private Context context; private Context context;
private IOnMapReadyCallback readyCallback;
public MapViewImpl(Context context, GoogleMapOptions options) { public MapViewImpl(Context context, GoogleMapOptions options) {
this.context = context; this.context = context;
@ -61,6 +66,16 @@ public class MapViewImpl extends IMapViewDelegate.Stub {
@Override @Override
public void onResume() throws RemoteException { public void onResume() throws RemoteException {
myMap().onResume(); myMap().onResume();
if (readyCallback != null) {
try {
readyCallback.onMapReady(map);
readyCallback = null;
} catch (Exception e) {
Log.w(TAG, e);
}
}
} }
@Override @Override
@ -87,4 +102,16 @@ public class MapViewImpl extends IMapViewDelegate.Stub {
public IObjectWrapper getView() throws RemoteException { public IObjectWrapper getView() throws RemoteException {
return ObjectWrapper.wrap(myMap().getView()); return ObjectWrapper.wrap(myMap().getView());
} }
@Override
public void addOnMapReadyCallback(IOnMapReadyCallback callback) throws RemoteException {
this.readyCallback = callback;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (super.onTransact(code, data, reply, flags)) return true;
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
return false;
}
} }

View File

@ -17,12 +17,16 @@
package org.microg.gms.maps.bitmap; package org.microg.gms.maps.bitmap;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper;
import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate; import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate;
public class BitmapDescriptorFactoryImpl extends IBitmapDescriptorFactoryDelegate.Stub { public class BitmapDescriptorFactoryImpl extends IBitmapDescriptorFactoryDelegate.Stub {
private static final String TAG = "GmsBitmapDescFactory";
@Override @Override
public IObjectWrapper fromResource(int resourceId) throws RemoteException { public IObjectWrapper fromResource(int resourceId) throws RemoteException {
@ -58,4 +62,11 @@ public class BitmapDescriptorFactoryImpl extends IBitmapDescriptorFactoryDelegat
public IObjectWrapper fromPath(String absolutePath) throws RemoteException { public IObjectWrapper fromPath(String absolutePath) throws RemoteException {
return ObjectWrapper.wrap(new PathBitmapDescriptor(absolutePath)); return ObjectWrapper.wrap(new PathBitmapDescriptor(absolutePath));
} }
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (super.onTransact(code, data, reply, flags)) return true;
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
return false;
}
} }

View File

@ -102,4 +102,8 @@ public class ConfigurationDatabaseHelper extends SQLiteOpenHelper {
return null; return null;
} }
} }
public void setEnabledState(String name, boolean enabled) {
getWritableDatabase().execSQL("UPDATE connectionConfigurations SET connectionEnabled=? WHERE name=?", new String[]{enabled ? "1" : "0", name});
}
} }

View File

@ -0,0 +1,48 @@
/*
* Copyright 2013-2015 µg Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.microg.gms.wearable;
import android.net.Uri;
import com.google.android.gms.wearable.Asset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class DataItemInternal {
public final String host;
public final String path;
public final Uri uri;
public byte[] data;
private Map<String, Asset> assets = new HashMap<String, Asset>();
public DataItemInternal(String host, String path) {
this.host = host;
this.path = path;
this.uri = new Uri.Builder().scheme("wear").authority(host).path(path).build();
}
public DataItemInternal addAsset(String key, Asset asset) {
this.assets.put(key, asset);
return this;
}
public Map<String, Asset> getAssets() {
return Collections.unmodifiableMap(new HashMap<String, Asset>(assets));
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2013-2015 µg Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.microg.gms.wearable;
import android.content.ContentValues;
import android.database.Cursor;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.internal.DataItemAssetParcelable;
import com.google.android.gms.wearable.internal.DataItemParcelable;
import java.util.Map;
public class DataItemRecord {
public DataItemInternal dataItem;
public String source;
public long seqId;
public long v1SeqId;
public long lastModified;
public boolean deleted;
public boolean assetsAreReady;
public String packageName;
public String signatureDigest;
public ContentValues getContentValues() {
ContentValues contentValues = new ContentValues();
contentValues.put("sourceNode", source);
contentValues.put("seqId", seqId);
contentValues.put("v1SourceNode", source);
contentValues.put("v1SeqId", v1SeqId);
contentValues.put("timestampMs", lastModified);
if (deleted) {
contentValues.put("deleted", 1);
contentValues.putNull("data");
} else {
contentValues.put("deleted", 0);
contentValues.put("data", dataItem.data);
}
contentValues.put("assetsPresent", assetsAreReady ? 1 : 0);
return contentValues;
}
public DataItemParcelable toParcelable() {
DataItemParcelable parcelable = new DataItemParcelable(dataItem.uri);
parcelable.data = dataItem.data;
for (Map.Entry<String, Asset> entry : dataItem.getAssets().entrySet()) {
parcelable.getAssets().put(entry.getKey(), new DataItemAssetParcelable(entry.getValue().getDigest(), entry.getKey()));
}
return parcelable;
}
public static DataItemRecord fromCursor(Cursor cursor) {
DataItemRecord record = new DataItemRecord();
record.packageName = cursor.getString(1);
record.signatureDigest = cursor.getString(2);
record.dataItem = new DataItemInternal(cursor.getString(3), cursor.getString(4));
record.seqId = cursor.getLong(5);
record.deleted = cursor.getLong(6) > 0;
record.source = cursor.getString(7);
record.dataItem.data = cursor.getBlob(8);
record.lastModified = cursor.getLong(9);
record.assetsAreReady = cursor.getLong(10) > 0;
return record;
}
}

View File

@ -21,15 +21,18 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri; import android.util.Log;
import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.Asset;
import java.util.Map; import java.util.Map;
public class NodeDatabaseHelper extends SQLiteOpenHelper { public class NodeDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "GmsWearNodeDB";
private static final String DB_NAME = "node.db"; private static final String DB_NAME = "node.db";
private static final int VERSION = 7; private static final String[] GDIBHAP_FIELDS = new String[]{"dataitems_id", "packageName", "signatureDigest", "host", "path", "seqId", "deleted", "sourceNode", "data", "timestampMs", "assetsPresent", "assetname", "assets_digest", "v1SourceNode", "v1SeqId"};
private static final int VERSION = 9;
public NodeDatabaseHelper(Context context) { public NodeDatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION); super(context, DB_NAME, null, VERSION);
@ -38,26 +41,29 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE appkeys(_id INTEGER PRIMARY KEY AUTOINCREMENT,packageName TEXT NOT NULL,signatureDigest TEXT NOT NULL);"); db.execSQL("CREATE TABLE appkeys(_id INTEGER PRIMARY KEY AUTOINCREMENT,packageName TEXT NOT NULL,signatureDigest TEXT NOT NULL);");
db.execSQL("CREATE TABLE dataitems(_id INTEGER PRIMARY KEY AUTOINCREMENT, appkeys_id INTEGER NOT NULL REFERENCES appkeys(_id), host TEXT NOT NULL, path TEXT NOT NULL, seqId INTEGER NOT NULL, deleted INTEGER NOT NULL, sourceNode TEXT NOT NULL, data BLOB, timestampMs INTEGER NOT NULL, assetsPresent INTEGER NOT NULL);"); db.execSQL("CREATE TABLE dataitems(_id INTEGER PRIMARY KEY AUTOINCREMENT, appkeys_id INTEGER NOT NULL REFERENCES appkeys(_id), host TEXT NOT NULL, path TEXT NOT NULL, seqId INTEGER NOT NULL, deleted INTEGER NOT NULL, sourceNode TEXT NOT NULL, data BLOB, timestampMs INTEGER NOT NULL, assetsPresent INTEGER NOT NULL, v1SourceNode TEXT NOT NULL, v1SeqId INTEGER NOT NULL);");
db.execSQL("CREATE TABLE assets(digest TEXT PRIMARY KEY, dataPresent INTEGER NOT NULL DEFAULT 0, timestampMs INTEGER NOT NULL);"); db.execSQL("CREATE TABLE assets(digest TEXT PRIMARY KEY, dataPresent INTEGER NOT NULL DEFAULT 0, timestampMs INTEGER NOT NULL);");
db.execSQL("CREATE TABLE assetrefs(assetname TEXT NOT NULL, dataitems_id INTEGER NOT NULL REFERENCES dataitems(_id), assets_digest TEXT NOT NULL REFERENCES assets(digest));"); db.execSQL("CREATE TABLE assetrefs(assetname TEXT NOT NULL, dataitems_id INTEGER NOT NULL REFERENCES dataitems(_id), assets_digest TEXT NOT NULL REFERENCES assets(digest));");
db.execSQL("CREATE TABLE assetsacls(appkeys_id INTEGER NOT NULL REFERENCES appkeys(_id), assets_digest TEXT NOT NULL);"); db.execSQL("CREATE TABLE assetsacls(appkeys_id INTEGER NOT NULL REFERENCES appkeys(_id), assets_digest TEXT NOT NULL);");
db.execSQL("CREATE VIEW appKeyDataItems AS SELECT appkeys._id AS appkeys_id, appkeys.packageName AS packageName, appkeys.signatureDigest AS signatureDigest, dataitems._id AS dataitems_id, dataitems.host AS host, dataitems.path AS path, dataitems.seqId AS seqId, dataitems.deleted AS deleted, dataitems.sourceNode AS sourceNode, dataitems.data AS data, dataitems.timestampMs AS timestampMs, dataitems.assetsPresent AS assetsPresent FROM appkeys, dataitems WHERE appkeys._id=dataitems.appkeys_id"); db.execSQL("CREATE TABLE nodeinfo(node TEXT NOT NULL PRIMARY KEY, seqId INTEGER, lastActivityMs INTEGER);");
db.execSQL("CREATE VIEW appKeyDataItems AS SELECT appkeys._id AS appkeys_id, appkeys.packageName AS packageName, appkeys.signatureDigest AS signatureDigest, dataitems._id AS dataitems_id, dataitems.host AS host, dataitems.path AS path, dataitems.seqId AS seqId, dataitems.deleted AS deleted, dataitems.sourceNode AS sourceNode, dataitems.data AS data, dataitems.timestampMs AS timestampMs, dataitems.assetsPresent AS assetsPresent, dataitems.v1SourceNode AS v1SourceNode, dataitems.v1SeqId AS v1SeqId FROM appkeys, dataitems WHERE appkeys._id=dataitems.appkeys_id");
db.execSQL("CREATE VIEW appKeyAcls AS SELECT appkeys._id AS appkeys_id, appkeys.packageName AS packageName, appkeys.signatureDigest AS signatureDigest, assetsacls.assets_digest AS assets_digest FROM appkeys, assetsacls WHERE _id=appkeys_id"); db.execSQL("CREATE VIEW appKeyAcls AS SELECT appkeys._id AS appkeys_id, appkeys.packageName AS packageName, appkeys.signatureDigest AS signatureDigest, assetsacls.assets_digest AS assets_digest FROM appkeys, assetsacls WHERE _id=appkeys_id");
db.execSQL("CREATE VIEW dataItemsAndAssets AS SELECT appKeyDataItems.packageName AS packageName, appKeyDataItems.signatureDigest AS signatureDigest, appKeyDataItems.dataitems_id AS dataitems_id, appKeyDataItems.host AS host, appKeyDataItems.path AS path, appKeyDataItems.seqId AS seqId, appKeyDataItems.deleted AS deleted, appKeyDataItems.sourceNode AS sourceNode, appKeyDataItems.data AS data, appKeyDataItems.timestampMs AS timestampMs, appKeyDataItems.assetsPresent AS assetsPresent, assetrefs.assetname AS assetname, assetrefs.assets_digest AS assets_digest FROM appKeyDataItems LEFT OUTER JOIN assetrefs ON appKeyDataItems.dataitems_id=assetrefs.dataitems_id"); db.execSQL("CREATE VIEW dataItemsAndAssets AS SELECT appKeyDataItems.packageName AS packageName, appKeyDataItems.signatureDigest AS signatureDigest, appKeyDataItems.dataitems_id AS dataitems_id, appKeyDataItems.host AS host, appKeyDataItems.path AS path, appKeyDataItems.seqId AS seqId, appKeyDataItems.deleted AS deleted, appKeyDataItems.sourceNode AS sourceNode, appKeyDataItems.data AS data, appKeyDataItems.timestampMs AS timestampMs, appKeyDataItems.assetsPresent AS assetsPresent, assetrefs.assetname AS assetname, assetrefs.assets_digest AS assets_digest, appKeyDataItems.v1SourceNode AS v1SourceNode, appKeyDataItems.v1SeqId AS v1SeqId FROM appKeyDataItems LEFT OUTER JOIN assetrefs ON appKeyDataItems.dataitems_id=assetrefs.dataitems_id");
db.execSQL("CREATE VIEW assetsReadyStatus AS SELECT dataitems_id AS dataitems_id, COUNT(*) = SUM(dataPresent) AS nowReady, assetsPresent AS markedReady FROM assetrefs, dataitems LEFT OUTER JOIN assets ON assetrefs.assets_digest = assets.digest WHERE assetrefs.dataitems_id=dataitems._id GROUP BY dataitems_id;"); db.execSQL("CREATE VIEW assetsReadyStatus AS SELECT dataitems_id AS dataitems_id, COUNT(*) = SUM(dataPresent) AS nowReady, assetsPresent AS markedReady FROM assetrefs, dataitems LEFT OUTER JOIN assets ON assetrefs.assets_digest = assets.digest WHERE assetrefs.dataitems_id=dataitems._id GROUP BY dataitems_id;");
db.execSQL("CREATE UNIQUE INDEX appkeys_NAME_AND_SIG ON appkeys(packageName,signatureDigest);"); db.execSQL("CREATE UNIQUE INDEX appkeys_NAME_AND_SIG ON appkeys(packageName,signatureDigest);");
db.execSQL("CREATE UNIQUE INDEX assetrefs_ASSET_REFS ON assetrefs(assets_digest,dataitems_id,assetname);"); db.execSQL("CREATE UNIQUE INDEX assetrefs_ASSET_REFS ON assetrefs(assets_digest,dataitems_id,assetname);");
db.execSQL("CREATE UNIQUE INDEX assets_DIGEST ON assets(digest);"); db.execSQL("CREATE UNIQUE INDEX assets_DIGEST ON assets(digest);");
db.execSQL("CREATE UNIQUE INDEX assetsacls_APPKEY_AND_DIGEST ON assetsacls(appkeys_id,assets_digest);"); db.execSQL("CREATE UNIQUE INDEX assetsacls_APPKEY_AND_DIGEST ON assetsacls(appkeys_id,assets_digest);");
db.execSQL("CREATE UNIQUE INDEX dataitems_APPKEY_HOST_AND_PATH ON dataitems(appkeys_id,host,path);"); db.execSQL("CREATE UNIQUE INDEX dataitems_APPKEY_HOST_AND_PATH ON dataitems(appkeys_id,host,path);");
db.execSQL("CREATE UNIQUE INDEX dataitems_SOURCENODE_AND_SEQID ON dataitems(sourceNode,seqId);");
db.execSQL("CREATE UNIQUE INDEX dataitems_SOURCENODE_DELETED_AND_SEQID ON dataitems(sourceNode,deleted,seqId);");
} }
public Cursor getDataItemsForDataHolder(String packageName, String signatureDigest) { public synchronized Cursor getDataItemsForDataHolder(String packageName, String signatureDigest) {
return getDataItemsForDataHolderByHostAndPath(packageName, signatureDigest, null, null); return getDataItemsForDataHolderByHostAndPath(packageName, signatureDigest, null, null);
} }
public Cursor getDataItemsForDataHolderByHostAndPath(String packageName, String signatureDigest, String host, String path) { public synchronized Cursor getDataItemsForDataHolderByHostAndPath(String packageName, String signatureDigest, String host, String path) {
String[] params; String[] params;
String selection; String selection;
if (path == null) { if (path == null) {
@ -76,32 +82,126 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != VERSION) {
// TODO: Upgrade not supported, cleaning up
db.execSQL("DROP TABLE IF EXISTS appkeys;");
db.execSQL("DROP TABLE IF EXISTS dataitems;");
db.execSQL("DROP TABLE IF EXISTS assets;");
db.execSQL("DROP TABLE IF EXISTS assetrefs;");
db.execSQL("DROP TABLE IF EXISTS assetsacls;");
db.execSQL("DROP TABLE IF EXISTS nodeinfo;");
db.execSQL("DROP VIEW IF EXISTS appKeyDataItems;");
db.execSQL("DROP VIEW IF EXISTS appKeyAcls;");
db.execSQL("DROP VIEW IF EXISTS dataItemsAndAssets;");
db.execSQL("DROP VIEW IF EXISTS assetsReadyStatus;");
onCreate(db);
}
} }
private synchronized long getAppKey(String packageName, String signatureDigest) { private synchronized long getAppKey(SQLiteDatabase db, String packageName, String signatureDigest) {
Cursor cursor = getReadableDatabase().rawQuery("SELECT _id FROM appkeys WHERE packageName=? AND signatureDigest=?", new String[]{packageName, signatureDigest}); Cursor cursor = db.rawQuery("SELECT _id FROM appkeys WHERE packageName=? AND signatureDigest=?", new String[]{packageName, signatureDigest});
if (cursor != null) { if (cursor != null) {
try {
if (cursor.moveToNext()) { if (cursor.moveToNext()) {
return cursor.getLong(0); return cursor.getLong(0);
} }
} finally {
cursor.close(); cursor.close();
} }
}
ContentValues appKey = new ContentValues(); ContentValues appKey = new ContentValues();
appKey.put("packageName", packageName); appKey.put("packageName", packageName);
appKey.put("signatureDigest", signatureDigest); appKey.put("signatureDigest", signatureDigest);
return getWritableDatabase().insert("appkeys", null, appKey); return db.insert("appkeys", null, appKey);
} }
public void putDataItem(String packageName, String signatureDigest, String host, String path, ContentValues data) { public synchronized void putRecord(DataItemRecord record) {
ContentValues item = new ContentValues(data); SQLiteDatabase db = getWritableDatabase();
item.put("appkeys_id", getAppKey(packageName, signatureDigest)); db.beginTransaction();
item.put("host", host); Cursor cursor = getDataItemsByHostAndPath(db, record.packageName, record.signatureDigest, record.dataItem.host, record.dataItem.path);
item.put("path", path); try {
getWritableDatabase().insertWithOnConflict("dataitems", "host", item, SQLiteDatabase.CONFLICT_REPLACE); String key;
if (cursor.moveToNext()) {
// update
key = cursor.getString(0);
updateRecord(db, key, record);
} else {
// insert
key = insertRecord(db, record);
}
if (record.assetsAreReady) {
ContentValues update = new ContentValues();
update.put("assetsPresent", 1);
db.update("dataitems", update, "_id=?", new String[]{key});
}
db.setTransactionSuccessful();
} finally {
cursor.close();
}
db.endTransaction();
} }
public void deleteDataItem(String packageName, String signatureDigest, String host, String path) { private static void updateRecord(SQLiteDatabase db, String key, DataItemRecord record) {
getWritableDatabase().delete("dataitems", "packageName=? AND signatureDigest=? AND host=? AND path=?", new String[]{packageName, signatureDigest, host, packageName}); Log.d(TAG, "updateRecord: " + record);
}
private String insertRecord(SQLiteDatabase db, DataItemRecord record) {
ContentValues contentValues = record.getContentValues();
contentValues.put("appkeys_id", getAppKey(db, record.packageName, record.signatureDigest));
contentValues.put("host", record.dataItem.host);
contentValues.put("path", record.dataItem.path);
String key = Long.toString(db.insert("dataitems", "host", contentValues));
if (!record.deleted) {
for (Map.Entry<String, Asset> asset : record.dataItem.getAssets().entrySet()) {
ContentValues assetValues = new ContentValues();
assetValues.put("assets_digest", asset.getValue().getDigest());
assetValues.put("dataitems_id", key);
assetValues.put("assetname", asset.getKey());
db.insert("assetrefs", "assetname", assetValues);
}
Cursor status = db.query("assetsReadyStatus", new String[]{"nowReady"}, "dataitems_id=?", new String[]{key}, null, null, null);
if (status.moveToNext()) {
record.assetsAreReady = status.getLong(0) != 0;
}
status.close();
} else {
record.assetsAreReady = false;
}
return key;
}
private static Cursor getDataItemsByHostAndPath(SQLiteDatabase db, String packageName, String signatureDigest, String host, String path) {
String[] params;
String selection;
if (path == null) {
params = new String[]{packageName, signatureDigest};
selection = "packageName =? AND signatureDigest =?";
} else if (host == null) {
params = new String[]{packageName, signatureDigest, path};
selection = "packageName =? AND signatureDigest =? AND path =?";
} else {
params = new String[]{packageName, signatureDigest, host, path};
selection = "packageName =? AND signatureDigest =? AND host =? AND path =?";
}
selection += " AND deleted=0";
return db.query("dataItemsAndAssets", GDIBHAP_FIELDS, selection, params, null, null, "packageName, signatureDigest, host, path");
}
public synchronized int deleteDataItems(String packageName, String signatureDigest, String host, String path) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();
Cursor cursor = getDataItemsByHostAndPath(db, packageName, signatureDigest, host, path);
int n = 0;
while (cursor.moveToNext()) {
DataItemRecord record = DataItemRecord.fromCursor(cursor);
record.deleted = true;
record.assetsAreReady = true;
record.dataItem.data = null;
updateRecord(db, cursor.getString(0), record);
n++;
}
db.setTransactionSuccessful();
db.endTransaction();
return n;
} }
} }

View File

@ -16,7 +16,6 @@
package org.microg.gms.wearable; package org.microg.gms.wearable;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
@ -24,21 +23,27 @@ import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.RemoteException; import android.os.RemoteException;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import com.google.android.gms.common.api.Status; import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.data.DataHolder; import com.google.android.gms.common.data.DataHolder;
import com.google.android.gms.wearable.AddListenerRequest; import com.google.android.gms.wearable.internal.AddListenerRequest;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.ConnectionConfiguration; import com.google.android.gms.wearable.ConnectionConfiguration;
import com.google.android.gms.wearable.GetConfigResponse; import com.google.android.gms.wearable.internal.CapabilityInfoParcelable;
import com.google.android.gms.wearable.GetConfigsResponse; import com.google.android.gms.wearable.internal.ChannelEventParcelable;
import com.google.android.gms.wearable.GetConnectedNodesResponse; import com.google.android.gms.wearable.internal.DeleteDataItemsResponse;
import com.google.android.gms.wearable.GetDataItemResponse; import com.google.android.gms.wearable.internal.GetConfigResponse;
import com.google.android.gms.wearable.GetLocalNodeResponse; import com.google.android.gms.wearable.internal.GetConfigsResponse;
import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.internal.GetConnectedNodesResponse;
import com.google.android.gms.wearable.PutDataResponse; import com.google.android.gms.wearable.internal.GetDataItemResponse;
import com.google.android.gms.wearable.RemoveListenerRequest; import com.google.android.gms.wearable.internal.GetLocalNodeResponse;
import com.google.android.gms.wearable.internal.DataItemAssetParcelable; import com.google.android.gms.wearable.internal.IWearableListener;
import com.google.android.gms.wearable.internal.MessageEventParcelable;
import com.google.android.gms.wearable.internal.PutDataRequest;
import com.google.android.gms.wearable.internal.PutDataResponse;
import com.google.android.gms.wearable.internal.RemoveListenerRequest;
import com.google.android.gms.wearable.internal.DataItemParcelable; import com.google.android.gms.wearable.internal.DataItemParcelable;
import com.google.android.gms.wearable.internal.IWearableCallbacks; import com.google.android.gms.wearable.internal.IWearableCallbacks;
import com.google.android.gms.wearable.internal.IWearableService; import com.google.android.gms.wearable.internal.IWearableService;
@ -46,22 +51,33 @@ import com.google.android.gms.wearable.internal.NodeParcelable;
import org.microg.gms.common.PackageUtils; import org.microg.gms.common.PackageUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public class WearableServiceImpl extends IWearableService.Stub { public class WearableServiceImpl extends IWearableService.Stub implements IWearableListener {
private static final String TAG = "GmsWearSvcImpl"; private static final String TAG = "GmsWearSvcImpl";
private static final String CLOCKWORK_NODE_PREFERENCES = "cw_node"; private static final String CLOCKWORK_NODE_PREFERENCES = "cw_node";
private static final String CLOCKWORK_NODE_PREFERENCE_NODE_ID = "node_id"; private static final String CLOCKWORK_NODE_PREFERENCE_NODE_ID = "node_id";
private static final String CLOCKWORK_NODE_PREFERENCE_NEXT_SEQ_ID_BLOCK = "nextSeqIdBlock";
private final Context context; private final Context context;
private final String packageName; private final String packageName;
private final NodeDatabaseHelper nodeDatabase; private final NodeDatabaseHelper nodeDatabase;
private final ConfigurationDatabaseHelper configDatabase; private final ConfigurationDatabaseHelper configDatabase;
private Set<IWearableListener> listeners = new HashSet<IWearableListener>();
private long seqIdBlock;
private long seqIdInBlock = -1;
public WearableServiceImpl(Context context, NodeDatabaseHelper nodeDatabase, ConfigurationDatabaseHelper configDatabase, String packageName) { public WearableServiceImpl(Context context, NodeDatabaseHelper nodeDatabase, ConfigurationDatabaseHelper configDatabase, String packageName) {
this.context = context; this.context = context;
@ -80,28 +96,81 @@ public class WearableServiceImpl extends IWearableService.Stub {
return nodeId; return nodeId;
} }
private synchronized long getNextSeqId() {
SharedPreferences preferences = context.getSharedPreferences(CLOCKWORK_NODE_PREFERENCES, Context.MODE_PRIVATE);
if (seqIdInBlock < 0) seqIdInBlock = 1000;
if (seqIdInBlock >= 1000) {
seqIdBlock = preferences.getLong(CLOCKWORK_NODE_PREFERENCE_NEXT_SEQ_ID_BLOCK, 100);
preferences.edit().putLong(CLOCKWORK_NODE_PREFERENCE_NEXT_SEQ_ID_BLOCK, seqIdBlock + seqIdInBlock).apply();
seqIdInBlock = 0;
}
return seqIdBlock + seqIdInBlock++;
}
@Override @Override
public void putData(IWearableCallbacks callbacks, PutDataRequest request) throws RemoteException { public void putData(IWearableCallbacks callbacks, PutDataRequest request) throws RemoteException {
Log.d(TAG, "putData: " + request); Log.d(TAG, "putData: " + request);
String host = request.getUri().getHost(); String host = request.getUri().getHost();
if (TextUtils.isEmpty(host)) host = getLocalNodeId(); if (TextUtils.isEmpty(host)) host = getLocalNodeId();
ContentValues prepared = new ContentValues(); DataItemInternal dataItem = new DataItemInternal(host, request.getUri().getPath());
prepared.put("sourceNode", getLocalNodeId()); for (Map.Entry<String, Asset> assetEntry : request.getAssets().entrySet()) {
prepared.put("deleted", false); Asset asset = prepareAsset(packageName, assetEntry.getValue());
prepared.put("data", request.getData()); if (asset != null) {
prepared.put("timestampMs", System.currentTimeMillis()); dataItem.addAsset(assetEntry.getKey(), asset);
prepared.put("seqId", 0xFFFFFFFFL);
prepared.put("assetsPresent", false);
nodeDatabase.putDataItem(packageName, PackageUtils.firstSignatureDigest(context, packageName), host, request.getUri().getPath(), prepared);
Map<String, DataItemAssetParcelable> assetMap = new HashMap<String, DataItemAssetParcelable>();
for (String key : request.getAssets().keySet()) {
assetMap.put(key, new DataItemAssetParcelable());
} }
if (!assetMap.isEmpty()) {
prepared.put("assetsPresent", true);
nodeDatabase.putDataItem(packageName, PackageUtils.firstSignatureDigest(context, packageName), host, request.getUri().getPath(), prepared);
} }
callbacks.onPutDataResponse(new PutDataResponse(0, new DataItemParcelable(request.getUri(), assetMap))); dataItem.data = request.getData();
DataItemParcelable parcelable = putDataItem(packageName,
PackageUtils.firstSignatureDigest(context, packageName), getLocalNodeId(), dataItem).toParcelable();
callbacks.onPutDataResponse(new PutDataResponse(0, parcelable));
}
private DataItemRecord putDataItem(String packageName, String signatureDigest, String source, DataItemInternal dataItem) {
DataItemRecord record = new DataItemRecord();
record.packageName = packageName;
record.signatureDigest = signatureDigest;
record.deleted = false;
record.source = source;
record.dataItem = dataItem;
record.v1SeqId = getNextSeqId();
if (record.source.equals(getLocalNodeId())) record.seqId = record.v1SeqId;
nodeDatabase.putRecord(record);
return record;
}
private Asset prepareAsset(String packageName, Asset asset) {
if (asset.data != null) {
String digest = calculateDigest(asset.data);
File assetFile = createAssetFile(digest);
boolean success = assetFile.exists();
if (!success) {
File tmpFile = new File(assetFile.getParent(), assetFile.getName() + ".tmp");
try {
FileOutputStream stream = new FileOutputStream(tmpFile);
stream.write(asset.data);
stream.close();
success = tmpFile.renameTo(assetFile);
} catch (IOException e) {
}
}
return Asset.createFromRef(digest);
}
return null;
}
private File createAssetFile(String digest) {
File dir = new File(new File(context.getFilesDir(), "assets"), digest.substring(digest.length() - 2, digest.length()));
dir.mkdirs();
return new File(dir, digest + ".asset");
}
private String calculateDigest(byte[] data) {
try {
return Base64.encodeToString(MessageDigest.getInstance("SHA1").digest(data), Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
} }
@Override @Override
@ -124,6 +193,11 @@ public class WearableServiceImpl extends IWearableService.Stub {
public void getDataItems(IWearableCallbacks callbacks) throws RemoteException { public void getDataItems(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "getDataItems: " + callbacks); Log.d(TAG, "getDataItems: " + callbacks);
Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolder(packageName, PackageUtils.firstSignatureDigest(context, packageName)); Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolder(packageName, PackageUtils.firstSignatureDigest(context, packageName));
while (dataHolderItems.moveToNext()) {
Log.d(TAG, "getDataItems[]: path=" + Uri.parse(dataHolderItems.getString(1)).getPath());
}
dataHolderItems.moveToFirst();
dataHolderItems.moveToPrevious();
callbacks.onDataHolder(DataHolder.fromCursor(dataHolderItems, 0, null)); callbacks.onDataHolder(DataHolder.fromCursor(dataHolderItems, 0, null));
} }
@ -137,7 +211,12 @@ public class WearableServiceImpl extends IWearableService.Stub {
@Override @Override
public void deleteDataItems(IWearableCallbacks callbacks, Uri uri) throws RemoteException { public void deleteDataItems(IWearableCallbacks callbacks, Uri uri) throws RemoteException {
Log.d(TAG, "deleteDataItems: " + uri); Log.d(TAG, "deleteDataItems: " + uri);
nodeDatabase.deleteDataItem(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath()); int count = nodeDatabase.deleteDataItems(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath());
callbacks.onDeleteDataItemsResponse(new DeleteDataItemsResponse(0, count));
}
@Override
public void optInCloudSync(IWearableCallbacks callbacks, boolean enable) throws RemoteException {
callbacks.onStatus(Status.SUCCESS); callbacks.onStatus(Status.SUCCESS);
} }
@ -159,11 +238,15 @@ public class WearableServiceImpl extends IWearableService.Stub {
@Override @Override
public void addListener(IWearableCallbacks callbacks, AddListenerRequest request) throws RemoteException { public void addListener(IWearableCallbacks callbacks, AddListenerRequest request) throws RemoteException {
Log.d(TAG, "addListener[nyp]: " + request); Log.d(TAG, "addListener[nyp]: " + request);
listeners.add(request.listener);
callbacks.onStatus(Status.SUCCESS);
} }
@Override @Override
public void removeListener(IWearableCallbacks callbacks, RemoveListenerRequest request) throws RemoteException { public void removeListener(IWearableCallbacks callbacks, RemoveListenerRequest request) throws RemoteException {
Log.d(TAG, "removeListener[nyp]: " + request); Log.d(TAG, "removeListener[nyp]: " + request);
listeners.remove(request.listener);
callbacks.onStatus(Status.SUCCESS);
} }
@Override @Override
@ -194,10 +277,65 @@ public class WearableServiceImpl extends IWearableService.Stub {
} }
} }
@Override
public void disableConnection(IWearableCallbacks callbacks, String name) throws RemoteException {
configDatabase.setEnabledState(name, false);
callbacks.onStatus(Status.SUCCESS);
}
@Override @Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (super.onTransact(code, data, reply, flags)) return true; if (super.onTransact(code, data, reply, flags)) return true;
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags); Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
return false; return false;
} }
@Override
public void onDataChanged(DataHolder data) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onDataChanged(data);
}
}
@Override
public void onMessageReceived(MessageEventParcelable messageEvent) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onMessageReceived(messageEvent);
}
}
@Override
public void onPeerConnected(NodeParcelable node) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onPeerConnected(node);
}
}
@Override
public void onPeerDisconnected(NodeParcelable node) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onPeerDisconnected(node);
}
}
@Override
public void onConnectedNodes(List<NodeParcelable> nodes) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onConnectedNodes(nodes);
}
}
@Override
public void onChannelEvent(ChannelEventParcelable channelEvent) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onChannelEvent(channelEvent);
}
}
@Override
public void onConnectedCapabilityChanged(CapabilityInfoParcelable capabilityInfo) throws RemoteException {
for (IWearableListener listener : listeners) {
listener.onConnectedCapabilityChanged(capabilityInfo);
}
}
} }