/* * Copyright 2012 The Stanford MobiSocial Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mobisocial.musubi.feed.action; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import mobisocial.musubi.App; import mobisocial.musubi.Helpers; import mobisocial.musubi.R; import mobisocial.musubi.feed.iface.FeedAction; import mobisocial.musubi.model.DbRelation; import mobisocial.musubi.model.MApp; import mobisocial.musubi.model.helpers.AppManager; import mobisocial.musubi.obj.ObjHelpers; import mobisocial.musubi.obj.action.EditPhotoAction.EditCallout; import mobisocial.musubi.objects.AppObj; import mobisocial.musubi.objects.PictureObj; import mobisocial.musubi.objects.VideoObj; import mobisocial.musubi.service.WizardStepHandler; import mobisocial.musubi.ui.fragments.AppSelectDialog; import mobisocial.musubi.ui.fragments.AppSelectDialog.MusubiWebApp; import mobisocial.musubi.ui.util.IntentProxyActivity; import mobisocial.musubi.util.ActivityCallout; import mobisocial.musubi.util.InstrumentedActivity; import mobisocial.musubi.util.PhotoTaker; import mobisocial.musubi.util.UriImage; import mobisocial.socialkit.Obj; import mobisocial.socialkit.musubi.DbObj; import mobisocial.socialkit.musubi.Musubi; import mobisocial.socialkit.obj.MemObj; import org.json.JSONException; import org.json.JSONObject; import org.mobisocial.corral.ContentCorral; import org.mobisocial.corral.CorralDownloadClient; import android.app.Activity; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; /** * Captures an image to share with a feed. * */ public class CameraAction extends FeedAction { private static final String TAG = "CameraAction"; public static final String CATEGORY_IN_PLACE = "mobisocial.intent.category.IN_PLACE"; static final String PICSAY_PACKAGE_PREFIX = "com.shinycore.picsay"; private static final int REQUEST_CAPTURE_MEDIA = 9412; private Uri mFeedUri; private Uri mImageUri; private String mType; static final String ACTION_MEDIA_CAPTURE = "mobisocial.intent.action.MEDIA_CAPTURE"; @Override public String getName() { return "Camera"; } @Override public Drawable getIcon(Context c) { return c.getResources().getDrawable(R.drawable.ic_attach_capture_picture_holo_light); } @Override public void onClick(final Context context, final Uri feedUri) { mFeedUri = feedUri; mImageUri = Uri.fromFile(PhotoTaker.getTempFile(getActivity())); mType = "image/jpeg"; Intent intent = new Intent(ACTION_MEDIA_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); intent.putExtra(Musubi.EXTRA_FEED_URI, feedUri); try { startActivityForResult(intent, REQUEST_CAPTURE_MEDIA); } catch (ActivityNotFoundException e) { intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent, REQUEST_CAPTURE_MEDIA); } } @Override public boolean isActive(Context c) { return true; } @Override public void onActivityResult(int requestCode, int resultCode, final Intent data) { if (requestCode == REQUEST_CAPTURE_MEDIA) { if (resultCode != Activity.RESULT_OK) { return; } new CameraCaptureTask(null).execute(); // needs UI overhaul for production. /* final Context context = getActivity(); final Dialog dialog = new Dialog(context, R.style.Dialog_Fullscreen); dialog.setContentView(R.layout.image_comment); dialog.setCancelable(true); final EditText editText = (EditText) dialog.findViewById(R.id.image_comment); ImageView image = (ImageView) dialog.findViewById(R.id.image); image.setImageResource(R.drawable.image_preview); new LoadImageTask(dialog).execute(); ((Button)dialog.findViewById(R.id.image_comment_send)).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new CameraCaptureTask(editText.getText().toString()).execute(); dialog.dismiss(); } }); ((Button)dialog.findViewById(R.id.image_comment_cancel)).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); } }); ((Button)dialog.findViewById(R.id.image_comment_edit)).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ((InstrumentedActivity)context).doActivityForResult( new EditCallout((Activity)context, mImageUri, mFeedUri, dialog)); } }); dialog.show(); */ } super.onActivityResult(requestCode, resultCode, data); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (savedInstanceState != null) { mFeedUri = savedInstanceState.getParcelable("feed"); mImageUri = savedInstanceState.getParcelable("image"); } } @Override public void onSaveInstanceState(Bundle outState) { outState.putParcelable("feed", mFeedUri); outState.putParcelable("image", mImageUri); } class LoadImageTask extends AsyncTask<Void, Void, Bitmap> { ImageView mImage; Dialog mDialog; public LoadImageTask(Dialog dialog) { mDialog = dialog; mImage = (ImageView) dialog.findViewById(R.id.image); } @Override protected Bitmap doInBackground(Void... params) { UriImage image = new UriImage(getActivity(), mImageUri); try { byte[] imageData = image.getResizedImageData(PictureObj.MAX_IMAGE_WIDTH, PictureObj.MAX_IMAGE_HEIGHT, PictureObj.MAX_IMAGE_SIZE); return BitmapFactory.decodeByteArray(imageData, 0, imageData.length); } catch (Exception e) { return null; } } @Override protected void onPostExecute(Bitmap result) { mImage.setImageBitmap(result); } } class CameraCaptureTask extends AsyncTask<Void, Void, Boolean> { Throwable mError; Obj mObj; String mComment; public CameraCaptureTask(String comment) { mComment = comment; mObj = null; } @Override protected Boolean doInBackground(Void... params) { Uri storedUri = ContentCorral.storeContent(getActivity(), mImageUri, mType); try { mObj = PictureObj.from(getActivity(), storedUri, true, mComment); } catch (IOException e) { Log.e(TAG, "Corral photo action had an issue", e); try { mObj = PictureObj.from(getActivity(), mImageUri, true, mComment); } catch(Throwable t) { Log.e(TAG, "fallback photo action had an issue", t); } } if (mObj == null) { return false; } Helpers.sendToFeed(getActivity(), mObj, mFeedUri); return true; } @Override protected void onPostExecute(Boolean result) { if (result) { Helpers.emailUnclaimedMembers(getActivity(), mObj, mFeedUri); WizardStepHandler.accomplishTask(getActivity(), WizardStepHandler.TASK_TAKE_PICTURE); } else { Toast.makeText(getActivity(), "Failed to capture media.", Toast.LENGTH_SHORT).show(); } } } static File getTempImagePath() { return new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/musubi_edit.png"); } public static class EditCallout implements ActivityCallout { final Activity mContext; final Uri mImageUri; final Uri mFeedUri; final Dialog mDialog; public EditCallout(Activity context, Uri imageUri, Uri feedUri, Dialog dialog) { mImageUri = imageUri; mContext = context; mFeedUri = feedUri; mDialog = dialog; } @Override public Intent getStartIntent() { Uri contentUri; File file; OutputStream out = null; try { file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/musubi_edit.png"); out = new FileOutputStream(file); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; UriImage image = new UriImage(mContext, mImageUri); byte[] imageData = image.getResizedImageData(image.getWidth(), image.getHeight(), PictureObj.MAX_IMAGE_SIZE); Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length); if(bitmap == null) return null; bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); out.flush(); out.close(); bitmap.recycle(); bitmap = null; } catch (Exception e) { e.printStackTrace(); return null; } finally { try { if(out != null) out.close(); } catch (IOException e) { Log.e(TAG, "failed to close output stream for picture", e); } } contentUri = Uri.fromFile(file); Log.w(TAG, "uri=" + contentUri); return getEditorChooserIntent(mContext, contentUri, "image/png", mFeedUri); } class LoadImageTask extends AsyncTask<Void, Void, Bitmap> { ImageView mImage; Dialog mDialog; Uri imageUri; public LoadImageTask(Dialog dialog, Uri image) { Log.w(TAG, "new load image task"); mDialog = dialog; mImage = (ImageView) dialog.findViewById(R.id.image); imageUri = image; } @Override protected Bitmap doInBackground(Void... params) { UriImage image = new UriImage(mContext, imageUri); Log.w(TAG, "new uri = " + imageUri); try { byte[] imageData = image.getResizedImageData(PictureObj.MAX_IMAGE_WIDTH, PictureObj.MAX_IMAGE_HEIGHT, PictureObj.MAX_IMAGE_SIZE); Log.w(TAG, "returning bitmap"); return BitmapFactory.decodeByteArray(imageData, 0, imageData.length); } catch (Exception e) { Log.w(TAG, "returning null"); return null; } } @Override protected void onPostExecute(Bitmap result) { Log.w(TAG, "setting bitmap"); mImage.setImageResource(R.drawable.image_preview); mImage.setImageBitmap(null); //mImage.setImageBitmap(result); } } @Override public void handleResult(int resultCode, final Intent data) { if (resultCode == Activity.RESULT_OK) { new Thread() { @Override public void run() { try { Uri result = Uri.fromFile(getTempImagePath()); Log.w(TAG, "result = " + result); /*boolean reference = true; Uri stored = ContentCorral.storeContent(mContext, result); if (stored == null) { Log.w(TAG, "Error storing content in corral"); stored = result; reference = false; } else { new File(result.getPath()).delete(); } */ new LoadImageTask(mDialog, result).execute(); /* MemObj outboundObj = PictureObj.from(mContext, stored, reference); try { JSONObject json = outboundObj.getJson(); json.put(ObjHelpers.TARGET_HASH, mHash); json.put(ObjHelpers.TARGET_RELATION, DbRelation.RELATION_EDIT); json.put(AppObj.ANDROID_PACKAGE_NAME, cn.getPackageName()); json.put(AppObj.CLAIMED_APP_ID, cn.getPackageName()); try { ActivityInfo info = mContext.getPackageManager() .getActivityInfo(cn, 0); json.put(AppObj.APP_NAME, info.loadLabel( mContext.getPackageManager())); } catch (NameNotFoundException e) { } } catch (JSONException e) {} Helpers.sendToFeed(mContext, outboundObj, mFeedUri); */ Log.w(TAG, "picture edited"); } catch (Exception e) { Log.e(TAG, "Error reading photo data.", e); toast("Error reading photo data."); } } }.start(); } } private final void toast(final String text) { mContext.runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); } }); } /** * Returns a chooser intent for editing a photo with results. Includes apps * that support the RETURN_RESULT category as well as PicSay, which is known * to return a result. */ Intent getEditorChooserIntent(Context context, Uri contentUri, String mimeType, Uri feedUri) { List<Intent> targetedShareIntents = new ArrayList<Intent>(); Intent editIntent = new Intent(android.content.Intent.ACTION_EDIT); editIntent.addCategory(CATEGORY_IN_PLACE); editIntent.setDataAndType(contentUri, mimeType); String title = "Edit with..."; Intent picsayIntent = getPicsayIntent(context, contentUri, mimeType); //targetedShareIntents.addAll(getBundledEditorIntents(context, mObjUri, feedUri)); assert(targetedShareIntents.size() != 0); List<ResolveInfo> resInfo = context.getPackageManager().queryIntentActivities( editIntent, PackageManager.MATCH_DEFAULT_ONLY); if (!resInfo.isEmpty()){ for (ResolveInfo resolveInfo : resInfo) { String packageName = resolveInfo.activityInfo.packageName; Intent targetedShareIntent = new Intent(Intent.ACTION_EDIT); targetedShareIntent.setDataAndType(contentUri, mimeType); targetedShareIntent.putExtra(Musubi.EXTRA_FEED_URI, feedUri); targetedShareIntent.addCategory(CATEGORY_IN_PLACE); targetedShareIntent.setPackage(packageName); targetedShareIntents.add(targetedShareIntent); } if (picsayIntent != null) { targetedShareIntents.add(picsayIntent); } } else { if (picsayIntent != null) { targetedShareIntents.add(picsayIntent); } else { LabeledIntent picsayMarket = new LabeledIntent(context.getPackageName(), "PicSay", R.drawable.picsay_icon); picsayMarket.setAction(Intent.ACTION_VIEW); picsayMarket.setData(Uri.parse("market://details?id=com.shinycore.picsayfree")); targetedShareIntents.add(picsayMarket); } } //targetedShareIntents.addAll(getWebEditorIntents(context, mObjUri, feedUri)); // XXX First intent must not be a LabeledIntent. // See getBundledEditorIntents(). // Sketch doens't have to be proxied because it doesn't return a result. Intent first = targetedShareIntents.remove(0); Intent[] later = new Intent[targetedShareIntents.size()]; int i = 0; for (Intent intent : targetedShareIntents) { later[i++] = IntentProxyActivity.getProxyIntent(context, intent); } Intent chooserIntent = Intent.createChooser(first, title); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, later); return chooserIntent; } Intent getPicsayIntent(Context context, Uri contentUri, String mimeType) { Intent edit = new Intent(Intent.ACTION_EDIT); edit.setDataAndType(contentUri, mimeType); List<ResolveInfo> resInfo = context.getPackageManager().queryIntentActivities(edit, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo i : resInfo) { if (i.activityInfo.packageName.startsWith(PICSAY_PACKAGE_PREFIX)) { Intent picsay = new Intent(); picsay.setAction(Intent.ACTION_EDIT); picsay.setPackage(i.activityInfo.packageName); picsay.setDataAndType(contentUri, mimeType); return picsay; } } return null; } /** * XXX This is largely a workaround for a nasty bug somewhere between here * and Android's ResolverActivity. You cannot send a LabeledIntent as the * first intent in Intent.createChooser(), or bad things happen. Here we * force the first intent to be an Intent serviced by our application. */ Set<Intent> getBundledEditorIntents(Context context, Uri objUri, Uri feedUri) { Set<Intent> editors = new HashSet<Intent>(); // Sketch MApp sketch = new AppManager(App.getDatabaseSource(context)) .lookupAppByAppId(AppSelectDialog.SKETCH_APP_ID); if (sketch != null) { MusubiWebApp app = new MusubiWebApp(context, sketch.name_, sketch.appId_, sketch.webAppUrl_, R.drawable.sketch); Intent intent = app.getLaunchIntent(context, feedUri); Intent wrapper = new Intent("musubi.intent.action.SKETCH"); wrapper.setData(objUri); wrapper.putExtras(intent.getExtras()); editors.add(wrapper); } return editors; } /** * This is a poor approximation of an edit intent over a SocialDB object. * The generalization is an app that supports the EDIT intent for objects * of type "picture" (ObjHelper.mimeTypeOf("picture") == "vnd.musubi.obj/picture") */ Set<Intent> getWebEditorIntents(Context context, Uri objUri, Uri feedUri) { Set<Intent> editors = new HashSet<Intent>(); List<MApp> apps = new AppManager(App.getDatabaseSource(context)) .lookupAppForAction(PictureObj.TYPE, "edit"); for(MApp app : apps) { MusubiWebApp web_app = new MusubiWebApp(context, app.name_, app.appId_, app.webAppUrl_, R.drawable.ic_menu_globe); Intent intent = web_app.getLaunchIntent(context, feedUri); intent.setData(objUri); editors.add(intent); } return editors; } } }