| // 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(); |
| } |
| } |