Wearable temp state

This commit is contained in:
Marvin W 2016-08-04 11:36:03 +02:00
parent 47a61d6034
commit 435b394e3a
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
13 changed files with 716 additions and 85 deletions

2
extern/GmsApi vendored

@ -1 +1 @@
Subproject commit cf2927f275c53099781dbf965acb7c7f8ed36e97
Subproject commit 417ec2585f085c5a599e45aa9ee3b3df6239c628

2
extern/Wearable vendored

@ -1 +1 @@
Subproject commit 796c8b6b87d564e3bc2e5000c6d4ed348c606083
Subproject commit 853f0382b960fcfcad8d7d3655d7963a58095069

View File

@ -129,6 +129,14 @@
</intent-filter>
</service>
<service
android:name="org.microg.gms.wearable.WearableLocationService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED"/>
</intent-filter>
</service>
<!-- Services Framework -->
<provider

View File

@ -72,6 +72,10 @@ public class ConfigurationDatabaseHelper extends SQLiteOpenHelper {
}
public void putConfiguration(ConnectionConfiguration config) {
putConfiguration(config, null);
}
public void putConfiguration(ConnectionConfiguration config, String oldNodeId) {
ContentValues contentValues = new ContentValues();
if (config.name != null) {
contentValues.put("name", config.name);
@ -89,7 +93,11 @@ public class ConfigurationDatabaseHelper extends SQLiteOpenHelper {
contentValues.put("role", config.role);
contentValues.put("connectionEnabled", true);
contentValues.put("nodeId", config.nodeId);
getWritableDatabase().insert(TABLE_NAME, null, contentValues);
if (oldNodeId == null) {
getWritableDatabase().insert(TABLE_NAME, null, contentValues);
} else {
getWritableDatabase().update(TABLE_NAME, contentValues, "nodeId=?", new String[]{oldNodeId});
}
}
public ConnectionConfiguration[] getAllConfigurations() {

View File

@ -51,4 +51,12 @@ public class DataItemInternal {
public Map<String, Asset> getAssets() {
return Collections.unmodifiableMap(new HashMap<String, Asset>(assets));
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("DataItemInternal{");
sb.append("uri=").append(uri);
sb.append('}');
return sb.toString();
}
}

View File

@ -19,6 +19,7 @@ package org.microg.gms.wearable;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import com.google.android.gms.common.data.DataHolder;
import com.google.android.gms.wearable.Asset;

View File

@ -44,7 +44,7 @@ import java.util.Arrays;
public class MessageHandler extends ServerMessageListener {
private static final String TAG = "GmsWearMsgHandler";
private final WearableImpl wearable;
private final String thisNodeId;
private final String oldConfigNodeId;
private String peerNodeId;
public MessageHandler(WearableImpl wearable, ConnectionConfiguration config) {
@ -54,28 +54,28 @@ public class MessageHandler extends ServerMessageListener {
private MessageHandler(WearableImpl wearable, ConnectionConfiguration config, String name, String networkId, long androidId) {
super(new Connect.Builder()
.name(name)
.id(config.nodeId)
.id(wearable.getLocalNodeId())
.networkId(networkId)
.peerAndroidId(androidId)
.unknown4(3)
.peerVersion(1)
.build());
this.wearable = wearable;
this.thisNodeId = config.nodeId;
this.oldConfigNodeId = config.nodeId;
}
@Override
public void onConnect(Connect connect) {
super.onConnect(connect);
peerNodeId = connect.id;
wearable.onConnectReceived(getConnection(), thisNodeId, connect);
wearable.onConnectReceived(getConnection(), oldConfigNodeId, connect);
try {
getConnection().writeMessage(new RootMessage.Builder().syncStart(new SyncStart.Builder()
.receivedSeqId(-1L)
.version(2)
.syncTable(Arrays.asList(
new SyncTableEntry.Builder().key("cloud").value(1L).build(),
new SyncTableEntry.Builder().key(thisNodeId).value(wearable.getCurrentSeqId(thisNodeId)).build(), // TODO
new SyncTableEntry.Builder().key(wearable.getLocalNodeId()).value(wearable.getCurrentSeqId(wearable.getLocalNodeId())).build(), // TODO
new SyncTableEntry.Builder().key(peerNodeId).value(wearable.getCurrentSeqId(peerNodeId)).build() // TODO
)).build()).build());
} catch (IOException e) {
@ -85,7 +85,10 @@ public class MessageHandler extends ServerMessageListener {
@Override
public void onDisconnected() {
wearable.onDisconnectReceived(getConnection(), thisNodeId, getRemoteConnect());
Connect connect = getRemoteConnect();
if (connect == null)
connect = new Connect.Builder().id(oldConfigNodeId).name("Wear device").build();
wearable.onDisconnectReceived(getConnection(), connect);
super.onDisconnected();
}
@ -120,13 +123,13 @@ public class MessageHandler extends ServerMessageListener {
boolean hasLocalNode = false;
if (syncStart.syncTable != null) {
for (SyncTableEntry entry : syncStart.syncTable) {
wearable.syncToPeer(getConnection(), entry.key, entry.value);
wearable.syncToPeer(peerNodeId, entry.key, entry.value);
if (wearable.getLocalNodeId().equals(entry.key)) hasLocalNode = true;
}
} else {
Log.d(TAG, "No sync table given.");
}
if (!hasLocalNode) wearable.syncToPeer(getConnection(), wearable.getLocalNodeId(), 0);
if (!hasLocalNode) wearable.syncToPeer(peerNodeId, wearable.getLocalNodeId(), 0);
}
@Override
@ -138,7 +141,7 @@ public class MessageHandler extends ServerMessageListener {
@Override
public void onRpcRequest(Request rpcRequest) {
Log.d(TAG, "onRpcRequest: " + rpcRequest);
if (TextUtils.isEmpty(rpcRequest.targetNodeId) || rpcRequest.targetNodeId.equals(thisNodeId)) {
if (TextUtils.isEmpty(rpcRequest.targetNodeId) || rpcRequest.targetNodeId.equals(wearable.getLocalNodeId())) {
MessageEventParcelable messageEvent = new MessageEventParcelable();
messageEvent.data = rpcRequest.rawData != null ? rpcRequest.rawData.toByteArray() : null;
messageEvent.path = rpcRequest.path;

View File

@ -22,6 +22,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.gms.wearable.Asset;
@ -73,18 +74,25 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
String selection;
if (path == null) {
params = new String[]{packageName, signatureDigest};
selection = "packageName =? AND signatureDigest =?";
selection = "packageName = ? AND signatureDigest = ?";
} else if (host == null) {
if (path.endsWith("/")) path = path + "%";
params = new String[]{packageName, signatureDigest, path};
selection = "packageName =? AND signatureDigest =? AND path =?";
selection = "packageName = ? AND signatureDigest = ? AND path LIKE ?";
} else {
if (path.endsWith("/")) path = path + "%";
params = new String[]{packageName, signatureDigest, host, path};
selection = "packageName =? AND signatureDigest =? AND host =? AND path =?";
selection = "packageName = ? AND signatureDigest = ? AND host = ? AND path LIKE ?";
}
selection += " AND deleted=0 AND assetsPresent !=0";
return getReadableDatabase().rawQuery("SELECT host AS host,path AS path,data AS data,\'\' AS tags,assetname AS asset_key,assets_digest AS asset_id FROM dataItemsAndAssets WHERE " + selection, params);
}
public synchronized Cursor getDataItemsByHostAndPath(String packageName, String signatureDigest, String host, String path) {
Log.d(TAG, "getDataItemsByHostAndPath: " + packageName + ", " + signatureDigest + ", " + host + ", " + path);
return getDataItemsByHostAndPath(getReadableDatabase(), packageName, signatureDigest, host, path);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != VERSION) {
@ -240,7 +248,7 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
return res;
}
public void putAsset(Asset asset, boolean dataPresent) {
public synchronized void putAsset(Asset asset, boolean dataPresent) {
ContentValues cv = new ContentValues();
cv.put("digest", asset.getDigest());
cv.put("dataPresent", dataPresent ? 1 : 0);
@ -248,11 +256,39 @@ public class NodeDatabaseHelper extends SQLiteOpenHelper {
getWritableDatabase().insertWithOnConflict("assets", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
}
public void allowAssetAccess(String digest, String packageName, String signatureDigest) {
public synchronized void allowAssetAccess(String digest, String packageName, String signatureDigest) {
SQLiteDatabase db = getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("assets_digest", digest);
cv.put("appkeys_id", getAppKey(db, packageName, signatureDigest));
db.insert("assetsacls", null, cv);
db.insertWithOnConflict("assetsacls", null, cv, SQLiteDatabase.CONFLICT_REPLACE);
}
public Cursor listMissingAssets() {
return getReadableDatabase().query("dataItemsAndAssets", GDIBHAP_FIELDS, "assetsPresent = 0 AND assets_digest NOT NULL", null, null, null, "packageName, signatureDigest, host, path");
}
public boolean hasAsset(Asset asset) {
Cursor cursor = getReadableDatabase().query("assets", new String[]{"dataPresent"}, "digest=?", new String[]{asset.getDigest()}, null, null, null);
if (cursor == null) return false;
try {
return (cursor.moveToNext() && cursor.getInt(0) == 1);
} finally {
cursor.close();
}
}
public synchronized void markAssetAsPresent(String digest) {
ContentValues cv = new ContentValues();
cv.put("dataPresent", 1);
SQLiteDatabase db = getWritableDatabase();
db.update("assets", cv, "digest=?", new String[]{digest});
Cursor status = db.query("assetsReadyStatus", null, "nowReady != markedReady", null, null, null, null);
while (status.moveToNext()) {
cv = new ContentValues();
cv.put("assetsPresent", status.getInt(status.getColumnIndex("nowReady")));
db.update("dataitems", cv, "_id=?", new String[]{Integer.toString(status.getInt(status.getColumnIndex("dataitems_id")))});
}
status.close();
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.HashMap;
import java.util.Map;
public class RpcHelper {
private final Map<String, RpcConnectionState> rpcStateMap = new HashMap<String, RpcConnectionState>();
private final SharedPreferences preferences;
private final Context context;
public RpcHelper(Context context) {
this.context = context;
this.preferences = context.getSharedPreferences("wearable.rpc_service.settings", 0);
}
private String getRpcConnectionId(String packageName, String targetNodeId, String path) {
String mode = "lo";
if (packageName.equals("com.google.android.wearable.app") && path.startsWith("/s3"))
mode = "hi";
return targetNodeId + ":" + mode;
}
public RpcHelper.RpcConnectionState useConnectionState(String packageName, String targetNodeId, String path) {
String rpcConnectionId = getRpcConnectionId(packageName, targetNodeId, path);
synchronized (rpcStateMap) {
if (!rpcStateMap.containsKey(rpcConnectionId)) {
int g = preferences.getInt(rpcConnectionId, 1)+1;
preferences.edit().putInt(rpcConnectionId, g).apply();
rpcStateMap.put(rpcConnectionId, new RpcConnectionState(g));
}
RpcHelper.RpcConnectionState res = rpcStateMap.get(rpcConnectionId);
res.lastRequestId++;
return res.freeze();
}
}
public static class RpcConnectionState {
public int generation;
public int lastRequestId;
public RpcConnectionState(int generation) {
this.generation = generation;
}
public RpcConnectionState freeze() {
RpcConnectionState res = new RpcConnectionState(generation);
res.lastRequestId = lastRequestId;
return res;
}
}
}

View File

@ -44,7 +44,9 @@ import org.microg.wearable.proto.AckAsset;
import org.microg.wearable.proto.AppKey;
import org.microg.wearable.proto.AppKeys;
import org.microg.wearable.proto.Connect;
import org.microg.wearable.proto.FetchAsset;
import org.microg.wearable.proto.FilePiece;
import org.microg.wearable.proto.Request;
import org.microg.wearable.proto.RootMessage;
import org.microg.wearable.proto.SetAsset;
@ -76,7 +78,8 @@ public class WearableImpl {
private final ConfigurationDatabaseHelper configDatabase;
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 final Map<String, WearableConnection> activeConnections = new HashMap<String, WearableConnection>();
private RpcHelper rpcHelper;
private SocketConnectionThread sct;
private ConnectionConfiguration[] configurations;
private boolean configurationsUpdated = false;
@ -87,6 +90,7 @@ public class WearableImpl {
this.nodeDatabase = nodeDatabase;
this.configDatabase = configDatabase;
this.clockworkNodePreferences = new ClockworkNodePreferences(context);
this.rpcHelper = new RpcHelper(context);
}
public String getLocalNodeId() {
@ -108,6 +112,13 @@ public class WearableImpl {
public DataItemRecord putDataItem(DataItemRecord record) {
nodeDatabase.putRecord(record);
if (!record.assetsAreReady) {
for (Asset asset : record.dataItem.getAssets().values()) {
if (!nodeDatabase.hasAsset(asset)) {
Log.d(TAG, "Asset is missing: " + asset);
}
}
}
try {
getListener(record.packageName, "com.google.android.gms.wearable.DATA_CHANGED", record.dataItem.uri)
.onDataChanged(record.toEventDataHolder());
@ -151,7 +162,7 @@ public class WearableImpl {
return null;
}
private File createAssetFile(String digest) {
public File createAssetFile(String digest) {
File dir = new File(new File(context.getFilesDir(), "assets"), digest.substring(digest.length() - 2));
dir.mkdirs();
return new File(dir, digest + ".asset");
@ -183,6 +194,7 @@ public class WearableImpl {
if (newConfiguration.name.equals(configuration.name)) {
newConfiguration.connected = configuration.connected;
newConfiguration.peerNodeId = configuration.peerNodeId;
newConfiguration.nodeId = configuration.nodeId;
break;
}
}
@ -211,52 +223,50 @@ public class WearableImpl {
return context;
}
public void syncToPeer(WearableConnection connection, String nodeId, long seqId) {
Log.d(TAG, "-- Start syncing over " + connection + ", nodeId " + nodeId + " starting with seqId " + seqId);
public void syncToPeer(String peerNodeId, String nodeId, long seqId) {
Log.d(TAG, "-- Start syncing over to " + peerNodeId + ", nodeId " + nodeId + " starting with seqId " + seqId);
Cursor cursor = nodeDatabase.getModifiedDataItems(nodeId, seqId, true);
if (cursor != null) {
while (cursor.moveToNext()) {
if (!syncRecordToPeer(connection, DataItemRecord.fromCursor(cursor))) break;
if (!syncRecordToPeer(peerNodeId, DataItemRecord.fromCursor(cursor))) break;
}
cursor.close();
}
Log.d(TAG, "-- Done syncing over " + connection + ", nodeId " + nodeId + " starting with seqId " + seqId);
Log.d(TAG, "-- Done syncing over to " + peerNodeId + ", nodeId " + nodeId + " starting with seqId " + seqId);
}
private void syncRecordToAll(DataItemRecord record) {
Log.d(TAG, "Syncing record " + record + " over " + activeConnections.size() + " connections.");
for (WearableConnection connection : new ArrayList<WearableConnection>(activeConnections)) {
if (!syncRecordToPeer(connection, record)) {
Log.d(TAG, "Removing connection as it seems not usable: " + connection);
activeConnections.remove(connection);
}
for (String nodeId : new ArrayList<String>(activeConnections.keySet())) {
syncRecordToPeer(nodeId, record);
}
}
private boolean syncRecordToPeer(WearableConnection connection, DataItemRecord record) {
private boolean syncRecordToPeer(String nodeId, DataItemRecord record) {
for (Asset asset : record.dataItem.getAssets().values()) {
syncAssetToPeer(connection, record, asset);
syncAssetToPeer(nodeId, record, asset);
}
Log.d(TAG, "Sync over " + connection + ": " + record);
Log.d(TAG, "Sync over to " + nodeId + ": " + record);
try {
connection.writeMessage(new RootMessage.Builder().setDataItem(record.toSetDataItem()).build());
activeConnections.get(nodeId).writeMessage(new RootMessage.Builder().setDataItem(record.toSetDataItem()).build());
} catch (IOException e) {
closeConnection(nodeId);
Log.w(TAG, e);
return false;
}
return true;
}
private void syncAssetToPeer(WearableConnection connection, DataItemRecord record, Asset asset) {
private void syncAssetToPeer(String nodeId, DataItemRecord record, Asset asset) {
try {
Log.d(TAG, "Sync over " + connection + ": " + asset);
Log.d(TAG, "Sync over to " + nodeId + ": " + asset);
RootMessage announceMessage = new RootMessage.Builder().setAsset(new SetAsset.Builder()
.digest(asset.getDigest())
.appkeys(new AppKeys(Collections.singletonList(new AppKey(record.packageName, record.signatureDigest))))
.build()).hasAsset(true).build();
connection.writeMessage(announceMessage);
activeConnections.get(nodeId).writeMessage(announceMessage);
File assetFile = createAssetFile(asset.getDigest());
String fileName = calculateDigest(announceMessage.toByteArray());
FileInputStream fis = new FileInputStream(assetFile);
@ -265,15 +275,16 @@ public class WearableImpl {
int c = 0;
while ((c = fis.read(arr)) > 0) {
if (lastPiece != null) {
Log.d(TAG, "Sync over " + connection + ": Asset piece for fileName " + fileName + ": " + lastPiece);
connection.writeMessage(new RootMessage.Builder().filePiece(new FilePiece(fileName, false, lastPiece, null)).build());
Log.d(TAG, "Sync over to " + nodeId + ": Asset piece for fileName " + fileName + ": " + lastPiece);
activeConnections.get(nodeId).writeMessage(new RootMessage.Builder().filePiece(new FilePiece(fileName, false, lastPiece, null)).build());
}
lastPiece = ByteString.of(arr, 0, c);
}
Log.d(TAG, "Sync over " + connection + ": Last asset piece for fileName " + fileName + ": " + lastPiece);
connection.writeMessage(new RootMessage.Builder().filePiece(new FilePiece(fileName, true, lastPiece, asset.getDigest())).build());
Log.d(TAG, "Sync over to " + nodeId + ": Last asset piece for fileName " + fileName + ": " + lastPiece);
activeConnections.get(nodeId).writeMessage(new RootMessage.Builder().filePiece(new FilePiece(fileName, true, lastPiece, asset.getDigest())).build());
} catch (IOException e) {
Log.w(TAG, e);
closeConnection(nodeId);
}
}
@ -303,7 +314,7 @@ public class WearableImpl {
String digest = calculateDigest(Utils.readStreamToEnd(new FileInputStream(file)));
if (digest.equals(finalPieceDigest)) {
if (file.renameTo(createAssetFile(digest))) {
// TODO: Mark as stored in db
nodeDatabase.markAssetAsPresent(digest);
connection.writeMessage(new RootMessage.Builder().ackAsset(new AckAsset(digest)).build());
} else {
Log.w(TAG, "Could not rename to target file name. delete=" + file.delete());
@ -320,23 +331,47 @@ public class WearableImpl {
public void onConnectReceived(WearableConnection connection, String nodeId, Connect connect) {
for (ConnectionConfiguration config : getConfigurations()) {
if (config.nodeId.equals(nodeId)) {
if (config.nodeId != nodeId) {
config.nodeId = connect.id;
configDatabase.putConfiguration(config, nodeId);
}
config.peerNodeId = connect.id;
config.connected = true;
}
}
Log.d(TAG, "Adding connection to list of open connections: " + connection);
activeConnections.add(connection);
Log.d(TAG, "Adding connection to list of open connections: " + connection + " with connect " + connect);
activeConnections.put(connect.id, connection);
onPeerConnected(new NodeParcelable(connect.id, connect.name));
// Fetch missing assets
Cursor cursor = nodeDatabase.listMissingAssets();
if (cursor != null) {
while (cursor.moveToNext()) {
try {
Log.d(TAG, "Fetch for " + cursor.getString(12));
connection.writeMessage(new RootMessage.Builder()
.fetchAsset(new FetchAsset.Builder()
.assetName(cursor.getString(12))
.packageName(cursor.getString(1))
.signatureDigest(cursor.getString(2))
.permission(false)
.build()).build());
} catch (IOException e) {
Log.w(TAG, e);
closeConnection(connect.id);
}
}
cursor.close();
}
}
public void onDisconnectReceived(WearableConnection connection, String nodeId, Connect connect) {
public void onDisconnectReceived(WearableConnection connection, Connect connect) {
for (ConnectionConfiguration config : getConfigurations()) {
if (config.nodeId.equals(nodeId)) {
if (config.nodeId.equals(connect.id)) {
config.connected = false;
}
}
Log.d(TAG, "Removing connection from list of open connections: " + connection);
activeConnections.remove(connection);
activeConnections.remove(connect.id);
onPeerDisconnected(new NodeParcelable(connect.id, connect.name));
}
@ -421,7 +456,7 @@ public class WearableImpl {
}
dataHolderItems.moveToFirst();
dataHolderItems.moveToPrevious();
return DataHolder.fromCursor(dataHolderItems, 0, null);
return new DataHolder(dataHolderItems, 0, null);
}
public DataHolder getDataItemForRecordAsHolder(DataItemRecord record) {
@ -431,7 +466,7 @@ public class WearableImpl {
}
dataHolderItems.moveToFirst();
dataHolderItems.moveToPrevious();
return DataHolder.fromCursor(dataHolderItems, 0, null);
return new DataHolder(dataHolderItems, 0, null);
}
public synchronized void addListener(String packageName, IWearableListener listener) {
@ -451,6 +486,7 @@ public class WearableImpl {
configDatabase.setEnabledState(name, true);
configurationsUpdated = true;
if (name.equals("server") && sct == null) {
Log.d(TAG, "Starting server on :" + WEAR_TCP_PORT);
(sct = SocketConnectionThread.serverListen(WEAR_TCP_PORT, new MessageHandler(this, configDatabase.getConfiguration(name)))).start();
}
}
@ -497,7 +533,7 @@ public class WearableImpl {
}
public DataItemRecord getDataItemByUri(Uri uri, String packageName) {
Cursor cursor = nodeDatabase.getDataItemsForDataHolderByHostAndPath(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath());
Cursor cursor = nodeDatabase.getDataItemsByHostAndPath(packageName, PackageUtils.firstSignatureDigest(context, packageName), uri.getHost(), uri.getPath());
DataItemRecord record = null;
if (cursor != null) {
if (cursor.moveToNext()) {
@ -505,6 +541,7 @@ public class WearableImpl {
}
cursor.close();
}
Log.d(TAG, "getDataItem: " + record);
return record;
}
@ -522,7 +559,49 @@ public class WearableImpl {
}
}
public void sendMessage(String targetNodeId, String path, byte[] data) {
Log.d(TAG, "sendMessage not yet implemented!");
private void closeConnection(String nodeId) {
WearableConnection connection = activeConnections.get(nodeId);
try {
connection.close();
} catch (IOException e1) {
Log.w(TAG, e1);
}
if (connection == sct.getWearableConnection()) {
sct.close();
sct = null;
}
activeConnections.remove(nodeId);
for (ConnectionConfiguration config : getConfigurations()) {
if (config.nodeId.equals(nodeId) || config.peerNodeId.equals(nodeId)) {
config.connected = false;
}
}
onPeerDisconnected(new NodeParcelable(nodeId, "Wear device"));
Log.d(TAG, "Closed connection to " + nodeId + " on error");
}
public int sendMessage(String packageName, String targetNodeId, String path, byte[] data) {
if (activeConnections.containsKey(targetNodeId)) {
WearableConnection connection = activeConnections.get(targetNodeId);
RpcHelper.RpcConnectionState state = rpcHelper.useConnectionState(packageName, targetNodeId, path);
try {
connection.writeMessage(new RootMessage.Builder().rpcRequest(new Request.Builder()
.targetNodeId(targetNodeId)
.path(path)
.rawData(ByteString.of(data))
.packageName(packageName)
.signatureDigest(PackageUtils.firstSignatureDigest(context, packageName))
.sourceNodeId(getLocalNodeId())
.generation(state.generation)
.requestId(state.lastRequestId)
.build()).build());
} catch (IOException e) {
Log.w(TAG, "Error while writing, closing link", e);
closeConnection(targetNodeId);
return -1;
}
return (state.generation + 527) * 31 + state.lastRequestId;
}
return -1;
}
}

View File

@ -0,0 +1,152 @@
/*
* 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.pm.PackageManager;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.data.DataHolder;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.internal.ClientIdentity;
import com.google.android.gms.location.internal.LocationRequestInternal;
import com.google.android.gms.wearable.internal.AmsEntityUpdateParcelable;
import com.google.android.gms.wearable.internal.AncsNotificationParcelable;
import com.google.android.gms.wearable.internal.CapabilityInfoParcelable;
import com.google.android.gms.wearable.internal.ChannelEventParcelable;
import com.google.android.gms.wearable.internal.IWearableListener;
import com.google.android.gms.wearable.internal.MessageEventParcelable;
import com.google.android.gms.wearable.internal.NodeParcelable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class WearableLocationListener extends IWearableListener.Stub {
public static final String LOCATION_REQUESTS = "com/google/android/location/fused/wearable/LOCATION_REQUESTS";
public static final String CAPABILITY_QUERY = "com/google/android/location/fused/wearable/CAPABILITY_QUERY";
private static final String TAG = "GmsWearLocListener";
private WearableLocationService locationService;
public WearableLocationListener(WearableLocationService locationService) {
this.locationService = locationService;
}
@Override
public void onDataChanged(DataHolder data) throws RemoteException {
}
@Override
public void onMessageReceived(MessageEventParcelable messageEvent) throws RemoteException {
if (messageEvent.getPath().equals(LOCATION_REQUESTS)) {
//DataMap dataMap = DataMap.fromByteArray(messageEvent.getData());
//locationService.onLocationRequests(messageEvent.getSourceNodeId(), parseLocationRequestList(dataMap, locationService), dataMap.getBoolean("TRIGGER_UPDATE", false));
} else if (messageEvent.getPath().equals(CAPABILITY_QUERY)) {
locationService.onCapabilityQuery(messageEvent.getSourceNodeId());
}
}
@Override
public void onPeerConnected(NodeParcelable node) throws RemoteException {
}
@Override
public void onPeerDisconnected(NodeParcelable node) throws RemoteException {
locationService.onLocationRequests(node.getId(), Collections.<LocationRequestInternal>emptyList(), false);
}
@Override
public void onConnectedNodes(List<NodeParcelable> nodes) throws RemoteException {
}
@Override
public void onNotificationReceived(AncsNotificationParcelable notification) throws RemoteException {
}
@Override
public void onChannelEvent(ChannelEventParcelable channelEvent) throws RemoteException {
}
@Override
public void onConnectedCapabilityChanged(CapabilityInfoParcelable capabilityInfo) throws RemoteException {
}
@Override
public void onEntityUpdate(AmsEntityUpdateParcelable update) throws RemoteException {
}
/*public static Collection<LocationRequestInternal> parseLocationRequestList(DataMap dataMap, Context context) {
if (!dataMap.containsKey("REQUEST_LIST")) {
Log.w(TAG, "malformed DataMap: missing key REQUEST_LIST");
return Collections.emptyList();
}
List<DataMap> requestMapList = dataMap.getDataMapArrayList("REQUEST_LIST");
List<LocationRequestInternal> locationRequests = new ArrayList<LocationRequestInternal>();
for (DataMap map : requestMapList) {
locationRequests.add(parseLocationRequest(map, context));
}
return locationRequests;
}
private static LocationRequestInternal parseLocationRequest(DataMap dataMap, Context context) {
LocationRequestInternal request = new LocationRequestInternal();
request.triggerUpdate = true;
request.request = new LocationRequest();
request.clients = Collections.emptyList();
if (dataMap.containsKey("PRIORITY"))
request.request.setPriority(dataMap.getInt("PRIORITY", 0));
if (dataMap.containsKey("INTERVAL_MS"))
request.request.setInterval(dataMap.getLong("INTERVAL_MS", 0));
if (dataMap.containsKey("FASTEST_INTERVAL_MS"))
request.request.setFastestInterval(dataMap.getLong("FASTEST_INTERVAL_MS", 0));
//if (dataMap.containsKey("MAX_WAIT_TIME_MS"))
if (dataMap.containsKey("SMALLEST_DISPLACEMENT_METERS"))
request.request.setSmallestDisplacement(dataMap.getFloat("SMALLEST_DISPLACEMENT_METERS", 0));
if (dataMap.containsKey("NUM_UPDATES"))
request.request.setNumUpdates(dataMap.getInt("NUM_UPDATES", 0));
if (dataMap.containsKey("EXPIRATION_DURATION_MS"))
request.request.setExpirationDuration(dataMap.getLong("EXPIRATION_DURATION_MS", 0));
if (dataMap.containsKey("TAG"))
request.tag = dataMap.getString("TAG");
if (dataMap.containsKey("CLIENTS_PACKAGE_ARRAY")) {
String[] packages = dataMap.getStringArray("CLIENTS_PACKAGE_ARRAY");
if (packages != null) {
request.clients = new ArrayList<ClientIdentity>();
for (String packageName : packages) {
request.clients.add(generateClientIdentity(packageName, context));
}
}
}
return request;
}
private static ClientIdentity generateClientIdentity(String packageName, Context context) {
return null;
try {
return new ClientIdentity(context.getPackageManager().getApplicationInfo(packageName, 0).uid, packageName);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unknown client identity: " + packageName, e);
return new ClientIdentity(context.getApplicationInfo().uid, context.getPackageName());
}
}*/
}

View File

@ -0,0 +1,55 @@
/*
* 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.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import com.google.android.gms.location.internal.LocationRequestInternal;
import java.util.Collection;
public class WearableLocationService extends Service {
// TODO: Implement and use WearableListenerService
private static final String TAG = "GmsWearLocSvc";
private WearableLocationListener listener;
@Override
public void onCreate() {
listener = new WearableLocationListener(this);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals("com.google.android.gms.wearable.BIND_LISTENER")) {
return listener.asBinder();
}
return null;
}
public void onLocationRequests(String nodeId, Collection<LocationRequestInternal> requests, boolean triggerUpdate) {
}
public void onCapabilityQuery(String nodeId) {
}
}

View File

@ -19,24 +19,33 @@ package org.microg.gms.wearable;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.ConnectionConfiguration;
import com.google.android.gms.wearable.internal.AddListenerRequest;
import com.google.android.gms.wearable.internal.AncsNotificationParcelable;
import com.google.android.gms.wearable.internal.DeleteDataItemsResponse;
import com.google.android.gms.wearable.internal.GetCloudSyncSettingResponse;
import com.google.android.gms.wearable.internal.GetConfigResponse;
import com.google.android.gms.wearable.internal.GetConfigsResponse;
import com.google.android.gms.wearable.internal.GetConnectedNodesResponse;
import com.google.android.gms.wearable.internal.GetDataItemResponse;
import com.google.android.gms.wearable.internal.GetFdForAssetResponse;
import com.google.android.gms.wearable.internal.GetLocalNodeResponse;
import com.google.android.gms.wearable.internal.IChannelStreamCallbacks;
import com.google.android.gms.wearable.internal.IWearableCallbacks;
import com.google.android.gms.wearable.internal.IWearableService;
import com.google.android.gms.wearable.internal.NodeParcelable;
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.SendMessageResponse;
import java.io.FileNotFoundException;
public class WearableServiceImpl extends IWearableService.Stub {
private static final String TAG = "GmsWearSvcImpl";
@ -51,6 +60,50 @@ public class WearableServiceImpl extends IWearableService.Stub {
this.packageName = packageName;
}
/*
* Config
*/
@Override
public void putConfig(IWearableCallbacks callbacks, ConnectionConfiguration config) throws RemoteException {
wearable.createConnection(config);
callbacks.onStatus(Status.SUCCESS);
}
@Override
public void deleteConfig(IWearableCallbacks callbacks, String name) throws RemoteException {
wearable.deleteConnection(name);
callbacks.onStatus(Status.SUCCESS);
}
@Override
public void getConfigs(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "getConfigs");
try {
callbacks.onGetConfigsResponse(new GetConfigsResponse(0, wearable.getConfigurations()));
} catch (Exception e) {
callbacks.onGetConfigsResponse(new GetConfigsResponse(8, new ConnectionConfiguration[0]));
}
}
@Override
public void enableConfig(IWearableCallbacks callbacks, String name) throws RemoteException {
Log.d(TAG, "enableConfig: " + name);
wearable.enableConnection(name);
callbacks.onStatus(Status.SUCCESS);
}
@Override
public void disableConfig(IWearableCallbacks callbacks, String name) throws RemoteException {
Log.d(TAG, "disableConfig: " + name);
wearable.disableConnection(name);
callbacks.onStatus(Status.SUCCESS);
}
/*
* DataItems
*/
@Override
public void putData(IWearableCallbacks callbacks, PutDataRequest request) throws RemoteException {
@ -74,32 +127,88 @@ public class WearableServiceImpl extends IWearableService.Stub {
@Override
public void getDataItems(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "getDataItems: " + callbacks);
callbacks.onDataHolder(wearable.getDataItemsAsHolder(packageName));
callbacks.onDataItemChanged(wearable.getDataItemsAsHolder(packageName));
}
@Override
public void sendMessage(IWearableCallbacks callbacks, String targetNodeId, String path, byte[] data) throws RemoteException {
Log.d(TAG, "sendMessage: " + targetNodeId + " / " + path);
wearable.sendMessage(targetNodeId, path, data);
public void getDataItemsByUri(IWearableCallbacks callbacks, Uri uri) throws RemoteException {
getDataItemsByUriWithFilter(callbacks, uri, 0);
}
@Override
public void getDataItemsByUri(IWearableCallbacks callbacks, Uri uri, int i) throws RemoteException {
public void getDataItemsByUriWithFilter(IWearableCallbacks callbacks, Uri uri, int typeFilter) throws RemoteException {
Log.d(TAG, "getDataItemsByUri: " + uri);
callbacks.onDataHolder(wearable.getDataItemsByUriAsHolder(uri, packageName));
callbacks.onDataItemChanged(wearable.getDataItemsByUriAsHolder(uri, packageName));
}
@Override
public void deleteDataItems(IWearableCallbacks callbacks, Uri uri) throws RemoteException {
deleteDataItemsWithFilter(callbacks, uri, 0);
}
@Override
public void deleteDataItemsWithFilter(IWearableCallbacks callbacks, Uri uri, int typeFilter) throws RemoteException {
Log.d(TAG, "deleteDataItems: " + uri);
callbacks.onDeleteDataItemsResponse(new DeleteDataItemsResponse(0, wearable.deleteDataItems(uri, packageName)));
}
@Override
public void sendMessage(IWearableCallbacks callbacks, String targetNodeId, String path, byte[] data) throws RemoteException {
Log.d(TAG, "sendMessage: " + targetNodeId + " / " + path);
SendMessageResponse sendMessageResponse = new SendMessageResponse();
try {
sendMessageResponse.resultId = wearable.sendMessage(packageName, targetNodeId, path, data);
if (sendMessageResponse.resultId == -1) {
sendMessageResponse.statusCode = 4000;
}
} catch (Exception e) {
sendMessageResponse.statusCode = 8;
}
callbacks.onSendMessageResponse(sendMessageResponse);
}
@Override
public void getFdForAsset(IWearableCallbacks callbacks, Asset asset) throws RemoteException {
Log.d(TAG, "getFdForAsset " + asset);
// TODO: Access control
try {
callbacks.onGetFdForAssetResponse(new GetFdForAssetResponse(0, ParcelFileDescriptor.open(wearable.createAssetFile(asset.getDigest()), ParcelFileDescriptor.MODE_READ_ONLY)));
} catch (FileNotFoundException e) {
callbacks.onGetFdForAssetResponse(new GetFdForAssetResponse(8, null));
}
}
@Override
public void optInCloudSync(IWearableCallbacks callbacks, boolean enable) throws RemoteException {
callbacks.onStatus(Status.SUCCESS);
}
@Override
@Deprecated
public void getCloudSyncOptInDone(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getCloudSyncOptInDone");
}
@Override
public void setCloudSyncSetting(IWearableCallbacks callbacks, boolean enable) throws RemoteException {
Log.d(TAG, "unimplemented Method: setCloudSyncSetting");
}
@Override
public void getCloudSyncSetting(IWearableCallbacks callbacks) throws RemoteException {
callbacks.onGetCloudSyncSettingResponse(new GetCloudSyncSettingResponse(0, false));
}
@Override
public void getCloudSyncOptInStatus(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getCloudSyncOptInStatus");
}
@Override
public void sendRemoteCommand(IWearableCallbacks callbacks, byte b) throws RemoteException {
Log.d(TAG, "unimplemented Method: sendRemoteCommand: " + b);
}
@Override
public void getLocalNode(IWearableCallbacks callbacks) throws RemoteException {
try {
@ -111,13 +220,35 @@ public class WearableServiceImpl extends IWearableService.Stub {
@Override
public void getConnectedNodes(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "getConnectedNodes");
callbacks.onGetConnectedNodesResponse(new GetConnectedNodesResponse(0, wearable.getConnectedNodesParcelableList()));
}
/*
* Capability
*/
@Override
public void getConnectedCapability(IWearableCallbacks callbacks, String s1, int i) throws RemoteException {
Log.d(TAG, "unimplemented Method: getConnectedCapability " + s1 + ", " + i);
}
@Override
public void getConnectedCapaibilties(IWearableCallbacks callbacks, int i) throws RemoteException {
Log.d(TAG, "unimplemented Method: getConnectedCapaibilties: " + i);
}
@Override
public void addLocalCapability(IWearableCallbacks callbacks, String cap) throws RemoteException {
Log.d(TAG, "unimplemented Method: addLocalCapability: " + cap);
}
@Override
public void removeLocalCapability(IWearableCallbacks callbacks, String cap) throws RemoteException {
Log.d(TAG, "unimplemented Method: removeLocalCapability: " + cap);
}
@Override
public void addListener(IWearableCallbacks callbacks, AddListenerRequest request) throws RemoteException {
Log.d(TAG, "addListener[nyp]: " + request);
if (request.listener != null) {
wearable.addListener(packageName, request.listener);
}
@ -126,25 +257,112 @@ public class WearableServiceImpl extends IWearableService.Stub {
@Override
public void removeListener(IWearableCallbacks callbacks, RemoveListenerRequest request) throws RemoteException {
Log.d(TAG, "removeListener[nyp]: " + request);
wearable.removeListener(request.listener);
callbacks.onStatus(Status.SUCCESS);
}
@Override
public void putConfig(IWearableCallbacks callbacks, ConnectionConfiguration config) throws RemoteException {
wearable.createConnection(config);
callbacks.onStatus(Status.SUCCESS);
public void getStrorageInformation(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getStrorageInformation");
}
@Override
public void deleteConfig(IWearableCallbacks callbacks, String name) throws RemoteException {
wearable.deleteConnection(name);
callbacks.onStatus(Status.SUCCESS);
public void clearStorage(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: clearStorage");
}
@Override
public void getConfig(IWearableCallbacks callbacks) throws RemoteException {
public void endCall(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: endCall");
}
@Override
public void acceptRingingCall(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: acceptRingingCall");
}
@Override
public void silenceRinger(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: silenceRinger");
}
/*
* Apple Notification Center Service
*/
@Override
public void injectAncsNotificationForTesting(IWearableCallbacks callbacks, AncsNotificationParcelable notification) throws RemoteException {
Log.d(TAG, "unimplemented Method: injectAncsNotificationForTesting: " + notification);
}
@Override
public void doAncsPositiveAction(IWearableCallbacks callbacks, int i) throws RemoteException {
Log.d(TAG, "unimplemented Method: doAncsPositiveAction: " + i);
}
@Override
public void doAncsNegativeAction(IWearableCallbacks callbacks, int i) throws RemoteException {
Log.d(TAG, "unimplemented Method: doAncsNegativeAction: " + i);
}
@Override
public void openChannel(IWearableCallbacks callbacks, String s1, String s2) throws RemoteException {
Log.d(TAG, "unimplemented Method: openChannel; " + s1 + ", " + s2);
}
/*
* Channels
*/
@Override
public void closeChannel(IWearableCallbacks callbacks, String s) throws RemoteException {
Log.d(TAG, "unimplemented Method: closeChannel: " + s);
}
@Override
public void closeChannelWithError(IWearableCallbacks callbacks, String s, int errorCode) throws RemoteException {
Log.d(TAG, "unimplemented Method: closeChannelWithError:" + s + ", " + errorCode);
}
@Override
public void getChannelInputStream(IWearableCallbacks callbacks, IChannelStreamCallbacks channelCallbacks, String s) throws RemoteException {
Log.d(TAG, "unimplemented Method: getChannelInputStream: " + s);
}
@Override
public void getChannelOutputStream(IWearableCallbacks callbacks, IChannelStreamCallbacks channelCallbacks, String s) throws RemoteException {
Log.d(TAG, "unimplemented Method: getChannelOutputStream: " + s);
}
@Override
public void writeChannelInputToFd(IWearableCallbacks callbacks, String s, ParcelFileDescriptor fd) throws RemoteException {
Log.d(TAG, "unimplemented Method: writeChannelInputToFd: " + s);
}
@Override
public void readChannelOutputFromFd(IWearableCallbacks callbacks, String s, ParcelFileDescriptor fd, long l1, long l2) throws RemoteException {
Log.d(TAG, "unimplemented Method: readChannelOutputFromFd: " + s + ", " + l1 + ", " + l2);
}
@Override
public void syncWifiCredentials(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: syncWifiCredentials");
}
/*
* Connection deprecated
*/
@Override
@Deprecated
public void putConnection(IWearableCallbacks callbacks, ConnectionConfiguration config) throws RemoteException {
Log.d(TAG, "unimplemented Method: putConnection");
}
@Override
@Deprecated
public void getConnection(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "getConfig");
ConnectionConfiguration[] configurations = wearable.getConfigurations();
if (configurations == null || configurations.length == 0) {
@ -155,28 +373,21 @@ public class WearableServiceImpl extends IWearableService.Stub {
}
@Override
public void getConfigs(IWearableCallbacks callbacks) throws RemoteException {
Log.d(TAG, "getConfigs");
try {
callbacks.onGetConfigsResponse(new GetConfigsResponse(0, wearable.getConfigurations()));
} catch (Exception e) {
callbacks.onGetConfigsResponse(new GetConfigsResponse(8, new ConnectionConfiguration[0]));
@Deprecated
public void enableConnection(IWearableCallbacks callbacks) throws RemoteException {
ConnectionConfiguration[] configurations = wearable.getConfigurations();
if (configurations.length > 0) {
enableConfig(callbacks, configurations[0].name);
}
}
@Override
public void enableConnection(IWearableCallbacks callbacks, String name) throws RemoteException {
Log.d(TAG, "enableConnection: " + name);
wearable.enableConnection(name);
callbacks.onStatus(Status.SUCCESS);
}
@Override
public void disableConnection(IWearableCallbacks callbacks, String name) throws RemoteException {
Log.d(TAG, "disableConnection: " + name);
wearable.disableConnection(name);
callbacks.onStatus(Status.SUCCESS);
@Deprecated
public void disableConnection(IWearableCallbacks callbacks) throws RemoteException {
ConnectionConfiguration[] configurations = wearable.getConfigurations();
if (configurations.length > 0) {
disableConfig(callbacks, configurations[0].name);
}
}
@Override