package cz.destil.wearsquare.service; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.SparseArray; import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.PutDataMapRequest; import com.mariux.teleport.lib.TeleportService; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import cz.destil.wearsquare.R; import cz.destil.wearsquare.api.Api; import cz.destil.wearsquare.api.CheckIns; import cz.destil.wearsquare.api.ExploreVenues; import cz.destil.wearsquare.api.SearchVenues; import cz.destil.wearsquare.core.App; import cz.destil.wearsquare.data.Preferences; import cz.destil.wearsquare.util.ImageUtils; import cz.destil.wearsquare.util.IntentUtils; import cz.destil.wearsquare.util.L; import cz.destil.wearsquare.util.LocationUtils; import retrofit.Call; import retrofit.Callback; import retrofit.Response; import retrofit.Retrofit; /** * Receives all communication from wearable and processes it. * * @author David Vávra (david@vavra.me) */ public class FoursquareService extends TeleportService { SparseArray<Target> mTargets; // need to hold strong reference to targets, because Picasso holds WeakReferences @Override public void onCreate() { super.onCreate(); setOnGetMessageCallback(new OnGetMessageCallback() { @Override public void onCallback(String path) { handleMessage(path); } }); } /** * Handle all messages here. */ private void handleMessage(String path) { if (path.equals("/check-in-list")) { if (Preferences.hasFoursquareToken()) { downloadCheckInList(); } else { sendError(getString(R.string.please_connect_foursquare_first)); } } else if (path.startsWith("check-in")) { sendCheckIn(path); } else if (path.startsWith("/explore-list")) { if (Preferences.hasFoursquareToken()) { downloadExploreList(); } else { sendError(getString(R.string.please_connect_foursquare_first)); } } else if (path.startsWith("/navigate")) { IntentUtils.launchNavigation(path); } else if (path.startsWith("/open")) { IntentUtils.openOnPhone(path); } else if (path.startsWith("exception:")) { IntentUtils.sendEmail(path.split("exception:")[1]); } } /** * Downloads explore list of venues. */ private void downloadExploreList() { try { LocationUtils.getLastLocation(new LocationUtils.LocationListener() { @Override public void onLocationUpdate(String location) { Call<ExploreVenues.ExploreVenuesResponse> call = Api.get().create(ExploreVenues.class).best(location); call.enqueue(new Callback<ExploreVenues.ExploreVenuesResponse>() { @Override public void onResponse(Response<ExploreVenues.ExploreVenuesResponse> response, Retrofit retrofit) { syncExploreToWear(response.body().getVenues()); } @Override public void onFailure(Throwable t) { sendError(getString(R.string.connect_to_internet)); } } ); } }); } catch (SecurityException e) { sendError(getString(R.string.please_grant_location_permission)); } } /** * Downloads list of venues for a check-in. */ private void downloadCheckInList() { try { LocationUtils.getLastLocation(new LocationUtils.LocationListener() { @Override public void onLocationUpdate(String location) { Call<SearchVenues.SearchResponse> call = Api.get().create(SearchVenues.class).searchForCheckIn(location); call.enqueue(new Callback<SearchVenues.SearchResponse>() { @Override public void onResponse(Response<SearchVenues.SearchResponse> response, Retrofit retrofit) { syncCheckInListToWear(response.body().getVenues()); } @Override public void onFailure(Throwable t) { sendError(getString(R.string.connect_to_internet)); } }); } }); } catch (SecurityException e) { sendError(getString(R.string.please_grant_location_permission)); } } /** * Actually pushes a check-in to 4sq. */ private void sendCheckIn(String path) { Uri uri = Uri.parse(path); List<String> pathSegments = uri.getPathSegments(); final String id = pathSegments.get(1); final String shout = pathSegments.size() > 2 ? pathSegments.get(2) : ""; LocationUtils.getLastLocation(new LocationUtils.LocationListener() { @Override public void onLocationUpdate(String location) { Call<CheckIns.CheckInResponse> call = Api.get() .create(CheckIns.class) .add(id, location, LocationUtils.getLastAccuracy(), LocationUtils.getLastAltitude(), Preferences.getBroadcast(), shout); call.enqueue(new Callback<CheckIns.CheckInResponse>() { @Override public void onResponse(Response<CheckIns.CheckInResponse> response, Retrofit retrofit) { // ignore for now, maybe log it to some check-in log in the future } @Override public void onFailure(Throwable t) { sendError(getString(R.string.connect_to_internet)); } }); } }); } /** * Sends error to wearable. */ private void sendError(String message) { L.e(message); PutDataMapRequest data = PutDataMapRequest.createWithAutoAppendedId("/error"); data.getDataMap().putString("error_message", message); syncDataItem(data); } /** * Pushes list of explore venues to wear and starts downloading images. */ private void syncExploreToWear(final List<ExploreVenues.Venue> venues) { final ArrayList<DataMap> dataVenues = new ArrayList<>(); Set<String> images = new HashSet<>(); for (final ExploreVenues.Venue venue : venues) { final DataMap dataMap = new DataMap(); dataMap.putString("id", venue.id); dataMap.putString("name", venue.name); dataMap.putString("tip", venue.tip); dataMap.putDouble("latitude", venue.latitude); dataMap.putDouble("longitude", venue.longitude); if (venue.imageUrl != null) { dataMap.putString("image_url", venue.imageUrl); images.add(venue.imageUrl); } dataVenues.add(dataMap); } PutDataMapRequest data = PutDataMapRequest.createWithAutoAppendedId("/explore-list"); data.getDataMap().putDataMapArrayList("explore_venues", dataVenues); syncDataItem(data); downloadImages(images); } /** * Pushes check-in list of venues to wearable and starts downloading images. */ private void syncCheckInListToWear(final List<SearchVenues.Venue> venues) { final ArrayList<DataMap> dataVenues = new ArrayList<>(); Set<String> images = new HashSet<>(); for (final SearchVenues.Venue venue : venues) { final DataMap dataMap = new DataMap(); dataMap.putString("id", venue.id); dataMap.putString("name", venue.name); dataMap.putString("image_url", venue.getCategoryIconUrl()); dataVenues.add(dataMap); images.add(venue.getCategoryIconUrl()); } PutDataMapRequest data = PutDataMapRequest.createWithAutoAppendedId("/check-in-list"); data.getDataMap().putDataMapArrayList("check_in_venues", dataVenues); DataMap emojiMap = new DataMap(); emojiMap.putStringArrayList("emojis", Preferences.getEmojis()); data.getDataMap().putDataMap("emojis", emojiMap); syncDataItem(data); downloadImages(images); } /** * Downloads images in parallel and pushes them to wearable as Assets. */ private void downloadImages(Set<String> imageUrls) { int i = 0; mTargets = new SparseArray<>(); // needs strong reference for (final String imageUrl : imageUrls) { mTargets.put(i, new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { Asset asset = ImageUtils.createAssetFromBitmap(bitmap); final PutDataMapRequest data = PutDataMapRequest.createWithAutoAppendedId("/image"); data.getDataMap().putString("image_url", imageUrl); data.getDataMap().putAsset("asset", asset); syncDataItem(data); } @Override public void onBitmapFailed(Drawable errorDrawable) { final PutDataMapRequest data = PutDataMapRequest.createWithAutoAppendedId("/image"); data.getDataMap().putString("image_url", imageUrl); syncDataItem(data); } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } }); Picasso.with(App.get()).load(imageUrl).into(mTargets.get(i)); i++; } } }