package com.dropbox.chooser.android;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
/**
* DbxChooser is a utility class to launch Dropbox's Android Chooser.
*
* Example use:
* <p><blockquote><pre>
* new DbxChooser(APP_KEY)
* .forResultType(DbxChooser.ResultType.PREVIEW_LINK)
* .limitToExtensions(".png", ".jpg", ".jpeg")
* .launch(this, CHOOSER_REQUEST_CODE);
* </pre></blockquote></p>
*
* <p>
* The result will be received in the onActivityResult callback of the Activity or Fragment supplied to {@link DbxChooser#launch}.
* </p>
*
*/
public class DbxChooser {
public enum ResultType {
PREVIEW_LINK("com.dropbox.android.intent.action.GET_PREVIEW"),
DIRECT_LINK("com.dropbox.android.intent.action.GET_DIRECT"),
FILE_CONTENT("com.dropbox.android.intent.action.GET_CONTENT");
final String action; // package-private
ResultType(String action) {
this.action = action;
}
}
private static final String[] intentResultExtras = {
"EXTRA_CHOOSER_RESULTS", // new unified result
"EXTRA_PREVIEW_RESULTS", // these others for backwards compatibility
"EXTRA_CONTENT_RESULTS",
};
/**
* This is passed in the Intent to identify the version of the
* client SDK. It should be incremented for any change in behavior
* in this code.
*/
private static final int SDK_VERSION = 2;
private String mAction = ResultType.FILE_CONTENT.action;
private boolean mForceNotAvailable = false;
private final String mAppKey;
public DbxChooser(String appKey) {
if (appKey == null || appKey.length() == 0) {
throw new IllegalArgumentException("An app key must be supplied.");
}
mAppKey = appKey;
}
private static boolean isChooserAvailable(PackageManager pm) {
ResultType[] resultTypes = { ResultType.FILE_CONTENT, ResultType.PREVIEW_LINK, ResultType.DIRECT_LINK };
for (ResultType resultType : resultTypes) {
ResolveInfo ri = pm.resolveActivity(new Intent(resultType.action), PackageManager.MATCH_DEFAULT_ONLY);
if (ri == null) {
return false;
}
}
return true;
}
private boolean chooserAvailable(PackageManager pm) {
if (mForceNotAvailable) {
return false;
}
return isChooserAvailable(pm);
}
/**
* Requests that the Chooser return a particular type of result.
* If this is not called, the default result type is FILE_CONTENT, which
* returns a URI that can be opened to retrieve the contents of the chosen
* file.
*/
public DbxChooser forResultType(ResultType resultType) {
if (resultType == null) {
throw new IllegalArgumentException("An app key must be supplied.");
}
mAction = resultType.action;
return this;
}
/**
* For testing purposes, this causes DbxChooser to behave as if
* the Dropbox Chooser isn't available.
*/
public DbxChooser pretendNotAvailable() {
mForceNotAvailable = true;
return this;
}
private Intent getIntent() {
Intent intent = new Intent(mAction).putExtra("EXTRA_APP_KEY", mAppKey);
intent.putExtra("EXTRA_SDK_VERSION", SDK_VERSION);
return intent;
}
/**
* Launches the Chooser with the supplied request code.
* The result will be received in the onActivityResult callback of the
* supplied Activity.
*/
public void launch(Activity act, int requestCode) throws ActivityNotFoundException {
final Activity mAct = act;
ActivityLike thing = new ActivityLike() {
@Override
public void startActivity(Intent intent)
throws ActivityNotFoundException {
mAct.startActivity(intent);
}
@Override
public void startActivityForResult(Intent intent, int requestCode)
throws ActivityNotFoundException {
mAct.startActivityForResult(intent, requestCode);
}
@Override
public ContentResolver getContentResolver() {
return mAct.getContentResolver();
}
@Override
public PackageManager getPackageManager() {
return mAct.getPackageManager();
}
@Override
public FragmentManager getFragmentManager() {
try {
return mAct.getFragmentManager();
} catch (NoSuchMethodError e) {
return null;
}
}
@Override
public android.support.v4.app.FragmentManager getSupportFragmentManager() {
if (mAct instanceof android.support.v4.app.FragmentActivity) {
return ((android.support.v4.app.FragmentActivity) mAct).getSupportFragmentManager();
} else {
return null;
}
}
};
launch(thing, requestCode);
}
/**
* Launches the Chooser with the supplied request code.
* The result will be received in the onActivityResult callback of the
* supplied Fragment. If the supplied Fragment is not attached to an Activity,
* this will throw an IllegalStateException.
*
* NOTE: this method requires Android API at least version 11.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void launch(Fragment frag, int requestCode) throws ActivityNotFoundException {
final Fragment mFrag = frag;
ActivityLike thing = new ActivityLike() {
@Override
public void startActivity(Intent intent)
throws ActivityNotFoundException {
mFrag.startActivity(intent);
}
@Override
public void startActivityForResult(Intent intent, int requestCode)
throws ActivityNotFoundException {
mFrag.startActivityForResult(intent, requestCode);
}
@Override
public ContentResolver getContentResolver() {
Activity act = mFrag.getActivity();
if (act == null) {
return null;
}
return act.getContentResolver();
}
@Override
public PackageManager getPackageManager() {
Activity act = mFrag.getActivity();
if (act == null) {
return null;
}
return act.getPackageManager();
}
@Override
public FragmentManager getFragmentManager() {
Activity act = mFrag.getActivity();
if (act == null) {
return null;
}
return act.getFragmentManager();
}
@Override
public android.support.v4.app.FragmentManager getSupportFragmentManager() {
return null;
}
};
launch(thing, requestCode);
}
/**
* Launches the Chooser with the supplied request code.
* The result will be received in the onActivityResult callback of the
* supplied Fragment. If the supplied Fragment is not attached to an Activity,
* this will throw an IllegalStateException.
*/
public void launch(android.support.v4.app.Fragment frag, int requestCode) throws ActivityNotFoundException {
final android.support.v4.app.Fragment mFrag = frag;
ActivityLike thing = new ActivityLike() {
@Override
public void startActivity(Intent intent)
throws ActivityNotFoundException {
mFrag.startActivity(intent);
}
@Override
public void startActivityForResult(Intent intent, int requestCode)
throws ActivityNotFoundException {
mFrag.startActivityForResult(intent, requestCode);
}
@Override
public ContentResolver getContentResolver() {
Activity act = mFrag.getActivity();
if (act == null) {
return null;
}
return act.getContentResolver();
}
@Override
public PackageManager getPackageManager() {
Activity act = mFrag.getActivity();
if (act == null) {
return null;
}
return act.getPackageManager();
}
@Override
public FragmentManager getFragmentManager() {
return null;
}
@Override
public android.support.v4.app.FragmentManager getSupportFragmentManager() {
android.support.v4.app.FragmentActivity act = mFrag.getActivity();
if (act == null) {
return null;
}
return act.getSupportFragmentManager();
}
};
launch(thing, requestCode);
}
private void launch(ActivityLike thing, int requestCode) {
if (requestCode < 0) {
throw new IllegalArgumentException("requestCode must be non-negative");
}
// Check whether we can show a fragment (needed for the app store interstitial)
if (thing.getSupportFragmentManager() == null && thing.getFragmentManager() == null) {
throw new IllegalArgumentException("Dropbox Chooser requires Fragments. If below API level 11, pass in a FragmentActivity from the support library.");
}
// Check whether we can launch the app
PackageManager pm = thing.getPackageManager();
if (pm == null) {
throw new IllegalStateException("DbxChooser's launch() must be called when there is an Activity available");
}
if (!chooserAvailable(thing.getPackageManager())) {
doAppStoreFallback(thing, requestCode);
return;
}
// Launch the app
Intent intent = getIntent();
try {
thing.startActivityForResult(intent, requestCode);
} catch (ActivityNotFoundException e) {
// Just being explicit.
throw e;
}
}
/**
* Show interstitial, and then app store
*/
private void doAppStoreFallback(ActivityLike thing, int requestCode) throws ActivityNotFoundException {
AppStoreInterstitial.showInterstitial(thing);
}
/**
* Helper class to access the result of a successful invocation of the Dropbox Chooser.
*
* Example use:
* <p><blockquote><pre>
* protected void onActivityResult(int requestCode, int resultCode, Intent data) {
* if (requestCode == CHOOSER_REQUEST_CODE && resultCode == RESULT_OK) {
* handlePreviewLink(new DropboxChooser.Result(data).getPreviewLink());
* }
* }
* </pre></blockquote></p>
*/
public static class Result {
private final Intent mIntent;
/**
* @param intent The Intent passed to onActivityResult as a result of the Dropbox Chooser.
* If the request code doesn't match the one provided to {@link DbxChooser#launch(Activity, int)} or if the result code
* was anything but {@link Activity#RESULT_OK}, this class will be unable to extract useful values.
*/
public Result(Intent intent) {
mIntent = intent;
}
/**
* @return A Uri referring to the file selected by the user.
* <ul>
* <li>If the chooser was for the ResultType PREVIEW_LINK or DIRECT_LINK, the
* Uri is a web link to a preview page for the file or the file itself.</li>
* <li>If the chooser was for the ResultType FILE_CONTENT, the Uri can be opened
* via a ContentResolver to provide the contents of the file, already downloaded.</li>
* </ul>
* If the provided Intent wasn't from a Chooser or a file wasn't chosen, this returns null.
*/
public Uri getLink() {
Bundle[] results = getResults();
if (results.length == 0) {
return null;
}
return results[0].getParcelable("uri");
}
/**
* @return The name of the selected file.
* If the provided Intent wasn't from a ResultType *_LINK Chooser, a file wasn't
* chosen, or the file was chosen from an old version of the Dropbox app, this returns null.
*/
public String getName() {
Bundle[] results = getResults();
if (results.length == 0) {
return null;
}
return results[0].getString("name");
}
/**
* @return A Map where the keys are thumbnail sizes and the values are
* Uris that, when opened via a ContentResolver, provide thumbnails of
* the file selected by the user.
*
* The map may be empty--this indicates no thumbnails could be
* generated for the file. If the map is not empty, it will contain at
* least the keys:
* <ul>
* <li><pre>"64x64"</pre></li>
* <li><pre>"200x200"</pre></li>
* <li><pre>"640x480"</pre></li>
* </ul>
* If the provided Intent wasn't from a ResultType *_LINK Chooser, a file wasn't
* chosen, or the file was chosen from an old version of the Dropbox app, this returns null.
*/
public Map<String, Uri> getThumbnails() {
Bundle[] results = getResults();
if (results.length == 0) {
return null;
}
Bundle thumbsBundle = results[0].getParcelable("thumbnails");
if (thumbsBundle == null) {
return null;
}
HashMap<String, Uri> thumbs = new HashMap<String, Uri>();
for (String key : thumbsBundle.keySet()) {
thumbs.put(key, (Uri) thumbsBundle.getParcelable(key));
}
return thumbs;
}
/**
* @return A Uri that refers to an icon appropriate for the type of the given file.
* If the provided Intent wasn't from a ResultType *_LINK Chooser, a file wasn't
* chosen, or the file was chosen from an old version of the Dropbox app, this returns null.
*/
public Uri getIcon() {
Bundle[] results = getResults();
if (results.length == 0) {
return null;
}
return results[0].getParcelable("icon");
}
/**
* @return The size of the file selected by the user, in bytes.
* If the provided Intent wasn't from a ResultType *_LINK Chooser, a file wasn't
* chosen, or the file was chosen from an old version of the Dropbox app, this returns -1.
*/
public long getSize() {
Bundle[] results = getResults();
if (results.length == 0) {
return -1;
}
return results[0].getLong("bytes", -1);
}
/**
* @return An array of Bundle objects, one for each file selected.
* If the provided Intent wasn't from a Chooser, this returns an empty array.
*/
private Bundle[] getResults() {
if (mIntent == null) {
return new Bundle[] {};
}
for (String resultExtra : intentResultExtras) {
Parcelable[] results = mIntent.getParcelableArrayExtra(resultExtra);
if (results != null) {
Bundle[] resultBundles = new Bundle[results.length];
for (int i = 0; i < results.length; i++) {
resultBundles[i] = (Bundle) results[i];
}
return resultBundles;
}
}
return new Bundle[] {};
}
}
}