Java tutorial
// 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 android.support.annotation.Nullable; import org.apache.commons.io.FileUtils; import org.joda.time.Duration; import java.io.File; import java.io.IOException; import java.io.Serializable; 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.RequiredArgsConstructor; import lombok.Value; import lombok.experimental.Accessors; import lombok.experimental.Wither; 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#getEndpoint()}. */ @Slf4j public class SyncbaseAndroidService extends Service { public static final String EXTRA_OPTIONS = "EXTRA_OPTIONS"; @RequiredArgsConstructor @Wither @Accessors(prefix = "") // override default 'm' prefix since these are public fields public static class Options implements Serializable { /** * Whether or not Syncbase data are cleared prior to service startup. Defaults to false. */ public final boolean cleanStart; /** * Time to keep the Syncbase service alive after the last (local) client disconnects. * Defaults to 5 minutes. */ public final Duration keepAlive; /** * Name to mount at. If `null`, the service is not initially mounted externally. Defaults to * `null`. */ public final @Nullable String name; /** * Name of the Vanadium proxy to use. If `null`, no proxy is used. Defaults to `"proxy"`. */ public final @Nullable String proxy; public Options() { this(false, Duration.standardMinutes(5), null, "proxy"); } } private static final Duration STOP_TIMEOUT = Duration.standardSeconds(5); @Accessors(prefix = "m") @Value public static class BindResult { Server mServer; String mEndpoint; } private VContext mVContext; private Observable<BindResult> mObservable; private Subscription mKill; private Options mOptions; public class Binder extends android.os.Binder { private Binder() { } public Observable<BindResult> getObservable() { return mObservable; } } @Override public void onCreate() { mVContext = V.init(this); } @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(mOptions.keepAlive.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"); mOptions = (Options) intent.getSerializableExtra(EXTRA_OPTIONS); if (mOptions == null) { mOptions = new Options(); } if (mOptions.cleanStart) { log.info("Clearing Syncbase data per intent"); try { FileUtils.deleteDirectory(storageRoot); } catch (final IOException e) { log.error("Could not clear Syncbase data", e); } } VContext serverContext = mVContext; if (mOptions.proxy != null) { try { serverContext = V.withListenSpec(mVContext, V.getListenSpec(mVContext).withProxy(mOptions.proxy)); } catch (final VException e) { log.warn("Unable to set up Vanadium proxy for Syncbase", e); } } storageRoot.mkdirs(); log.info("Starting Syncbase"); final VContext sbCtx = SyncbaseServer.withNewServer(serverContext, new SyncbaseServer.Params().withPermissions(BlessingsUtils.OPEN_DATA_PERMS) .withStorageRootDir(storageRoot.getAbsolutePath()).withName(mOptions.name)); // name is ignored if null final Server server = V.getServer(sbCtx); return new BindResult(server, "/" + server.getStatus().getEndpoints()[0]); } }