blob: be75c39c2b7784ffa3d0bb8e0f3650afb2b068aa [file] [log] [blame]
package io.v.todos.sharing;
import android.app.Fragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.SwitchCompat;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.Toast;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import io.v.todos.R;
import io.v.todos.persistence.syncbase.SyncbasePersistence;
import io.v.v23.context.VContext;
import io.v.v23.discovery.Advertisement;
import io.v.v23.verror.VException;
/**
* A fragment encapsulating menu options and functionality related to list sharing.
*/
public class NeighborhoodFragment extends Fragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String
FRAGMENT_TAG = NeighborhoodFragment.class.getSimpleName();
private static final String PREF_ADVERTISE_NEIGHBORHOOD = "advertise neighborhood";
private static SharedPreferences sPrefs;
private static VContext sAdvertiseContext;
private static Advertisement sAd;
// This has to be a field because registerOnSharedPreferenceChangeListener does not keep a hard
// reference to the listener, making it otherwise susceptible to garbage collection.
private static final SharedPreferences.OnSharedPreferenceChangeListener sSharedPrefListener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (PREF_ADVERTISE_NEIGHBORHOOD.equals(key)) {
updateSharePresence();
}
}
};
/**
* Initializes advertisement of one's presence to the neighborhood and watches shared
* preferences to toggle sharing on and off as the preference is changed.
*/
public static void initSharePresence() {
sPrefs = sPrefs != null ? sPrefs : PreferenceManager.getDefaultSharedPreferences
(SyncbasePersistence.getAppContext());
sAd = new Advertisement();
sAd.setInterfaceName(Sharing.getPresenceInterface());
// TODO(alexfandrianto): Revisit why we must put an address inside the advertisement.
// If we are advertising our presence, then there isn't any need for it.
// For now, put our email address in the addresses, despite it being an attribute.
sAd.getAddresses().add(SyncbasePersistence.getPersonalEmail());
sPrefs.registerOnSharedPreferenceChangeListener(sSharedPrefListener);
updateSharePresence();
}
private static void updateSharePresence() {
if (isAdvertising()) {
if (sAdvertiseContext == null) {
sAdvertiseContext = SyncbasePersistence.getAppVContext().withCancel();
try {
Futures.addCallback(Sharing.getDiscovery().advertise(sAdvertiseContext, sAd,
// TODO(rosswang): Restrict to contacts only. However, per discussion
// with mattr@ and ashankar@, this could be a scalability challenge as
// the advertisement would need to be IB-encrypted for each possible
// recipient. This can be broken into multiple advertisements but at the
// increased risk of over-the-air hash collision.
null),
new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
}
@Override
public void onFailure(@NonNull Throwable t) {
handleAdvertisingError(t);
}
});
} catch (VException e) {
handleAdvertisingError(e);
}
}
} else if (sAdvertiseContext != null) {
sAdvertiseContext.cancel();
sAdvertiseContext = null;
}
}
private static void handleAdvertisingError(Throwable t) {
SyncbasePersistence.getAppErrorReporter().onError(R.string.err_share_location, t);
setAdvertiseNeighborhood(false);
}
private static void setAdvertiseNeighborhood(boolean value) {
sPrefs.edit()
.putBoolean(PREF_ADVERTISE_NEIGHBORHOOD, value)
.apply();
}
private static boolean isAdvertising() {
return sPrefs.getBoolean(PREF_ADVERTISE_NEIGHBORHOOD, true);
}
private SwitchCompat mSwitch;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.neighborhood, menu);
MenuItem menuItem = menu.findItem(R.id.advertise_neighborhood);
mSwitch = (SwitchCompat) menuItem.getActionView().
findViewById(R.id.neighborhood_switch);
mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setAdvertiseNeighborhood(isChecked);
}
});
sPrefs = sPrefs != null ? sPrefs : PreferenceManager.getDefaultSharedPreferences
(getActivity());
sPrefs.registerOnSharedPreferenceChangeListener(this);
updateAdvertiseNeighborhoodChecked();
}
@Override
public void onDestroyOptionsMenu() {
sPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PREF_ADVERTISE_NEIGHBORHOOD.equals(key)) {
updateAdvertiseNeighborhoodChecked();
}
}
private static Boolean sLastToastCheck; // Used to track the last toasted value.
private void updateAdvertiseNeighborhoodChecked() {
boolean value = isAdvertising();
mSwitch.setChecked(value);
mSwitch.setThumbResource(value ?
R.drawable.sharing_activated_0_5x :
R.drawable.sharing_deactivated_0_5x);
// If this was a change, then toast information to the user.
if (sLastToastCheck == null || sLastToastCheck != value) {
sLastToastCheck = value;
Toast.makeText(getActivity(), value ?
R.string.presence_on : R.string.presence_off, Toast.LENGTH_SHORT).show();
}
}
}