blob: b8805301e689bcde781ea79c03c673200c8c01c4 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.baku.examples.distro;
import android.app.Service;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.IBinder;
import android.provider.ContactsContract;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.jaredrummler.android.device.DeviceName;
import org.joda.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import io.v.android.v23.V;
import io.v.v23.context.VContext;
import io.v.v23.options.RpcServerOptions;
import io.v.v23.rpc.Server;
import io.v.v23.rpc.ServerCall;
import io.v.v23.security.BlessingPattern;
import io.v.v23.security.Blessings;
import io.v.v23.security.VPrincipal;
import io.v.v23.security.VSecurity;
import io.v.v23.vdl.ServerRecvStream;
import io.v.v23.vdl.ServerStream;
import io.v.v23.vdl.VdlAny;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;
import lombok.RequiredArgsConstructor;
public class DistroAndroidService extends Service {
private static final String TAG = DistroAndroidService.class.getSimpleName();
@RequiredArgsConstructor
public static class Session {
public final SettableFuture<Void> completion;
public final ServerRecvStream<State> stream;
}
private static Map<String, Session> sessions = new HashMap<>();
public static Session getSession(final String key) {
return sessions.get(key);
}
public static final String
BLESSINGS_EXTRA = "Blessings";
public static final Duration CHANNEL_TIMEOUT = Duration.standardSeconds(3);
private VContext vContext;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
vContext = V.init(this);
final VPrincipal principal = V.getPrincipal(vContext);
final Blessings blessings;
try {
blessings = (Blessings) VomUtil.decodeFromString(
intent.getStringExtra(BLESSINGS_EXTRA), Blessings.class);
principal.blessingStore().setDefaultBlessings(blessings);
principal.blessingStore().set(blessings, new BlessingPattern("..."));
VSecurity.addToRoots(principal, blessings);
} catch (final VException e) {
// TODO(rosswang): handle this better
Log.e(TAG, "Unable to assume blessings", e);
}
final DistroServer handlers = new DistroServer() {
@Override
public ListenableFuture<String> getDescription(final VContext context,
final ServerCall call) {
final SettableFuture<String> description = SettableFuture.create();
DeviceName.with(DistroAndroidService.this).request((i, e) -> {
final String owner;
final ContentResolver cr = getContentResolver();
try (final Cursor c = cr.query(Uri.withAppendedPath(
ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY),
null, null, null, null)) {
if (c.getCount() > 0) {
c.moveToFirst();
owner = c.getString(c.getColumnIndex(
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)) +
"'s ";
} else {
owner = "";
}
}
final String device = e == null ? i.getName() : DeviceName.getDeviceName();
description.set(owner + device);
});
return description;
}
@Override
public ListenableFuture<Void> cast(final VContext context, final ServerCall call,
final ServerStream<State, State> stream) {
final String key = UUID.randomUUID().toString();
Log.i(TAG, "Hosting new casting session " + key);
final SettableFuture<Void> completion = SettableFuture.create();
completion.addListener(() -> {
sessions.remove(key);
Log.i(TAG, "Terminated casting session " + key);
}, MoreExecutors.directExecutor());
sessions.put(key, new Session(completion, stream));
final Intent intent = new Intent(DistroAndroidService.this, HostActivity.class);
intent.putExtra(HostActivity.SESSION_EXTRA, key);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (final Exception e) {
completion.setException(e);
Log.e(TAG, e.getMessage(), e);
}
return completion;
}
};
VContext listenContextCandidate;
try {
listenContextCandidate = V.withListenSpec(vContext,
V.getListenSpec(vContext).withProxy("proxy"));
} catch (final VException e) {
listenContextCandidate = vContext;
Log.e(TAG, "Unable to listen on proxy", e);
}
final VContext listenContext = listenContextCandidate;
final Server s;
try {
s = V.getServer(V.withNewServer(listenContext, Disco.name(this), handlers,
VSecurity.newAllowEveryoneAuthorizer(), new RpcServerOptions()
.channelTimeout(CHANNEL_TIMEOUT)));
} catch (final VException e) {
throw new RuntimeException(e);
}
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
vContext.cancel();
}
}