blob: 142db3f86a45d46691906ab2c43c3ebfe3dc0d6c [file] [log] [blame]
// Copyright 2015 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.v.debug;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import org.apache.commons.io.FileUtils;
import org.joda.time.Duration;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import io.v.android.v23.V;
import io.v.baku.toolkit.blessings.BlessingsUtils;
import io.v.impl.google.services.syncbase.SyncbaseServer;
import io.v.rx.VFn;
import io.v.v23.context.VContext;
import io.v.v23.rpc.Server;
import io.v.v23.verror.VException;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import rx.Observable;
import rx.Subscription;
import rx.schedulers.Schedulers;
import rx.util.async.Async;
/**
* Syncbase Android service in lieu of GMS Core Syncbase. Exposes Syncbase as a simplistic bound
* service, relying on Vanadium RPC in lieu of IPC, with open permissions, returning the local
* Vanadium name in {@link Binder#getObservable()}...{@link BindResult#getName()}.
*/
@Slf4j
public class SyncbaseAndroidService extends Service {
public static final String
EXTRA_CLEAN_START = "EXTRA_CLEAN_START",
EXTRA_KEEP_ALIVE = "EXTRA_KEEP_ALIVE";
public static final Duration DEFAULT_KEEP_ALIVE = Duration.standardMinutes(5);
private static final Duration STOP_TIMEOUT = Duration.standardSeconds(5);
@Accessors(prefix = "m")
@Value
public static class BindResult {
Server mServer;
String mName;
}
private VContext mVContext;
private Observable<BindResult> mObservable;
private Subscription mKill;
private Duration mKeepAlive;
public class Binder extends android.os.Binder {
private Binder() {
}
public Observable<BindResult> getObservable() {
return mObservable;
}
}
@Override
public void onCreate() {
mVContext = V.init(this);
// If we don't proxy, it seems we can't even mount.
try {
mVContext = V.withListenSpec(mVContext, V.getListenSpec(mVContext).withProxy("proxy"));
} catch (final VException e) {
log.warn("Unable to set up Vanadium proxy for Syncbase", e);
}
}
@Override
public void onDestroy() {
try {
mObservable.doOnNext(VFn.unchecked(b -> {
log.info("Stopping Syncbase");
mVContext.cancel();
}))
.timeout(STOP_TIMEOUT.getMillis(), TimeUnit.MILLISECONDS)
.toBlocking()
.single();
log.info("Syncbase is over");
// TODO(rosswang): https://github.com/vanadium/issues/issues/809
System.exit(0);
} catch (final RuntimeException e) {
log.error("Failed to shut down Syncbase", e);
System.exit(1);
}
}
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
ensureStarted(intent);
return START_NOT_STICKY;
}
@Override
public IBinder onBind(final Intent intent) {
ensureStarted(intent);
return new Binder();
}
@Override
public boolean onUnbind(final Intent intent) {
mKill = Observable.timer(mKeepAlive.getMillis(), TimeUnit.MILLISECONDS)
.subscribe(x -> stopSelf());
return true;
}
@Override
public void onRebind(final Intent intent) {
mKill.unsubscribe();
}
private void ensureStarted(final Intent intent) {
if (mObservable == null) {
mObservable = Async.fromCallable(() -> startServer(intent), Schedulers.io())
.replay(1).autoConnect(0); //cache last result; connect immediately
}
}
private BindResult startServer(final Intent intent) throws SyncbaseServer.StartException {
final File storageRoot = new File(getFilesDir(), "syncbase");
mKeepAlive = (Duration)intent.getSerializableExtra(EXTRA_KEEP_ALIVE);
if (mKeepAlive == null) {
mKeepAlive = DEFAULT_KEEP_ALIVE;
}
if (intent.getBooleanExtra(EXTRA_CLEAN_START, false)) {
log.info("Clearing Syncbase data per intent");
try {
FileUtils.deleteDirectory(storageRoot);
} catch (final IOException e) {
log.error("Could not clear Syncbase data", e);
}
}
storageRoot.mkdirs();
log.info("Starting Syncbase");
final VContext sbCtx = SyncbaseServer.withNewServer(mVContext, new SyncbaseServer.Params()
.withPermissions(BlessingsUtils.OPEN_DATA_PERMS)
.withStorageRootDir(storageRoot.getAbsolutePath()));
final Server server = V.getServer(sbCtx);
return new BindResult(server, "/" + server.getStatus().getEndpoints()[0]);
}
}