package com.ToxicBakery.lollipop.demo;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.LoaderManager;
import android.content.Context;
import android.content.CursorLoader;
import android.content.IntentSender;
import android.content.Loader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.v13.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.ToxicBakery.lollipop.R;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
public class WearableSyncActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor>,
DataApi.DataListener, GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, ViewPager.OnPageChangeListener {
private static final LruCache<String, Bitmap> BITMAP_LRU_CACHE;
private static final int WEAR_SCREEN_SIZE_PX = 320;
// Wear information
private static final int REQUEST_RESOLVE_ERROR = 1000;
private static final String START_ACTIVITY_PATH = "/start-wearableSyncActivity";
private static final String IMAGE_PATH = "/image";
private static final String IMAGE_KEY = "photo";
static {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
BITMAP_LRU_CACHE = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getAllocationByteCount() / 1024;
}
};
}
private static final String[] GALLERY_PROJECTION = new String[]{
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATE_TAKEN
};
private ViewPager viewPager;
private GalleryAdapter galleryAdapter;
private GoogleApiClient googleApiClient;
private boolean isResolvingError = false;
private int pageSelected;
/**
* Builds an {@link com.google.android.gms.wearable.Asset} from a bitmap. The image that we get
* back from the camera in "data" is a thumbnail size. Typically, your image should not exceed
* 320x320 and if you want to have zoom and parallax effect in your app, limit the size of your
* image to 640x400. Resize your image before transferring to your wearable device.
*/
private static Asset toAsset(Bitmap bitmap) {
ByteArrayOutputStream byteStream = null;
try {
byteStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
return Asset.createFromBytes(byteStream.toByteArray());
} finally {
if (byteStream != null) {
try {
byteStream.close();
} catch (IOException e) {
// ignore
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wearable_sync);
galleryAdapter = new GalleryAdapter(getFragmentManager());
viewPager = (ViewPager) findViewById(R.id.demo_wearable_sync_viewpager);
viewPager.setAdapter(galleryAdapter);
viewPager.setOnPageChangeListener(this);
getLoaderManager().initLoader(0, null, this);
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
@Override
protected void onResume() {
super.onResume();
new StartWearableActivityTask().execute();
}
@Override
protected void onStart() {
super.onStart();
if (!isResolvingError) {
googleApiClient.connect();
}
}
@Override
protected void onStop() {
if (!isResolvingError) {
Wearable.DataApi.removeListener(googleApiClient, this);
googleApiClient.disconnect();
}
super.onStop();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, GALLERY_PROJECTION, null, null,
null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
galleryAdapter.setCursor(cursor);
galleryAdapter.notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
galleryAdapter.setCursor(null);
galleryAdapter.notifyDataSetChanged();
}
@Override
public void onPageScrolled(int i, float v, int i2) {
}
@Override
public void onPageSelected(int i) {
pageSelected = i;
System.out.println("onpage selected: " + i);
((FragmentGalleryImage) galleryAdapter.instantiateItem(viewPager, i)).sendPhoto();
}
@Override
public void onPageScrollStateChanged(int i) {
}
@Override
public void onConnected(Bundle bundle) {
isResolvingError = false;
Wearable.DataApi.addListener(googleApiClient, this);
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
dataEvents.close();
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (!isResolvingError) {
if (connectionResult.hasResolution()) {
try {
isResolvingError = true;
connectionResult.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
googleApiClient.connect();
}
} else {
isResolvingError = false;
Wearable.DataApi.removeListener(googleApiClient, this);
}
}
}
private Collection<String> getNodes() {
Collection<String> results = new HashSet<String>();
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
results.add(node.getId());
}
return results;
}
private void sendStartActivityMessage(String node) {
Wearable.MessageApi.sendMessage(
googleApiClient, node, START_ACTIVITY_PATH, new byte[0]).setResultCallback(
new ResultCallback<MessageApi.SendMessageResult>() {
@Override
public void onResult(MessageApi.SendMessageResult sendMessageResult) {
}
}
);
}
/**
* Sends the asset that was created form the photo we took by adding it to the Data Item store.
*/
private void sendPhoto(Asset asset) {
PutDataMapRequest dataMap = PutDataMapRequest.create(IMAGE_PATH);
dataMap.getDataMap().putAsset(IMAGE_KEY, asset);
dataMap.getDataMap().putLong("time", new Date().getTime());
PutDataRequest request = dataMap.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, request)
.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {
}
});
}
public static final class FragmentGalleryImage extends Fragment {
private static final String EXTRA_GALLERY_IMAGE = FragmentGalleryImage.class.getSimpleName() +
".EXTRA_GALLERY_IMAGE";
private static int lastSent;
private GalleryImage galleryImage;
private ImageView imageViewImage;
private TextView textViewDateTaken;
private ImageLoader imageLoader;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
galleryImage = getArguments().getParcelable(EXTRA_GALLERY_IMAGE);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_gallery_image, container, false);
imageViewImage = (ImageView) view.findViewById(R.id.fragment_gallery_image_imageview_image);
textViewDateTaken = (TextView) view.findViewById(R.id.fragment_gallery_image_textview_date_taken);
imageLoader = new ImageLoader(imageViewImage, new ImageLoader.ICallback() {
@Override
public void onFinished() {
sendPhoto();
}
});
return view;
}
@Override
public void onResume() {
super.onResume();
Date date = new Date(galleryImage.dateTaken);
imageViewImage.setVisibility(View.INVISIBLE);
textViewDateTaken.setText(SimpleDateFormat.getInstance().format(date));
imageLoader.execute(galleryImage);
}
@Override
public void onPause() {
super.onPause();
imageLoader.cancel(false);
}
private void sendPhoto() {
WearableSyncActivity activity = (WearableSyncActivity) getActivity();
if (activity != null && activity.galleryAdapter.instantiateItem(activity.viewPager,
activity.pageSelected) == this) {
Bitmap bitmap = BITMAP_LRU_CACHE.get(ImageLoader.getWearImageCacheName(galleryImage));
if (bitmap != null) {
lastSent = activity.pageSelected;
activity.sendPhoto(toAsset(bitmap));
}
}
}
private static final class ImageLoader extends AsyncTask<GalleryImage, Void, Bitmap> {
private final ImageView imageView;
private final Context context;
private Bitmap wearBitmap;
private ICallback callback;
private ImageLoader(ImageView imageView, ICallback callback) {
this.imageView = imageView;
this.callback = callback;
context = imageView.getContext().getApplicationContext();
}
public static String getWearImageCacheName(GalleryImage galleryImage) {
return getImageUri(galleryImage).toString().replaceAll("[^a-zA-Z0-9.]", "") + "small";
}
public static Uri getImageUri(GalleryImage galleryImage) {
return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
Integer.toString(galleryImage._id));
}
private static void calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
options.inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / options.inSampleSize) > reqHeight
&& (halfWidth / options.inSampleSize) > reqWidth) {
options.inSampleSize *= 2;
}
options.outHeight = height / options.inSampleSize;
options.outWidth = width / options.inSampleSize;
}
}
@Override
protected Bitmap doInBackground(GalleryImage... galleryImages) {
Uri uri = getImageUri(galleryImages[0]);
String uriString = uri.toString();
Bitmap bitmap = BITMAP_LRU_CACHE.get(uriString);
if (bitmap != null)
return bitmap;
try {
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);
} catch (IOException e) {
e.printStackTrace();
}
BITMAP_LRU_CACHE.put(uriString, bitmap);
// Downscale for wearBitmap
String wearCacheName = getWearImageCacheName(galleryImages[0]);
File wearFile = new File(context.getCacheDir(), wearCacheName);
if (wearFile.exists()) {
wearBitmap = BitmapFactory.decodeFile(wearFile.getAbsolutePath());
BITMAP_LRU_CACHE.put(wearCacheName, wearBitmap);
} else {
try {
FileDescriptor imageDescriptor = context.getContentResolver().openFileDescriptor
(uri, "r").getFileDescriptor();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(imageDescriptor, null, options);
calculateInSampleSize(options, WEAR_SCREEN_SIZE_PX, WEAR_SCREEN_SIZE_PX);
options.inJustDecodeBounds = false;
wearBitmap = BitmapFactory.decodeFileDescriptor(imageDescriptor, null, options);
BITMAP_LRU_CACHE.put(wearCacheName, wearBitmap);
// cache to disk
OutputStream os = new FileOutputStream(wearFile);
wearBitmap.compress(Bitmap.CompressFormat.JPEG, 70, os);
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setVisibility(View.VISIBLE);
if (bitmap == null) {
imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setImageResource(R.drawable.ic_launcher);
} else {
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageBitmap(bitmap);
}
callback.onFinished();
callback = null;
}
@Override
protected void onCancelled() {
super.onCancelled();
callback = null;
}
private static interface ICallback {
void onFinished();
}
}
}
private static final class GalleryImage implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public Object createFromParcel(Parcel source) {
return new GalleryImage(source);
}
@Override
public GalleryImage[] newArray(int size) {
return new GalleryImage[0];
}
};
private final int _id;
private final long dateTaken;
private GalleryImage(Cursor cursor) {
int indexId = cursor.getColumnIndex(MediaStore.Images.Media._ID);
int indexDateTaken = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN);
_id = cursor.getInt(indexId);
dateTaken = Long.parseLong(cursor.getString(indexDateTaken));
}
private GalleryImage(Parcel parcel) {
_id = parcel.readInt();
dateTaken = parcel.readLong();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(_id);
dest.writeLong(dateTaken);
}
}
private static final class GalleryAdapter extends FragmentStatePagerAdapter {
private Cursor cursor;
private GalleryAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
cursor.moveToPosition(i);
Bundle bundle = new Bundle();
bundle.putParcelable(FragmentGalleryImage.EXTRA_GALLERY_IMAGE, new GalleryImage(cursor));
FragmentGalleryImage fragmentGalleryImage = new FragmentGalleryImage();
fragmentGalleryImage.setArguments(bundle);
return fragmentGalleryImage;
}
@Override
public int getCount() {
return cursor == null ? 0 : cursor.getCount();
}
public void setCursor(Cursor cursor) {
this.cursor = cursor;
}
}
private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... args) {
Collection<String> nodes = getNodes();
for (String node : nodes) {
sendStartActivityMessage(node);
}
return null;
}
}
}