Update Wearable

- Add support for deleting dataitems
- Refactoring
- Update wearable-lib
This commit is contained in:
Marvin W 2016-05-18 14:09:00 +02:00
parent 4ea10d9049
commit db013606e0
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
8 changed files with 338 additions and 129 deletions

2
extern/Wearable vendored

@ -1 +1 @@
Subproject commit 5c24adaa3928de68167ce31c0fa5b9b1a3256677
Subproject commit 666ef2a8628b1c76701a2d5f138d4fd90751b5e9

View File

@ -17,6 +17,7 @@
package org.microg.gms.common;
import android.os.IInterface;
import android.support.annotation.NonNull;
import android.util.Log;
import java.lang.reflect.InvocationHandler;
@ -25,17 +26,22 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class MultiListenerProxy<T extends IInterface> implements InvocationHandler {
private static final String TAG = "GmsMultiListener";
public static <T extends IInterface> T get(Class<T> tClass, final Collection<T> listeners) {
return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, new MultiListenerProxy<T>(listeners));
return get(tClass, new CollectionListenerPool<T>(listeners));
}
private final Collection<T> listeners;
public static <T extends IInterface> T get(Class<T> tClass, final ListenerPool<T> listenerPool) {
return (T) Proxy.newProxyInstance(tClass.getClassLoader(), new Class[]{tClass}, new MultiListenerProxy<T>(listenerPool));
}
private MultiListenerProxy(Collection<T> listeners) {
private final ListenerPool<T> listeners;
private MultiListenerProxy(ListenerPool<T> listeners) {
this.listeners = listeners;
}
@ -44,14 +50,175 @@ public class MultiListenerProxy<T extends IInterface> implements InvocationHandl
for (T listener : new HashSet<T>(listeners)) {
try {
method.invoke(listener, args);
} catch (IllegalAccessException e) {
} catch (Exception e) {
Log.w(TAG, e);
listeners.remove(listener);
} catch (InvocationTargetException e) {
Log.w(TAG, e.getTargetException());
listeners.remove(listener);
}
}
return null;
}
public static abstract class ListenerPool<T> implements Collection<T> {
@Override
public boolean addAll(Collection<? extends T> collection) {
return false;
}
@Override
public boolean add(T object) {
return false;
}
@Override
public boolean containsAll(Collection<?> collection) {
for (Object o : collection) {
if (!contains(o)) return false;
}
return true;
}
@Override
public boolean removeAll(Collection<?> collection) {
boolean x = true;
for (Object o : collection) {
if (!remove(o)) x = false;
}
return x;
}
@Override
public boolean retainAll(Collection<?> collection) {
return false;
}
@NonNull
@Override
public Object[] toArray() {
throw new IllegalArgumentException();
}
@NonNull
@Override
public <T1> T1[] toArray(T1[] array) {
throw new IllegalArgumentException();
}
}
private static class CollectionListenerPool<T> extends ListenerPool<T> {
private Collection<T> listeners;
public CollectionListenerPool(Collection<T> listeners) {
this.listeners = listeners;
}
@Override
public void clear() {
listeners.clear();
}
@Override
public boolean contains(Object object) {
return listeners.contains(object);
}
@Override
public boolean isEmpty() {
return listeners.isEmpty();
}
@NonNull
@Override
public Iterator<T> iterator() {
return listeners.iterator();
}
@Override
public boolean remove(Object object) {
return listeners.remove(object);
}
@Override
public int size() {
return listeners.size();
}
}
public static class MultiCollectionListenerPool<T> extends ListenerPool<T> {
private Collection<? extends Collection<T>> multiCol;
public MultiCollectionListenerPool(Collection<? extends Collection<T>> multiCol) {
this.multiCol = multiCol;
}
@Override
public void clear() {
for (Collection<T> ts : multiCol) {
ts.clear();
}
}
@Override
public boolean contains(Object object) {
for (Collection<T> ts : multiCol) {
if (ts.contains(object)) return true;
}
return false;
}
@Override
public boolean isEmpty() {
for (Collection<T> ts : multiCol) {
if (!ts.isEmpty()) return false;
}
return true;
}
@NonNull
@Override
public Iterator<T> iterator() {
final Iterator<? extends Collection<T>> interMed = multiCol.iterator();
return new Iterator<T>() {
private Iterator<T> med;
@Override
public boolean hasNext() {
while ((med == null || !med.hasNext()) && interMed.hasNext()) {
med = interMed.next().iterator();
}
return med != null && med.hasNext();
}
@Override
public T next() {
while (med == null || !med.hasNext()) {
med = interMed.next().iterator();
}
return med.next();
}
@Override
public void remove() {
med.remove();
}
};
}
@Override
public boolean remove(Object object) {
for (Collection<T> ts : multiCol) {
if (ts.remove(object)) return true;
}
return false;
}
@Override
public int size() {
int sum = 0;
for (Collection<T> ts : multiCol) {
sum += ts.size();
}
return sum;
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2013-2016 microG 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.Context;
import android.content.SharedPreferences;
import java.util.UUID;
public class ClockworkNodePreferences {
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_NEXT_SEQ_ID_BLOCK = "nextSeqIdBlock";
private static final Object lock = new Object();
private static long seqIdBlock;
private static long seqIdInBlock = -1;
private Context context;
public ClockworkNodePreferences(Context context) {
this.context = context;
}
public String getLocalNodeId() {
SharedPreferences preferences = context.getSharedPreferences(CLOCKWORK_NODE_PREFERENCES, Context.MODE_PRIVATE);
String nodeId = preferences.getString(CLOCKWORK_NODE_PREFERENCE_NODE_ID, null);
if (nodeId == null) {
nodeId = UUID.randomUUID().toString();
preferences.edit().putString(CLOCKWORK_NODE_PREFERENCE_NODE_ID, nodeId).apply();
}
return nodeId;
}
public long getNextSeqId() {
synchronized (lock) {
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++;
}
}
}

View File

@ -58,7 +58,7 @@ public class MessageHandler extends ServerMessageListener {
.networkId(networkId)
.peerAndroidId(androidId)
.unknown4(3)
.unknown5(1)
.peerVersion(1)
.build());
this.wearable = wearable;
this.thisNodeId = config.nodeId;
@ -83,6 +83,12 @@ public class MessageHandler extends ServerMessageListener {
}
}
@Override
public void onDisconnected() {
wearable.onDisconnectReceived(getConnection(), thisNodeId, getRemoteConnect());
super.onDisconnected();
}
@Override
public void onSetAsset(SetAsset setAsset) {
Log.d(TAG, "onSetAsset: " + setAsset);
@ -132,8 +138,7 @@ public class MessageHandler extends ServerMessageListener {
@Override
public void onRpcRequest(Request rpcRequest) {
Log.d(TAG, "onRpcRequest: " + rpcRequest);
if (TextUtils.isEmpty(rpcRequest.targetNodeId)) {
// TODO: That's probably not how it should go!
if (TextUtils.isEmpty(rpcRequest.targetNodeId) || rpcRequest.targetNodeId.equals(thisNodeId)) {
MessageEventParcelable messageEvent = new MessageEventParcelable();
messageEvent.data = rpcRequest.rawData != null ? rpcRequest.rawData.toByteArray() : null;
messageEvent.path = rpcRequest.path;

View File

@ -1,68 +0,0 @@
/*
* Copyright 2013-2015 microG 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.util.Log;
import com.google.android.gms.wearable.ConnectionConfiguration;
import org.microg.wearable.SocketWearableConnection;
import org.microg.wearable.WearableConnection;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
class NetworkConnectionThread extends Thread {
private static final String TAG = "GmsWearNetConnThr";
private static final int WEAR_TCP_PORT = 5601;
private ConnectionConfiguration config;
private ServerSocket socket;
private MessageHandler handler;
private WearableConnection wearableConnection;
public NetworkConnectionThread(WearableImpl wearable, ConnectionConfiguration config) {
this.config = config;
this.handler = new MessageHandler(wearable, config);
}
public void close() {
try {
socket.close();
} catch (IOException e) {
Log.w(TAG, e);
}
}
public WearableConnection getWearableConnection() {
return wearableConnection;
}
@Override
public void run() {
try {
socket = new ServerSocket(WEAR_TCP_PORT);
Log.d(TAG, "Listening for connections on TCP :" + WEAR_TCP_PORT);
Socket accepted = socket.accept();
(wearableConnection = new SocketWearableConnection(accepted, handler)).run();
Log.d(TAG, "Connection terminated, me too");
} catch (IOException e) {
Log.w(TAG, e);
}
}
}

View File

@ -25,6 +25,8 @@ import android.text.TextUtils;
import com.google.android.gms.wearable.Asset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class NodeDatabaseHelper extends SQLiteOpenHelper {
@ -34,8 +36,11 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
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;
private ClockworkNodePreferences clockworkNodePreferences;
public NodeDatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
clockworkNodePreferences = new ClockworkNodePreferences(context);
}
@Override
@ -198,27 +203,32 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
return getReadableDatabase().query("dataItemsAndAssets", GDIBHAP_FIELDS, selection, new String[]{nodeId, Long.toString(seqId)}, null, null, "seqId", null);
}
public synchronized int deleteDataItems(String packageName, String signatureDigest, String host, String path) {
public synchronized List<DataItemRecord> deleteDataItems(String packageName, String signatureDigest, String host, String path) {
List<DataItemRecord> updated = new ArrayList<DataItemRecord>();
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;
record.seqId = clockworkNodePreferences.getNextSeqId();
record.v1SeqId = record.seqId;
updateRecord(db, cursor.getString(0), record);
n++;
updated.add(record);
}
db.setTransactionSuccessful();
db.endTransaction();
return n;
return updated;
}
public long getCurrentSeqId(String sourceNode) {
if (TextUtils.isEmpty(sourceNode)) return 1;
SQLiteDatabase db = getReadableDatabase();
return getCurrentSeqId(getReadableDatabase(), sourceNode);
}
private long getCurrentSeqId(SQLiteDatabase db, String sourceNode) {
Cursor cursor = db.query("dataItemsAndAssets", new String[]{"seqId"}, "sourceNode=?", new String[]{sourceNode}, null, null, "seqId DESC", "1");
long res = 1;
if (cursor != null) {

View File

@ -17,10 +17,10 @@
package org.microg.gms.wearable;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@ -37,6 +37,7 @@ import com.google.android.gms.wearable.internal.PutDataRequest;
import org.microg.gms.common.MultiListenerProxy;
import org.microg.gms.common.PackageUtils;
import org.microg.gms.common.Utils;
import org.microg.wearable.SocketConnectionThread;
import org.microg.wearable.WearableConnection;
import org.microg.wearable.proto.AckAsset;
import org.microg.wearable.proto.AppKey;
@ -54,12 +55,14 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import okio.ByteString;
@ -67,48 +70,28 @@ public class WearableImpl {
private static final String TAG = "GmsWear";
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_NEXT_SEQ_ID_BLOCK = "nextSeqIdBlock";
private static final int WEAR_TCP_PORT = 5601;
private final Context context;
private final NodeDatabaseHelper nodeDatabase;
private final ConfigurationDatabaseHelper configDatabase;
private final Set<IWearableListener> listeners = new HashSet<IWearableListener>();
private final Map<String, List<IWearableListener>> listeners = new HashMap<String, List<IWearableListener>>();
private final Set<Node> connectedNodes = new HashSet<Node>();
private final Set<WearableConnection> activeConnections = new HashSet<WearableConnection>();
private NetworkConnectionThread nct;
private SocketConnectionThread sct;
private ConnectionConfiguration[] configurations;
private boolean configurationsUpdated = false;
private long seqIdBlock;
private long seqIdInBlock = -1;
private ClockworkNodePreferences clockworkNodePreferences;
public WearableImpl(Context context, NodeDatabaseHelper nodeDatabase, ConfigurationDatabaseHelper configDatabase) {
this.context = context;
this.nodeDatabase = nodeDatabase;
this.configDatabase = configDatabase;
this.clockworkNodePreferences = new ClockworkNodePreferences(context);
}
public String getLocalNodeId() {
SharedPreferences preferences = context.getSharedPreferences(CLOCKWORK_NODE_PREFERENCES, Context.MODE_PRIVATE);
String nodeId = preferences.getString(CLOCKWORK_NODE_PREFERENCE_NODE_ID, null);
if (nodeId == null) {
nodeId = UUID.randomUUID().toString();
preferences.edit().putString(CLOCKWORK_NODE_PREFERENCE_NODE_ID, nodeId).apply();
}
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++;
return clockworkNodePreferences.getLocalNodeId();
}
public DataItemRecord putDataItem(String packageName, String signatureDigest, String source, DataItemInternal dataItem) {
@ -118,7 +101,7 @@ public class WearableImpl {
record.deleted = false;
record.source = source;
record.dataItem = dataItem;
record.v1SeqId = getNextSeqId();
record.v1SeqId = clockworkNodePreferences.getNextSeqId();
if (record.source.equals(getLocalNodeId())) record.seqId = record.v1SeqId;
nodeDatabase.putRecord(record);
return record;
@ -127,7 +110,11 @@ public class WearableImpl {
public DataItemRecord putDataItem(DataItemRecord record) {
nodeDatabase.putRecord(record);
try {
getAllListeners().onDataChanged(getDataItemsByUri(record.dataItem.uri, record.packageName));
if (listeners.containsKey(record.packageName)) {
MultiListenerProxy.get(IWearableListener.class, listeners.get(record.packageName)).onDataChanged(getDataItemForRecord(record));
} else {
}
} catch (RemoteException e) {
Log.w(TAG, e);
}
@ -272,7 +259,7 @@ public class WearableImpl {
RootMessage announceMessage = new RootMessage.Builder().setAsset(new SetAsset.Builder()
.digest(asset.getDigest())
.appkeys(new AppKeys(Collections.singletonList(new AppKey(record.packageName, record.signatureDigest))))
.build()).unknown13(true).build();
.build()).hasAsset(true).build();
connection.writeMessage(announceMessage);
File assetFile = createAssetFile(asset.getDigest());
String fileName = calculateDigest(announceMessage.toByteArray());
@ -346,6 +333,16 @@ public class WearableImpl {
onPeerConnected(new NodeParcelable(connect.id, connect.name));
}
public void onDisconnectReceived(WearableConnection connection, String nodeId, Connect connect) {
for (ConnectionConfiguration config : getConfigurations()) {
if (config.nodeId.equals(nodeId)) {
config.connected = false;
}
}
Log.d(TAG, "Removing connection from list of open connections: " + connection);
activeConnections.remove(connection);
onPeerDisconnected(new NodeParcelable(connect.id, connect.name));
}
public List<NodeParcelable> getConnectedNodesParcelableList() {
List<NodeParcelable> nodes = new ArrayList<NodeParcelable>();
@ -356,7 +353,7 @@ public class WearableImpl {
}
public IWearableListener getAllListeners() {
return MultiListenerProxy.get(IWearableListener.class, listeners);
return MultiListenerProxy.get(IWearableListener.class, new MultiListenerProxy.MultiCollectionListenerPool<IWearableListener>(listeners.values()));
}
public void onPeerConnected(NodeParcelable node) {
@ -369,6 +366,16 @@ public class WearableImpl {
addConnectedNode(node);
}
public void onPeerDisconnected(NodeParcelable node) {
Log.d(TAG, "onPeerDisconnected: " + node);
try {
getAllListeners().onPeerDisconnected(node);
} catch (RemoteException e) {
Log.w(TAG, e);
}
removeConnectedNode(node.getId());
}
public void onConnectedNodes(List<NodeParcelable> nodes) {
Log.d(TAG, "onConnectedNodes: " + nodes);
try {
@ -406,7 +413,13 @@ public class WearableImpl {
}
public DataHolder getDataItemsByUri(Uri uri, String packageName) {
Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolderByHostAndPath(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath());
String firstSignature;
try {
firstSignature = PackageUtils.firstSignatureDigest(context, packageName);
} catch (Exception e) {
return null;
}
Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolderByHostAndPath(packageName, firstSignature, uri.getHost(), uri.getPath());
while (dataHolderItems.moveToNext()) {
Log.d(TAG, "getDataItems[]: path=" + Uri.parse(dataHolderItems.getString(1)).getPath());
}
@ -415,30 +428,45 @@ public class WearableImpl {
return DataHolder.fromCursor(dataHolderItems, 0, null);
}
public void addListener(IWearableListener listener) {
listeners.add(listener);
public DataHolder getDataItemForRecord(DataItemRecord record) {
Cursor dataHolderItems = nodeDatabase.getDataItemsForDataHolderByHostAndPath(record.packageName, record.signatureDigest, record.dataItem.uri.getHost(), record.dataItem.uri.getPath());
while (dataHolderItems.moveToNext()) {
Log.d(TAG, "getDataItems[]: path=" + Uri.parse(dataHolderItems.getString(1)).getPath());
}
dataHolderItems.moveToFirst();
dataHolderItems.moveToPrevious();
return DataHolder.fromCursor(dataHolderItems, 0, null);
}
public synchronized void addListener(String packageName, IWearableListener listener) {
if (!listeners.containsKey(packageName)) {
listeners.put(packageName, new ArrayList<IWearableListener>());
}
listeners.get(packageName).add(listener);
}
public void removeListener(IWearableListener listener) {
listeners.add(listener);
for (List<IWearableListener> list : listeners.values()) {
list.remove(listener);
}
}
public void enableConnection(String name) {
configDatabase.setEnabledState(name, true);
configurationsUpdated = true;
if (name.equals("server") && nct == null) {
(nct = new NetworkConnectionThread(this, configDatabase.getConfiguration(name))).start();
if (name.equals("server") && sct == null) {
(sct = SocketConnectionThread.serverListen(WEAR_TCP_PORT, new MessageHandler(this, configDatabase.getConfiguration(name)))).start();
}
}
public void disableConnection(String name) {
configDatabase.setEnabledState(name, false);
configurationsUpdated = true;
if (name.equals("server") && nct != null) {
activeConnections.remove(nct.getWearableConnection());
nct.close();
nct.interrupt();
nct = null;
if (name.equals("server") && sct != null) {
activeConnections.remove(sct.getWearableConnection());
sct.close();
sct.interrupt();
sct = null;
}
}
@ -455,7 +483,11 @@ public class WearableImpl {
}
public int deleteDataItems(Uri uri, String packageName) {
return nodeDatabase.deleteDataItems(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath());
List<DataItemRecord> records = nodeDatabase.deleteDataItems(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath());
for (DataItemRecord record : records) {
syncRecordToAll(record);
}
return records.size();
}
public void sendMessageReceived(MessageEventParcelable messageEvent) {

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.common.api.Status;
@ -113,7 +114,7 @@ public class WearableServiceImpl extends IWearableService.Stub {
public void addListener(IWearableCallbacks callbacks, AddListenerRequest request) throws RemoteException {
Log.d(TAG, "addListener[nyp]: " + request);
if (request.listener != null) {
wearable.addListener(request.listener);
wearable.addListener(packageName, request.listener);
}
callbacks.onStatus(Status.SUCCESS);
}