Blame | Last modification | View Log | RSS feed
/*** Copyright 2010-present Facebook.** 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 com.facebook;import android.content.Context;import android.graphics.Bitmap;import android.util.Log;import com.facebook.internal.Utility;import com.facebook.internal.Validate;import java.io.*;import java.net.URLEncoder;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.UUID;/*** <p>This class works in conjunction with {@link NativeAppCallContentProvider} to allow apps to attach binary* attachments (e.g., images) to native dialogs launched via the {@link com.facebook.widget.FacebookDialog}* class. It stores attachments in temporary files and allows the Facebook application to retrieve them via* the content provider.</p>** <p>Callers are generally not expected to need to use this class directly;* see {@link com.facebook.widget.FacebookDialog.OpenGraphActionDialogBuilder#setImageAttachmentsForObject(String,* java.util.List) OpenGraphActionDialogBuilder.setImageAttachmentsForObject} for an example of a function* that will accept attachments, attach them to the native dialog call, and add them to the content provider* automatically.</p>**/public final class NativeAppCallAttachmentStore implements NativeAppCallContentProvider.AttachmentDataSource {private static final String TAG = NativeAppCallAttachmentStore.class.getName();static final String ATTACHMENTS_DIR_NAME = "com.facebook.NativeAppCallAttachmentStore.files";private static File attachmentsDirectory;/*** Adds a number of bitmap attachments associated with a native app call. The attachments will be* served via {@link NativeAppCallContentProvider#openFile(android.net.Uri, String) openFile}.** @param context the Context the call is being made from* @param callId the unique ID of the call* @param imageAttachments a Map of attachment names to Bitmaps; the attachment names will be part of* the URI processed by openFile* @throws java.io.IOException*/public void addAttachmentsForCall(Context context, UUID callId, Map<String, Bitmap> imageAttachments) {Validate.notNull(context, "context");Validate.notNull(callId, "callId");Validate.containsNoNulls(imageAttachments.values(), "imageAttachments");Validate.containsNoNullOrEmpty(imageAttachments.keySet(), "imageAttachments");addAttachments(context, callId, imageAttachments, new ProcessAttachment<Bitmap>() {@Overridepublic void processAttachment(Bitmap attachment, File outputFile) throws IOException {FileOutputStream outputStream = new FileOutputStream(outputFile);try {attachment.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);} finally {Utility.closeQuietly(outputStream);}}});}/*** Adds a number of bitmap/video attachment files associated with a native app call. The attachments will be* served via {@link NativeAppCallContentProvider#openFile(android.net.Uri, String) openFile}.** @param context the Context the call is being made from* @param callId the unique ID of the call* @param mediaAttachmentFiles a Map of attachment names to Files containing the bitmaps/videos; the attachment* names will be part of the URI processed by openFile* @throws java.io.IOException*/public void addAttachmentFilesForCall(Context context, UUID callId, Map<String, File> mediaAttachmentFiles) {Validate.notNull(context, "context");Validate.notNull(callId, "callId");Validate.containsNoNulls(mediaAttachmentFiles.values(), "mediaAttachmentFiles");Validate.containsNoNullOrEmpty(mediaAttachmentFiles.keySet(), "mediaAttachmentFiles");addAttachments(context, callId, mediaAttachmentFiles, new ProcessAttachment<File>() {@Overridepublic void processAttachment(File attachment, File outputFile) throws IOException {FileOutputStream outputStream = new FileOutputStream(outputFile);FileInputStream inputStream = null;try {inputStream = new FileInputStream(attachment);byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) > 0) {outputStream.write(buffer, 0, len);}} finally {Utility.closeQuietly(outputStream);Utility.closeQuietly(inputStream);}}});}private <T> void addAttachments(Context context, UUID callId, Map<String, T> attachments,ProcessAttachment<T> processor) {if (attachments.size() == 0) {return;}// If this is the first time we've been instantiated, clean up any existing attachments.if (attachmentsDirectory == null) {cleanupAllAttachments(context);}ensureAttachmentsDirectoryExists(context);List<File> filesToCleanup = new ArrayList<File>();try {for (Map.Entry<String, T> entry : attachments.entrySet()) {String attachmentName = entry.getKey();T attachment = entry.getValue();File file = getAttachmentFile(callId, attachmentName, true);filesToCleanup.add(file);processor.processAttachment(attachment, file);}} catch (IOException exception) {Log.e(TAG, "Got unexpected exception:" + exception);for (File file : filesToCleanup) {try {file.delete();} catch (Exception e) {// Always try to delete other files.}}throw new FacebookException(exception);}}interface ProcessAttachment<T> {void processAttachment(T attachment, File outputFile) throws IOException;}/*** Removes any temporary files associated with a particular native app call.** @param context the Context the call is being made from* @param callId the unique ID of the call*/public void cleanupAttachmentsForCall(Context context, UUID callId) {File dir = getAttachmentsDirectoryForCall(callId, false);Utility.deleteDirectory(dir);}@Overridepublic File openAttachment(UUID callId, String attachmentName) throws FileNotFoundException {if (Utility.isNullOrEmpty(attachmentName) ||callId == null) {throw new FileNotFoundException();}try {return getAttachmentFile(callId, attachmentName, false);} catch (IOException e) {// We don't try to create the file, so we shouldn't get any IOExceptions. But if we do, just// act like the file wasn't found.throw new FileNotFoundException();}}synchronized static File getAttachmentsDirectory(Context context) {if (attachmentsDirectory == null) {attachmentsDirectory = new File(context.getCacheDir(), ATTACHMENTS_DIR_NAME);}return attachmentsDirectory;}File ensureAttachmentsDirectoryExists(Context context) {File dir = getAttachmentsDirectory(context);dir.mkdirs();return dir;}File getAttachmentsDirectoryForCall(UUID callId, boolean create) {if (attachmentsDirectory == null) {return null;}File dir = new File(attachmentsDirectory, callId.toString());if (create && !dir.exists()) {dir.mkdirs();}return dir;}File getAttachmentFile(UUID callId, String attachmentName, boolean createDirs) throws IOException {File dir = getAttachmentsDirectoryForCall(callId, createDirs);if (dir == null) {return null;}try {return new File(dir, URLEncoder.encode(attachmentName, "UTF-8"));} catch (UnsupportedEncodingException e) {return null;}}void cleanupAllAttachments(Context context) {// Attachments directory may or may not exist; we won't create it if not, since we are just going to delete it.File dir = getAttachmentsDirectory(context);Utility.deleteDirectory(dir);}}