/* * 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.objects; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.SoftReference; import mobisocial.musubi.App; import mobisocial.musubi.ImageGalleryActivity; import mobisocial.musubi.R; import mobisocial.musubi.feed.iface.Activator; import mobisocial.musubi.feed.iface.DbEntryHandler; import mobisocial.musubi.feed.iface.FeedRenderer; import mobisocial.musubi.model.DbContactAttributes; import mobisocial.musubi.model.MFeed; import mobisocial.musubi.model.MIdentity; import mobisocial.musubi.model.MObject; import mobisocial.musubi.ui.MusubiBaseActivity; import mobisocial.musubi.ui.fragments.FeedListFragment.FeedSummary; import mobisocial.musubi.ui.widget.DbObjCursorAdapter.DbObjCursor; import mobisocial.musubi.util.CommonLayouts; import mobisocial.musubi.util.UriImage; import mobisocial.socialkit.musubi.DbObj; import mobisocial.socialkit.obj.MemObj; import org.apache.commons.io.IOUtils; import org.json.JSONException; import org.json.JSONObject; import org.mobisocial.corral.ContentCorral; import org.mobisocial.corral.CorralDownloadClient; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.media.MediaScannerConnection; import android.net.Uri; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; public class PictureObj extends DbEntryHandler implements FeedRenderer, Activator { public static final String TAG = "PictureObj"; public static final String TYPE = "picture"; public static final String DATA = "data"; public static final String TEXT = "text"; public static final int MAX_IMAGE_WIDTH = 1280; public static final int MAX_IMAGE_HEIGHT = 720; public static final int MAX_IMAGE_SIZE = 40*1024; @Override public String getType() { return TYPE; } public static MemObj from(byte[] data) { return from(data, ""); } /** * This does NOT do any SCALING! */ public static MemObj from(byte[] data, String text) { JSONObject base = new JSONObject(); try{ base.put(TEXT, text); }catch(JSONException e){} return new MemObj(TYPE, base, data); } public static MemObj from(JSONObject base, byte[] data) { return new MemObj(TYPE, base, data); } public static MemObj from(Context context, Uri imageUri, boolean referenceOrig) throws IOException { return from(context, imageUri, referenceOrig, null); } public static MemObj from(Context context, Uri imageUri, boolean referenceOrig, String text) throws IOException { // Query gallery for camera picture via // Android ContentResolver interface ContentResolver cr = context.getContentResolver(); UriImage image = new UriImage(context, imageUri); byte[] data = image.getResizedImageData(MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT, MAX_IMAGE_SIZE); JSONObject base = new JSONObject(); // Maintain a reference to original file try { String type = cr.getType(imageUri); if (type == null) { type = "image/jpeg"; } if (text != null && !text.isEmpty()) { base.put(TEXT, text); } base.put(CorralDownloadClient.OBJ_MIME_TYPE, type); if (referenceOrig) { base.put(CorralDownloadClient.OBJ_LOCAL_URI, imageUri.toString()); String localIp = ContentCorral.getLocalIpAddress(); // TODO: Share IP if allowed for the given feed if (localIp != null && MusubiBaseActivity.isDeveloperModeEnabled(context)) { base.put(DbContactAttributes.ATTR_LAN_IP, localIp); } } } catch (JSONException e) { Log.e(TAG, "impossible json error possible!"); } return new MemObj(TYPE, base, data); } @Override public View createView(Context context, ViewGroup parent) { LinearLayout frame = new LinearLayout(context); frame.setLayoutParams(CommonLayouts.FULL_WIDTH); frame.setOrientation(LinearLayout.VERTICAL); ImageView imageView = new ImageView(context); imageView.setBackgroundResource(android.R.drawable.picture_frame); imageView.setPadding(6, 4, 8, 9); frame.addView(imageView); TextView textView = new TextView(context); frame.addView(textView); return frame; } @Override public void render(Context context, View view, DbObjCursor obj, boolean allowInteractions) { LinearLayout frame = (LinearLayout)view; ImageView imageView = (ImageView)frame.getChildAt(0); FileDescriptor fd = obj.getFileDescriptorForRaw(); byte[] raw = null; if (fd == null) { raw = obj.getRaw(); } bindImageToView(context, imageView, raw, fd); String text = obj.getJson().optString(TEXT); TextView textView = (TextView)frame.getChildAt(1); textView.setText(text); } /** * Pass in one of raw or fd as the source of the image. * Not thread safe, only call on the ui thread. */ protected static void bindImageToView(Context context, ImageView imageView, byte[] raw, FileDescriptor fd) { // recycle old images (vs. caching in ImageCache) if (imageView.getDrawable() != null) { BitmapDrawable d = (BitmapDrawable)imageView.getDrawable(); if (d != null && d.getBitmap() != null) { d.getBitmap().recycle(); } } BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; if (fd != null) { BitmapFactory.decodeFileDescriptor(fd, null, options); } else { BitmapFactory.decodeByteArray(raw, 0, raw.length, options); } Resources res = context.getResources(); float scaleFactor; if (res.getBoolean(R.bool.is_tablet)) { scaleFactor = 3.0f; } else { scaleFactor = 2.0f; } DisplayMetrics dm = context.getResources().getDisplayMetrics(); int pixels = dm.widthPixels; if (dm.heightPixels < pixels) { pixels = dm.heightPixels; } int width = (int)(pixels / scaleFactor); int height = (int)((float)width / options.outWidth * options.outHeight); int max_height = (int)(AppStateObj.MAX_HEIGHT * dm.density); if(height > max_height) { width = width * max_height / height; height = max_height; } options.inJustDecodeBounds = false; options.inTempStorage = getTempData(); options.inSampleSize = 1; //TODO: lame, can just compute while(options.outWidth / (options.inSampleSize + 1) >= width && options.outHeight / (options.inSampleSize + 1) >= height) { options.inSampleSize++; } options.inPurgeable = true; options.inInputShareable = true; Bitmap bm; if (fd != null) { bm = BitmapFactory.decodeFileDescriptor(fd, null, options); } else { bm = BitmapFactory.decodeByteArray(raw, 0, raw.length, options); } imageView.getLayoutParams().width = width + 13; imageView.getLayoutParams().height = height + 14; imageView.setImageBitmap(bm); } @Override public void activate(Context context, DbObj obj) { // TODO: set data uri for obj Intent intent = new Intent(context, ImageGalleryActivity.class); intent.setData(obj.getContainingFeed().getUri()); intent.putExtra(ImageGalleryActivity.EXTRA_DEFAULT_OBJECT_ID, obj.getLocalId()); if (!(context instanceof Activity)) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(intent); } @Override public boolean processObject(Context context, MFeed feed, MIdentity sender, MObject object) { DbObj obj = App.getMusubi(context).objForId(object.id_); File thumbFile = CorralDownloadClient.localFileForContent(obj, true); try { FileOutputStream fout = new FileOutputStream(thumbFile); ByteArrayInputStream fin = new ByteArrayInputStream(object.raw_); IOUtils.copy(fin, fout); String[] paths = new String[] { thumbFile.getAbsolutePath() }; MediaScannerConnection.scanFile(context, paths, null, null); } catch (IOException e) { Log.e(TAG, "Error saving thumbnail", e); thumbFile.delete(); } return true; } static byte[] mTempData; static byte[] getTempData() { if (mTempData == null) { mTempData = new byte[16*1024]; } return mTempData; } @Override public void getSummaryText(Context context, TextView view, FeedSummary summary) { view.setTypeface(null, Typeface.ITALIC); JSONObject json = summary.getJson(); if (json != null && json.optString(PictureObj.TEXT).length() > 0) { StringBuilder summaryText = new StringBuilder(50) .append(summary.getSender()).append(" shared a picture with the caption \"" + json.optString(StatusObj.TEXT) + "\""); view.setText(summaryText.toString()); } else { StringBuilder summaryText = new StringBuilder(50) .append(summary.getSender()).append(" shared a picture."); view.setText(summaryText.toString()); } } }