/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
* copy, modify, and distribute this software in source code or binary form for use
* in connection with the web services and APIs provided by Facebook.
*
* As with any software that integrates with the Facebook platform, your use of
* this software is subject to the Facebook Developer Principles and Policies
* [http://developers.facebook.com/policy/]. This copyright notice shall be
* included in all copies or substantial portions of the software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.facebook.internal;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;
import com.facebook.FacebookContentProvider;
import com.facebook.FacebookException;
import com.facebook.FacebookSdk;
import java.io.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
/**
* com.facebook.internal is solely for the use of other packages within the Facebook SDK for
* Android. Use of any of the classes in this package is unsupported, and they may be modified or
* removed without warning at any time.
*
* <p>This class works in conjunction with {@link com.facebook.FacebookContentProvider} to allow
* apps to attach binary attachments (e.g., images) to native dialogs launched via the sdk.It stores
* attachments in temporary files and allows the Facebook application to retrieve them via the
* content provider.</p>
*/
public final class NativeAppCallAttachmentStore {
private static final String TAG = NativeAppCallAttachmentStore.class.getName();
static final String ATTACHMENTS_DIR_NAME = "com.facebook.NativeAppCallAttachmentStore.files";
private static File attachmentsDirectory;
private NativeAppCallAttachmentStore() {}
public static Attachment createAttachment(UUID callId, Bitmap attachmentBitmap) {
Validate.notNull(callId, "callId");
Validate.notNull(attachmentBitmap, "attachmentBitmap");
return new Attachment(callId, attachmentBitmap, null);
}
public static Attachment createAttachment(UUID callId, Uri attachmentUri) {
Validate.notNull(callId, "callId");
Validate.notNull(attachmentUri, "attachmentUri");
return new Attachment(callId, null, attachmentUri);
}
private static void processAttachmentBitmap(Bitmap bitmap, File outputFile) throws IOException {
FileOutputStream outputStream = new FileOutputStream(outputFile);
try {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
} finally {
Utility.closeQuietly(outputStream);
}
}
private static void processAttachmentFile(
Uri imageUri,
boolean isContentUri,
File outputFile) throws IOException {
FileOutputStream outputStream = new FileOutputStream(outputFile);
try {
InputStream inputStream;
if (!isContentUri) {
inputStream = new FileInputStream(imageUri.getPath());
} else {
inputStream = FacebookSdk
.getApplicationContext()
.getContentResolver()
.openInputStream(imageUri);
}
Utility.copyAndCloseInputStream(inputStream, outputStream);
} finally {
Utility.closeQuietly(outputStream);
}
}
public static void addAttachments(Collection<Attachment> attachments) {
if (attachments == null || attachments.size() == 0) {
return;
}
// If this is the first time we've been instantiated, clean up any existing attachments.
if (attachmentsDirectory == null) {
cleanupAllAttachments();
}
ensureAttachmentsDirectoryExists();
List<File> filesToCleanup = new ArrayList<>();
try {
for (Attachment attachment : attachments) {
if (!attachment.shouldCreateFile) {
continue;
}
File file = getAttachmentFile(
attachment.callId,
attachment.attachmentName,
true);
filesToCleanup.add(file);
if (attachment.bitmap != null) {
processAttachmentBitmap(attachment.bitmap, file);
} else if (attachment.originalUri != null) {
processAttachmentFile(
attachment.originalUri,
attachment.isContentUri,
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);
}
}
/**
* Removes any temporary files associated with a particular native app call.
*
* @param callId the unique ID of the call
*/
public static void cleanupAttachmentsForCall(UUID callId) {
File dir = getAttachmentsDirectoryForCall(callId, false);
if (dir != null) {
Utility.deleteDirectory(dir);
}
}
public static 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() {
if (attachmentsDirectory == null) {
attachmentsDirectory = new File(
FacebookSdk.getApplicationContext().getCacheDir(),
ATTACHMENTS_DIR_NAME);
}
return attachmentsDirectory;
}
static File ensureAttachmentsDirectoryExists() {
File dir = getAttachmentsDirectory();
dir.mkdirs();
return dir;
}
static 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;
}
static 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;
}
}
public static void cleanupAllAttachments() {
// 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();
Utility.deleteDirectory(dir);
}
public static final class Attachment {
private final UUID callId;
private final String attachmentUrl;
private final String attachmentName;
private Bitmap bitmap;
private Uri originalUri;
private boolean isContentUri;
private boolean shouldCreateFile;
private Attachment(UUID callId, Bitmap bitmap, Uri uri) {
this.callId = callId;
this.bitmap = bitmap;
this.originalUri = uri;
if (uri != null) {
String scheme = uri.getScheme();
if ("content".equalsIgnoreCase(scheme)) {
isContentUri = true;
shouldCreateFile = uri.getAuthority() != null &&
!uri.getAuthority().startsWith("media");
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
shouldCreateFile = true;
} else if (!Utility.isWebUri(uri)) {
throw new FacebookException("Unsupported scheme for media Uri : " + scheme);
}
} else if (bitmap != null) {
shouldCreateFile = true;
} else {
throw new FacebookException("Cannot share media without a bitmap or Uri set");
}
attachmentName = !shouldCreateFile ? null : UUID.randomUUID().toString();
attachmentUrl = !shouldCreateFile
? this.originalUri.toString()
: FacebookContentProvider.getAttachmentUrl(
FacebookSdk.getApplicationId(),
callId,
attachmentName);
}
public String getAttachmentUrl() {
return attachmentUrl;
}
}
}