/** * 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.messenger; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; import com.facebook.FacebookSdk; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import bolts.AppLinks; /** * Utilities for Messenger Content Platform. * <p> * Applications should specify the app id in their manifest or call {@link #setAppId(String)} in * their application startup path. For specifying in the manifest, add a meta tag in your * <application> tag. * * <pre> * <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="YOUR_APP_ID"/> * </pre> * * </p> * * <p> * When sharing to Messenger, apps should call the {@link #shareToMessenger} method. For example, * * <pre> * ShareToMessengerParams params = ShareToMessengerParams.newBuilder(uri, "image/*") * .setMetaData(metaData) * .build(); * MessengerUtils.shareToMessenger(this, REQUEST_CODE_SHARE_TO_MESSENGER, params); * </pre> * * To handle receiving a composer shortcut or reply intent from Messenger, apps should * put the following intent filter in their manifest for the activity that receives the intent: * * <pre> * <intent-filter> * <action android:name="android.intent.action.PICK" /> * <category android:name="android.intent.category.DEFAULT"/> * <category android:name="com.facebook.orca.category.PLATFORM_THREAD_20150311"/> * </intent-filter> * </pre> * * When handling the intent, then call {@link #getMessengerThreadParamsForIntent} to receive * the parameters for messenger. When the user has clicked the Sened button to send the content * to Messenger, ten call {@link #finishShareToMessenger} to return the data back to Messenger. * </p> */ public class MessengerUtils { private static final String TAG = "MessengerUtils"; public static final String PACKAGE_NAME = "com.facebook.orca"; public static final String APPLICATION_ID_PROPERTY = "com.facebook.sdk.ApplicationId"; public static final String EXTRA_PROTOCOL_VERSION = "com.facebook.orca.extra.PROTOCOL_VERSION"; public static final String EXTRA_APP_ID = "com.facebook.orca.extra.APPLICATION_ID"; public static final String EXTRA_REPLY_TOKEN_KEY = "com.facebook.orca.extra.REPLY_TOKEN"; public static final String EXTRA_THREAD_TOKEN_KEY = "com.facebook.orca.extra.THREAD_TOKEN"; public static final String EXTRA_METADATA = "com.facebook.orca.extra.METADATA"; public static final String EXTRA_EXTERNAL_URI = "com.facebook.orca.extra.EXTERNAL_URI"; public static final String EXTRA_PARTICIPANTS = "com.facebook.orca.extra.PARTICIPANTS"; public static final String EXTRA_IS_REPLY = "com.facebook.orca.extra.IS_REPLY"; public static final String EXTRA_IS_COMPOSE = "com.facebook.orca.extra.IS_COMPOSE"; public static final int PROTOCOL_VERSION_20150314 = 20150314; public static final String ORCA_THREAD_CATEGORY_20150314 = "com.facebook.orca.category.PLATFORM_THREAD_20150314"; /** * Starts an intent to share a piece of media on Messenger using the messenger content platform. * * @param activity the activity sharing the content * @param requestCode a unique request code for {@link Activity#startActivityForResult} * @param shareToMessengerParams parameters for what to share */ public static void shareToMessenger( Activity activity, int requestCode, ShareToMessengerParams shareToMessengerParams) { if (!MessengerUtils.hasMessengerInstalled(activity)) { MessengerUtils.openMessengerInPlayStore(activity); return; } Set<Integer> allAvailableVersions = getAllAvailableProtocolVersions(activity); if (allAvailableVersions.contains(PROTOCOL_VERSION_20150314)) { shareToMessenger20150314(activity, requestCode, shareToMessengerParams); } else { // TODO -- should we show a upgrade dialog? MessengerUtils.openMessengerInPlayStore(activity); } } private static void shareToMessenger20150314( Activity activity, int requestCode, ShareToMessengerParams shareToMessengerParams) { try { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); shareIntent.setPackage(PACKAGE_NAME); shareIntent.putExtra(Intent.EXTRA_STREAM, shareToMessengerParams.uri); shareIntent.setType(shareToMessengerParams.mimeType); String appId = FacebookSdk.getApplicationId(); if (appId != null) { shareIntent.putExtra(EXTRA_PROTOCOL_VERSION, PROTOCOL_VERSION_20150314); shareIntent.putExtra(EXTRA_APP_ID, appId); shareIntent.putExtra(EXTRA_METADATA, shareToMessengerParams.metaData); shareIntent.putExtra(EXTRA_EXTERNAL_URI, shareToMessengerParams.externalUri); } activity.startActivityForResult(shareIntent, requestCode); } catch (ActivityNotFoundException e) { Intent openMessenger = activity.getPackageManager().getLaunchIntentForPackage(PACKAGE_NAME); activity.startActivity(openMessenger); } } /** * When handling an {@code Intent} from Messenger, call this to parse the parameters of the * intent. * * @param intent the intent of the activity * @return a {@link MessengerThreadParams} or null if this intent wasn't recognized as a request * from Messenger to share. */ public static MessengerThreadParams getMessengerThreadParamsForIntent(Intent intent) { Set<String> categories = intent.getCategories(); if (categories == null) { return null; } if (categories.contains(ORCA_THREAD_CATEGORY_20150314)) { Bundle appLinkExtras = AppLinks.getAppLinkExtras(intent); String threadToken = appLinkExtras.getString(EXTRA_THREAD_TOKEN_KEY); String metadata = appLinkExtras.getString(EXTRA_METADATA); String participants = appLinkExtras.getString(EXTRA_PARTICIPANTS); boolean isReply = appLinkExtras.getBoolean(EXTRA_IS_REPLY); boolean isCompose = appLinkExtras.getBoolean(EXTRA_IS_COMPOSE); MessengerThreadParams.Origin origin = MessengerThreadParams.Origin.UNKNOWN; if (isReply) { origin = MessengerThreadParams.Origin.REPLY_FLOW; } else if (isCompose) { origin = MessengerThreadParams.Origin.COMPOSE_FLOW; } return new MessengerThreadParams( origin, threadToken, metadata, parseParticipants(participants)); } else { return null; } } /** * Finishes the activity and returns the media item the user picked to Messenger. * * @param activity the activity that received the original intent from Messenger * @param shareToMessengerParams parameters for what to share */ public static void finishShareToMessenger( Activity activity, ShareToMessengerParams shareToMessengerParams) { Intent originalIntent = activity.getIntent(); Set<String> categories = originalIntent.getCategories(); if (categories == null) { // This shouldn't happen. activity.setResult(Activity.RESULT_CANCELED, null); activity.finish(); return; } if (categories.contains(ORCA_THREAD_CATEGORY_20150314)) { Bundle appLinkExtras = AppLinks.getAppLinkExtras(originalIntent); Intent resultIntent = new Intent(); if (categories.contains(ORCA_THREAD_CATEGORY_20150314)) { resultIntent.putExtra(EXTRA_PROTOCOL_VERSION, MessengerUtils.PROTOCOL_VERSION_20150314); String threadToken = appLinkExtras.getString(MessengerUtils.EXTRA_THREAD_TOKEN_KEY); resultIntent.putExtra(EXTRA_THREAD_TOKEN_KEY, threadToken); } else { throw new RuntimeException(); // Can't happen. } resultIntent.setDataAndType(shareToMessengerParams.uri, shareToMessengerParams.mimeType); resultIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); resultIntent.putExtra(EXTRA_APP_ID, FacebookSdk.getApplicationId()); resultIntent.putExtra(EXTRA_METADATA, shareToMessengerParams.metaData); resultIntent.putExtra(EXTRA_EXTERNAL_URI, shareToMessengerParams.externalUri); activity.setResult(Activity.RESULT_OK, resultIntent); activity.finish(); } else { // This shouldn't happen. activity.setResult(Activity.RESULT_CANCELED, null); activity.finish(); } } /** * Checks whether any version of messenger is installed. * * @param context an android context * @return whether any version of messenger is installed */ public static boolean hasMessengerInstalled(Context context) { try { context.getPackageManager().getPackageInfo(PACKAGE_NAME, 0); return true; } catch (PackageManager.NameNotFoundException e) { return false; } } /** * Opens the play store to install Messenger. * * @param context an android context. */ public static void openMessengerInPlayStore(Context context) { try { startViewUri(context, "market://details?id=" + PACKAGE_NAME); } catch (ActivityNotFoundException anfe) { startViewUri(context, "http://play.google.com/store/apps/details?id=" + PACKAGE_NAME); } } private static Set<Integer> getAllAvailableProtocolVersions(Context context) { ContentResolver contentResolver = context.getContentResolver(); Set<Integer> allAvailableVersions = new HashSet<Integer>(); Uri uri = Uri.parse("content://com.facebook.orca.provider.MessengerPlatformProvider/versions"); String [] projection = new String[]{ "version" }; Cursor c = contentResolver.query(uri, projection, null, null, null); if (c != null) { try { int versionColumnIndex = c.getColumnIndex("version"); while (c.moveToNext()) { int version = c.getInt(versionColumnIndex); allAvailableVersions.add(version); } } finally { c.close(); } } return allAvailableVersions; } private static void startViewUri(Context context, String uri) { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(uri))); } private static List<String> parseParticipants(String s) { if (s == null || s.length() == 0) { return Collections.emptyList(); } String[] parts = s.split(","); List<String> ret = new ArrayList<String>(); for (String part : parts) { ret.add(part.trim()); } return ret; } }