blob: 1656b60845da32aa26771748e953e51f17dbb900 [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.syncslides.discovery;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import io.v.syncslides.db.VDeck;
import io.v.syncslides.model.Deck;
import io.v.syncslides.model.DeckImpl;
import io.v.syncslides.model.DynamicList;
import io.v.syncslides.model.ListListener;
import io.v.syncslides.model.Person;
import io.v.syncslides.model.PresentationAdvertisement;
import io.v.v23.InputChannel;
import io.v.v23.InputChannels;
import io.v.v23.VIterable;
import io.v.v23.context.CancelableVContext;
import io.v.v23.context.VContext;
import io.v.v23.discovery.Attributes;
import io.v.v23.discovery.Service;
import io.v.v23.discovery.Update;
import io.v.v23.discovery.VDiscovery;
import io.v.v23.verror.VException;
import static io.v.v23.VFutures.sync;
/**
* Scans for live presentations using Vanadium Discovery. When it does find those advertisements,
* it issues an RPC to fetch the details. The scanning is started/stopped when the first/last
* listener is added/removed.
*/
class RpcScanner implements DynamicList<PresentationAdvertisement> {
private static final String TAG = "RpcScanner";
private static final String QUERY = "v.InterfaceName=\"" +
RpcPresentationDiscovery.INTERFACE_NAME + "\"";
private final VContext mBaseContext;
private final VDiscovery mDiscovery;
private final ClientFactory mClientFactory;
private final Set<ListListener> mListeners;
private final ExecutorService mExecutor;
private final List<PresentationAdvertisement> mElems;
private final Handler mHandler;
private CancelableVContext mCurrentContext;
public RpcScanner(VContext context, VDiscovery discovery, ClientFactory clientFactory) {
mBaseContext = context;
mDiscovery = discovery;
mClientFactory = clientFactory;
mListeners = Sets.newHashSet();
mExecutor = Executors.newSingleThreadExecutor();
mElems = Lists.newArrayList();
mHandler = new Handler(Looper.getMainLooper());
}
@Override
public int getItemCount() {
return mElems.size();
}
@Override
public PresentationAdvertisement get(int i) {
return mElems.get(i);
}
@Override
public void addListener(final ListListener listener) {
mListeners.add(listener);
if (mListeners.size() == 1) {
// First listener. Start the thread.
mCurrentContext = mBaseContext.withCancel();
mExecutor.submit(() -> scan());
}
mHandler.post(() -> listener.notifyDataSetChanged());
}
@Override
public void removeListener(ListListener listener) {
mListeners.remove(listener);
if (mListeners.isEmpty()) {
// Stop the scan via cancel.
mCurrentContext.cancel();
mCurrentContext = null;
mHandler.removeCallbacksAndMessages(null);
}
}
private void addAdvertisement(PresentationAdvertisement ad) {
mElems.add(ad);
for (ListListener listener : mListeners) {
listener.notifyItemInserted(mElems.size() - 1);
}
}
private void removeAdvertisement(String id) {
for (int i = 0; i < mElems.size(); i++) {
if (id.equals(mElems.get(i).getId())) {
mElems.remove(i);
for (ListListener listener : mListeners) {
listener.notifyItemRemoved(i);
}
}
}
}
private void handleError(Exception e) {
for (ListListener listener : mListeners) {
listener.onError(e);
}
}
// Runs in a background thread.
private void scan() {
InputChannel<Update> updateChannel = mDiscovery.scan(mCurrentContext, QUERY);
final VIterable<Update> updates = InputChannels.asIterable(updateChannel);
for (Update update : updates) {
if (update instanceof Update.Found) {
Service descriptor = ((Update.Found) update).getElem().getService();
Attributes attrs = descriptor.getAttrs();
String remoteDevice = attrs.get(RpcPresentationDiscovery.DEVICE_ID_ATTRIBUTE);
if (remoteDevice != null &&
remoteDevice.equals(RpcPresentationDiscovery.DEVICE_ID)) {
// Self advertisement.
continue;
}
fetchDetails(descriptor);
} else {
String id = ((Update.Lost) update).getElem().getInstanceId();
mHandler.post(() -> removeAdvertisement(id));
}
}
if (updates.error() != null) {
mHandler.post(() -> handleError(updates.error()));
}
}
// Runs in a background thread.
private void fetchDetails(Service descriptor) {
List<String> addresses = descriptor.getAddrs();
if (addresses.isEmpty()) {
Log.e(TAG, "Descriptor " + descriptor.getInstanceId() + " had no addrs.");
return;
}
String name = "/" + addresses.get(0);
LivePresentationClient client = mClientFactory.make(name);
VContext rpcContext = mCurrentContext.withCancel();
PresentationInfo info;
try {
info = sync(client.getInfo(rpcContext));
} catch (VException e) {
Log.e(TAG, "Failed to fetch presentation info: " + e.getMessage());
return;
}
VDeck vDeck = info.getDeck();
Deck deck = new DeckImpl(vDeck.getTitle(), vDeck.getThumbnail(), info.getDeckId());
Person person = new Person(info.getPerson().getBlessing(), info.getPerson().getName());
final PresentationAdvertisement ad = new PresentationAdvertisement(
descriptor.getInstanceId(), person, deck, info.getSyncgroupName());
mHandler.post(() -> addAdvertisement(ad));
}
}