Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.facebook.internal;
18
 
19
import android.content.ContentResolver;
20
import android.content.Context;
21
import android.content.Intent;
22
import android.content.pm.*;
23
import android.database.Cursor;
24
import android.net.Uri;
25
import android.os.Build;
26
import android.os.Bundle;
27
import android.text.TextUtils;
28
import com.facebook.FacebookException;
29
import com.facebook.FacebookOperationCanceledException;
30
import com.facebook.SessionDefaultAudience;
31
import com.facebook.Settings;
32
 
33
import java.util.*;
34
 
35
/**
36
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for Android. Use of
37
 * any of the classes in this package is unsupported, and they may be modified or removed without warning at
38
 * any time.
39
 */
40
public final class NativeProtocol {
41
 
42
    public static final int NO_PROTOCOL_AVAILABLE = -1;
43
 
44
    private static final String FACEBOOK_PROXY_AUTH_ACTIVITY = "com.facebook.katana.ProxyAuth";
45
    private static final String FACEBOOK_TOKEN_REFRESH_ACTIVITY = "com.facebook.katana.platform.TokenRefreshService";
46
 
47
    public static final String FACEBOOK_PROXY_AUTH_PERMISSIONS_KEY = "scope";
48
    public static final String FACEBOOK_PROXY_AUTH_APP_ID_KEY = "client_id";
49
    public static final String FACEBOOK_PROXY_AUTH_E2E_KEY = "e2e";
50
 
51
    // ---------------------------------------------------------------------------------------------
52
    // Native Protocol updated 2012-11
53
 
54
    static final String INTENT_ACTION_PLATFORM_ACTIVITY = "com.facebook.platform.PLATFORM_ACTIVITY";
55
    static final String INTENT_ACTION_PLATFORM_SERVICE = "com.facebook.platform.PLATFORM_SERVICE";
56
 
57
    public static final int PROTOCOL_VERSION_20121101 = 20121101;
58
    public static final int PROTOCOL_VERSION_20130502 = 20130502;
59
    public static final int PROTOCOL_VERSION_20130618 = 20130618;
60
    public static final int PROTOCOL_VERSION_20131107 = 20131107;
61
    public static final int PROTOCOL_VERSION_20140204 = 20140204;
62
    public static final int PROTOCOL_VERSION_20140324 = 20140324;
63
    public static final int PROTOCOL_VERSION_20140701 = 20140701;
64
    public static final int PROTOCOL_VERSION_20141001 = 20141001;
65
    public static final int PROTOCOL_VERSION_20141028 = 20141028;
66
    public static final int PROTOCOL_VERSION_20141107 = 20141107; // Bucketed Result Intents
67
 
68
    public static final String EXTRA_PROTOCOL_VERSION = "com.facebook.platform.protocol.PROTOCOL_VERSION";
69
    public static final String EXTRA_PROTOCOL_ACTION = "com.facebook.platform.protocol.PROTOCOL_ACTION";
70
    public static final String EXTRA_PROTOCOL_CALL_ID = "com.facebook.platform.protocol.CALL_ID";
71
    public static final String EXTRA_GET_INSTALL_DATA_PACKAGE = "com.facebook.platform.extra.INSTALLDATA_PACKAGE";
72
 
73
    public static final String EXTRA_PROTOCOL_BRIDGE_ARGS =
74
            "com.facebook.platform.protocol.BRIDGE_ARGS";
75
 
76
    public static final String EXTRA_PROTOCOL_METHOD_ARGS =
77
            "com.facebook.platform.protocol.METHOD_ARGS";
78
 
79
    public static final String EXTRA_PROTOCOL_METHOD_RESULTS =
80
            "com.facebook.platform.protocol.RESULT_ARGS";
81
 
82
    public static final String BRIDGE_ARG_APP_NAME_STRING = "app_name";
83
    public static final String BRIDGE_ARG_ACTION_ID_STRING = "action_id";
84
    public static final String BRIDGE_ARG_ERROR_BUNDLE = "error";
85
 
86
    // Messages supported by PlatformService:
87
    public static final int MESSAGE_GET_ACCESS_TOKEN_REQUEST    = 0x10000;
88
    public static final int MESSAGE_GET_ACCESS_TOKEN_REPLY      = 0x10001;
89
    static final int MESSAGE_GET_PROTOCOL_VERSIONS_REQUEST      = 0x10002;
90
    static final int MESSAGE_GET_PROTOCOL_VERSIONS_REPLY        = 0x10003;
91
    public static final int MESSAGE_GET_INSTALL_DATA_REQUEST    = 0x10004;
92
    public static final int MESSAGE_GET_INSTALL_DATA_REPLY      = 0x10005;
93
    public static final int MESSAGE_GET_LIKE_STATUS_REQUEST     = 0x10006;
94
    public static final int MESSAGE_GET_LIKE_STATUS_REPLY       = 0x10007;
95
 
96
    // MESSAGE_ERROR_REPLY data keys:
97
    // See STATUS_*
98
 
99
    // MESSAGE_GET_ACCESS_TOKEN_REQUEST data keys:
100
    // EXTRA_APPLICATION_ID
101
 
102
    // MESSAGE_GET_ACCESS_TOKEN_REPLY data keys:
103
    // EXTRA_ACCESS_TOKEN
104
    // EXTRA_EXPIRES_SECONDS_SINCE_EPOCH
105
    // EXTRA_PERMISSIONS
106
 
107
    // MESSAGE_GET_LIKE_STATUS_REQUEST data keys:
108
    // EXTRA_APPLICATION_ID
109
    // EXTRA_OBJECT_ID
110
 
111
    // MESSAGE_GET_LIKE_STATUS_REPLY data keys:
112
    // EXTRA_OBJECT_IS_LIKED
113
    // EXTRA_LIKE_COUNT_STRING_WITH_LIKE
114
    // EXTRA_LIKE_COUNT_STRING_WITHOUT_LIKE
115
    // EXTRA_SOCIAL_SENTENCE_WITH_LIKE
116
    // EXTRA_SOCIAL_SENTENCE_WITHOUT_LIKE
117
    // EXTRA_UNLIKE_TOKEN
118
 
119
    // MESSAGE_GET_PROTOCOL_VERSIONS_REPLY data keys:
120
    static final String EXTRA_PROTOCOL_VERSIONS = "com.facebook.platform.extra.PROTOCOL_VERSIONS";
121
 
122
    // Values of EXTRA_PROTOCOL_ACTION supported by PlatformActivity:
123
    public static final String ACTION_FEED_DIALOG = "com.facebook.platform.action.request.FEED_DIALOG";
124
    public static final String ACTION_MESSAGE_DIALOG = "com.facebook.platform.action.request.MESSAGE_DIALOG";
125
    public static final String ACTION_OGACTIONPUBLISH_DIALOG =
126
            "com.facebook.platform.action.request.OGACTIONPUBLISH_DIALOG";
127
    public static final String ACTION_OGMESSAGEPUBLISH_DIALOG =
128
            "com.facebook.platform.action.request.OGMESSAGEPUBLISH_DIALOG";
129
    public static final String ACTION_LIKE_DIALOG =
130
            "com.facebook.platform.action.request.LIKE_DIALOG";
131
 
132
    // Values of EXTRA_PROTOCOL_ACTION values returned by PlatformActivity:
133
    public static final String ACTION_FEED_DIALOG_REPLY =
134
            "com.facebook.platform.action.reply.FEED_DIALOG";
135
    public static final String ACTION_MESSAGE_DIALOG_REPLY =
136
            "com.facebook.platform.action.reply.MESSAGE_DIALOG";
137
    public static final String ACTION_OGACTIONPUBLISH_DIALOG_REPLY =
138
            "com.facebook.platform.action.reply.OGACTIONPUBLISH_DIALOG";
139
    public static final String ACTION_OGMESSAGEPUBLISH_DIALOG_REPLY =
140
            "com.facebook.platform.action.reply.OGMESSAGEPUBLISH_DIALOG";
141
    public static final String ACTION_LIKE_DIALOG_REPLY =
142
            "com.facebook.platform.action.reply.LIKE_DIALOG";
143
 
144
    // Extras supported for ACTION_LOGIN_DIALOG:
145
    public static final String EXTRA_PERMISSIONS = "com.facebook.platform.extra.PERMISSIONS";
146
    public static final String EXTRA_APPLICATION_ID = "com.facebook.platform.extra.APPLICATION_ID";
147
    public static final String EXTRA_APPLICATION_NAME = "com.facebook.platform.extra.APPLICATION_NAME";
148
 
149
    // Extras returned by setResult() for ACTION_LOGIN_DIALOG
150
    public static final String EXTRA_ACCESS_TOKEN = "com.facebook.platform.extra.ACCESS_TOKEN";
151
    public static final String EXTRA_EXPIRES_SECONDS_SINCE_EPOCH =
152
            "com.facebook.platform.extra.EXPIRES_SECONDS_SINCE_EPOCH";
153
    // EXTRA_PERMISSIONS
154
 
155
    public static final String RESULT_ARGS_ACCESS_TOKEN = "access_token";
156
    public static final String RESULT_ARGS_EXPIRES_SECONDS_SINCE_EPOCH =
157
            "expires_seconds_since_epoch";
158
    public static final String RESULT_ARGS_PERMISSIONS = "permissions";
159
 
160
    // Extras supported for ACTION_FEED_DIALOG:
161
    public static final String EXTRA_PLACE_TAG = "com.facebook.platform.extra.PLACE";
162
    public static final String EXTRA_FRIEND_TAGS = "com.facebook.platform.extra.FRIENDS";
163
    public static final String EXTRA_LINK = "com.facebook.platform.extra.LINK";
164
    public static final String EXTRA_IMAGE = "com.facebook.platform.extra.IMAGE";
165
    public static final String EXTRA_TITLE = "com.facebook.platform.extra.TITLE";
166
    public static final String EXTRA_SUBTITLE = "com.facebook.platform.extra.SUBTITLE";
167
    public static final String EXTRA_DESCRIPTION = "com.facebook.platform.extra.DESCRIPTION";
168
    public static final String EXTRA_REF = "com.facebook.platform.extra.REF";
169
    public static final String EXTRA_DATA_FAILURES_FATAL = "com.facebook.platform.extra.DATA_FAILURES_FATAL";
170
    public static final String EXTRA_PHOTOS = "com.facebook.platform.extra.PHOTOS";
171
 
172
    public static final String METHOD_ARGS_PLACE_TAG = "PLACE";
173
    public static final String METHOD_ARGS_FRIEND_TAGS = "FRIENDS";
174
    public static final String METHOD_ARGS_LINK = "LINK";
175
    public static final String METHOD_ARGS_IMAGE = "IMAGE";
176
    public static final String METHOD_ARGS_TITLE = "TITLE";
177
    public static final String METHOD_ARGS_SUBTITLE = "SUBTITLE";
178
    public static final String METHOD_ARGS_DESCRIPTION = "DESCRIPTION";
179
    public static final String METHOD_ARGS_REF = "REF";
180
    public static final String METHOD_ARGS_DATA_FAILURES_FATAL = "DATA_FAILURES_FATAL";
181
    public static final String METHOD_ARGS_PHOTOS = "PHOTOS";
182
    public static final String METHOD_ARGS_VIDEO = "VIDEO";
183
 
184
    // Extras supported for ACTION_OGACTIONPUBLISH_DIALOG:
185
    public static final String EXTRA_ACTION = "com.facebook.platform.extra.ACTION";
186
    public static final String EXTRA_ACTION_TYPE = "com.facebook.platform.extra.ACTION_TYPE";
187
    public static final String EXTRA_PREVIEW_PROPERTY_NAME =
188
            "com.facebook.platform.extra.PREVIEW_PROPERTY_NAME";
189
 
190
    public static final String METHOD_ARGS_ACTION = "ACTION";
191
    public static final String METHOD_ARGS_ACTION_TYPE = "ACTION_TYPE";
192
    public static final String METHOD_ARGS_PREVIEW_PROPERTY_NAME = "PREVIEW_PROPERTY_NAME";
193
 
194
    // Extras supported for MESSAGE_GET_LIKE_STATUS_REQUEST:
195
    public static final String EXTRA_OBJECT_ID = "com.facebook.platform.extra.OBJECT_ID";
196
 
197
    // Extras supported in MESSAGE_GET_LIKE_STATUS_REPLY:
198
    public static final String EXTRA_OBJECT_IS_LIKED = "com.facebook.platform.extra.OBJECT_IS_LIKED";
199
    public static final String EXTRA_LIKE_COUNT_STRING_WITH_LIKE = "com.facebook.platform.extra.LIKE_COUNT_STRING_WITH_LIKE";
200
    public static final String EXTRA_LIKE_COUNT_STRING_WITHOUT_LIKE = "com.facebook.platform.extra.LIKE_COUNT_STRING_WITHOUT_LIKE";
201
    public static final String EXTRA_SOCIAL_SENTENCE_WITH_LIKE = "com.facebook.platform.extra.SOCIAL_SENTENCE_WITH_LIKE";
202
    public static final String EXTRA_SOCIAL_SENTENCE_WITHOUT_LIKE = "com.facebook.platform.extra.SOCIAL_SENTENCE_WITHOUT_LIKE";
203
    public static final String EXTRA_UNLIKE_TOKEN = "com.facebook.platform.extra.UNLIKE_TOKEN";
204
 
205
    // OG objects will have this key to set to true if they should be created as part of OG Action publish
206
    public static final String OPEN_GRAPH_CREATE_OBJECT_KEY = "fbsdk:create_object";
207
    // Determines whether an image is user generated
208
    public static final String IMAGE_USER_GENERATED_KEY = "user_generated";
209
    // url key for images
210
    public static final String IMAGE_URL_KEY = "url";
211
 
212
    // Method args supported for ACTION_LIKE_DIALOG
213
    public static final String METHOD_ARGS_OBJECT_ID = "object_id";
214
 
215
    // Keys for status data in MESSAGE_ERROR_REPLY from PlatformService and for error
216
    // extras returned by PlatformActivity's setResult() in case of errors:
217
    public static final String STATUS_ERROR_TYPE = "com.facebook.platform.status.ERROR_TYPE";
218
    public static final String STATUS_ERROR_DESCRIPTION =
219
            "com.facebook.platform.status.ERROR_DESCRIPTION";
220
    public static final String STATUS_ERROR_CODE = "com.facebook.platform.status.ERROR_CODE";
221
    public static final String STATUS_ERROR_SUBCODE = "com.facebook.platform.status.ERROR_SUBCODE";
222
    public static final String STATUS_ERROR_JSON = "com.facebook.platform.status.ERROR_JSON";
223
 
224
    public static final String BRIDGE_ARG_ERROR_TYPE = "error_type";
225
    public static final String BRIDGE_ARG_ERROR_DESCRIPTION = "error_description";
226
    public static final String BRIDGE_ARG_ERROR_CODE = "error_code";
227
    public static final String BRIDGE_ARG_ERROR_SUBCODE = "error_subcode";
228
    public static final String BRIDGE_ARG_ERROR_JSON = "error_json";
229
 
230
    // Expected values for ERROR_KEY_TYPE.  Clients should tolerate other values:
231
    public static final String ERROR_UNKNOWN_ERROR = "UnknownError";
232
    public static final String ERROR_PROTOCOL_ERROR = "ProtocolError";
233
    public static final String ERROR_USER_CANCELED = "UserCanceled";
234
    public static final String ERROR_APPLICATION_ERROR = "ApplicationError";
235
    public static final String ERROR_NETWORK_ERROR = "NetworkError";
236
    public static final String ERROR_PERMISSION_DENIED = "PermissionDenied";
237
    public static final String ERROR_SERVICE_DISABLED = "ServiceDisabled";
238
 
239
    public static final String AUDIENCE_ME = "only_me";
240
    public static final String AUDIENCE_FRIENDS = "friends";
241
    public static final String AUDIENCE_EVERYONE = "everyone";
242
 
243
    // Request codes for different categories of native protocol calls.
244
    public static final int DIALOG_REQUEST_CODE = 0xfacf;
245
 
246
    private static final String CONTENT_SCHEME = "content://";
247
    private static final String PLATFORM_PROVIDER_VERSIONS = ".provider.PlatformProvider/versions";
248
 
249
    // Columns returned by PlatformProvider
250
    private static final String PLATFORM_PROVIDER_VERSION_COLUMN = "version";
251
 
252
    private static abstract class NativeAppInfo {
253
        abstract protected String getPackage();
254
 
255
        private static final String FBI_HASH = "a4b7452e2ed8f5f191058ca7bbfd26b0d3214bfc";
256
        private static final String FBL_HASH = "5e8f16062ea3cd2c4a0d547876baa6f38cabf625";
257
        private static final String FBR_HASH = "8a3c4b262d721acd49a4bf97d5213199c86fa2b9";
258
 
259
        private static final HashSet<String> validAppSignatureHashes = buildAppSignatureHashes();
260
 
261
        private static HashSet<String> buildAppSignatureHashes() {
262
            HashSet<String> set = new HashSet<String>();
263
            set.add(FBR_HASH);
264
            set.add(FBI_HASH);
265
            set.add(FBL_HASH);
266
            return set;
267
        }
268
 
269
        public boolean validateSignature(Context context, String packageName) {
270
            String brand = Build.BRAND;
271
            int applicationFlags = context.getApplicationInfo().flags;
272
            if (brand.startsWith("generic") && (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
273
                // We are debugging on an emulator, don't validate package signature.
274
                return true;
275
            }
276
 
277
            PackageInfo packageInfo = null;
278
            try {
279
                packageInfo = context.getPackageManager().getPackageInfo(packageName,
280
                        PackageManager.GET_SIGNATURES);
281
            } catch (PackageManager.NameNotFoundException e) {
282
                return false;
283
            }
284
 
285
            for (Signature signature : packageInfo.signatures) {
286
                String hashedSignature = Utility.sha1hash(signature.toByteArray());
287
                if (validAppSignatureHashes.contains(hashedSignature)) {
288
                    return true;
289
                }
290
            }
291
 
292
            return false;
293
        }
294
    }
295
 
296
    private static class KatanaAppInfo extends NativeAppInfo {
297
        static final String KATANA_PACKAGE = "com.facebook.katana";
298
 
299
        @Override
300
        protected String getPackage() {
301
            return KATANA_PACKAGE;
302
        }
303
    }
304
 
305
    private static class MessengerAppInfo extends NativeAppInfo {
306
        static final String MESSENGER_PACKAGE = "com.facebook.orca";
307
 
308
        @Override
309
        protected String getPackage() {
310
            return MESSENGER_PACKAGE;
311
        }
312
    }
313
 
314
    private static class WakizashiAppInfo extends NativeAppInfo {
315
        static final String WAKIZASHI_PACKAGE = "com.facebook.wakizashi";
316
 
317
        @Override
318
        protected String getPackage() {
319
            return WAKIZASHI_PACKAGE;
320
        }
321
    }
322
 
323
    private static final NativeAppInfo FACEBOOK_APP_INFO = new KatanaAppInfo();
324
    private static List<NativeAppInfo> facebookAppInfoList = buildFacebookAppList();
325
    private static Map<String, List<NativeAppInfo>> actionToAppInfoMap = buildActionToAppInfoMap();
326
 
327
    private static List<NativeAppInfo> buildFacebookAppList() {
328
        List<NativeAppInfo> list = new ArrayList<NativeAppInfo>();
329
 
330
        // Katana needs to be the first thing in the list since it will get selected as the default FACEBOOK_APP_INFO
331
        list.add(FACEBOOK_APP_INFO);
332
        list.add(new WakizashiAppInfo());
333
 
334
        return list;
335
    }
336
 
337
    private static Map<String, List<NativeAppInfo>> buildActionToAppInfoMap() {
338
        Map<String, List<NativeAppInfo>> map = new HashMap<String, List<NativeAppInfo>>();
339
 
340
        ArrayList<NativeAppInfo> messengerAppInfoList = new ArrayList<NativeAppInfo>();
341
        messengerAppInfoList.add(new MessengerAppInfo());
342
 
343
        // Add individual actions and the list they should try
344
        map.put(ACTION_OGACTIONPUBLISH_DIALOG, facebookAppInfoList);
345
        map.put(ACTION_FEED_DIALOG, facebookAppInfoList);
346
        map.put(ACTION_LIKE_DIALOG, facebookAppInfoList);
347
        map.put(ACTION_MESSAGE_DIALOG, messengerAppInfoList);
348
        map.put(ACTION_OGMESSAGEPUBLISH_DIALOG, messengerAppInfoList);
349
 
350
        return map;
351
    }
352
 
353
    static Intent validateActivityIntent(Context context, Intent intent, NativeAppInfo appInfo) {
354
        if (intent == null) {
355
            return null;
356
        }
357
 
358
        ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
359
        if (resolveInfo == null) {
360
            return null;
361
        }
362
 
363
        if (!appInfo.validateSignature(context, resolveInfo.activityInfo.packageName)) {
364
            return null;
365
        }
366
 
367
        return intent;
368
    }
369
 
370
    static Intent validateServiceIntent(Context context, Intent intent, NativeAppInfo appInfo) {
371
        if (intent == null) {
372
            return null;
373
        }
374
 
375
        ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, 0);
376
        if (resolveInfo == null) {
377
            return null;
378
        }
379
 
380
        if (!appInfo.validateSignature(context, resolveInfo.serviceInfo.packageName)) {
381
            return null;
382
        }
383
 
384
        return intent;
385
    }
386
 
387
    public static Intent createProxyAuthIntent(Context context, String applicationId, List<String> permissions,
388
            String e2e, boolean isRerequest, SessionDefaultAudience defaultAudience) {
389
        for (NativeAppInfo appInfo : facebookAppInfoList) {
390
            Intent intent = new Intent()
391
                    .setClassName(appInfo.getPackage(), FACEBOOK_PROXY_AUTH_ACTIVITY)
392
                    .putExtra(FACEBOOK_PROXY_AUTH_APP_ID_KEY, applicationId);
393
 
394
            if (!Utility.isNullOrEmpty(permissions)) {
395
                intent.putExtra(FACEBOOK_PROXY_AUTH_PERMISSIONS_KEY, TextUtils.join(",", permissions));
396
            }
397
            if (!Utility.isNullOrEmpty(e2e)) {
398
                intent.putExtra(FACEBOOK_PROXY_AUTH_E2E_KEY, e2e);
399
            }
400
 
401
            intent.putExtra(ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE, ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN);
402
            intent.putExtra(ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE);
403
            intent.putExtra(ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, defaultAudience.getNativeProtocolAudience());
404
 
405
            if (!Settings.getPlatformCompatibilityEnabled()) {
406
                // Override the API Version for Auth
407
                intent.putExtra(ServerProtocol.DIALOG_PARAM_LEGACY_OVERRIDE, ServerProtocol.GRAPH_API_VERSION);
408
 
409
                // Only set the rerequest auth type for non legacy requests
410
                if (isRerequest) {
411
                    intent.putExtra(ServerProtocol.DIALOG_PARAM_AUTH_TYPE, ServerProtocol.DIALOG_REREQUEST_AUTH_TYPE);
412
                }
413
            }
414
 
415
            intent = validateActivityIntent(context, intent, appInfo);
416
 
417
            if (intent != null) {
418
                return intent;
419
            }
420
        }
421
        return null;
422
    }
423
 
424
    public static Intent createTokenRefreshIntent(Context context) {
425
        for (NativeAppInfo appInfo : facebookAppInfoList) {
426
            Intent intent = new Intent()
427
                    .setClassName(appInfo.getPackage(), FACEBOOK_TOKEN_REFRESH_ACTIVITY);
428
 
429
            intent = validateServiceIntent(context, intent, appInfo);
430
 
431
            if (intent != null) {
432
                return intent;
433
            }
434
        }
435
        return null;
436
    }
437
 
438
    public static final int getLatestKnownVersion() {
439
        return KNOWN_PROTOCOL_VERSIONS.get(0);
440
    }
441
 
442
    // Note: be sure this stays sorted in descending order; add new versions at the beginning
443
    private static final List<Integer> KNOWN_PROTOCOL_VERSIONS =
444
            Arrays.asList(
445
                    PROTOCOL_VERSION_20141107,
446
                    PROTOCOL_VERSION_20141028,
447
                    PROTOCOL_VERSION_20141001,
448
                    PROTOCOL_VERSION_20140701,
449
                    PROTOCOL_VERSION_20140324,
450
                    PROTOCOL_VERSION_20140204,
451
                    PROTOCOL_VERSION_20131107,
452
                    PROTOCOL_VERSION_20130618,
453
                    PROTOCOL_VERSION_20130502,
454
                    PROTOCOL_VERSION_20121101
455
            );
456
 
457
    private static Intent findActivityIntent(Context context, String activityAction, String internalAction) {
458
        List<NativeAppInfo> list = actionToAppInfoMap.get(internalAction);
459
        if (list == null) {
460
            return null;
461
        }
462
 
463
        Intent intent = null;
464
        for (NativeAppInfo appInfo : list) {
465
            intent = new Intent()
466
                    .setAction(activityAction)
467
                    .setPackage(appInfo.getPackage())
468
                    .addCategory(Intent.CATEGORY_DEFAULT);
469
            intent = validateActivityIntent(context, intent, appInfo);
470
            if (intent != null) {
471
                return intent;
472
            }
473
        }
474
 
475
        return intent;
476
    }
477
 
478
    public static boolean isVersionCompatibleWithBucketedIntent(int version) {
479
        return KNOWN_PROTOCOL_VERSIONS.contains(version) && version >= PROTOCOL_VERSION_20140701;
480
    }
481
 
482
    public static Intent createPlatformActivityIntent(
483
            Context context,
484
            String callId,
485
            String action,
486
            int version,
487
            String applicationName,
488
            Bundle extras) {
489
        Intent intent = findActivityIntent(context, INTENT_ACTION_PLATFORM_ACTIVITY, action);
490
        if (intent == null) {
491
            return null;
492
        }
493
 
494
        String applicationId = Utility.getMetadataApplicationId(context);
495
 
496
        intent.putExtra(EXTRA_PROTOCOL_VERSION, version)
497
                .putExtra(EXTRA_PROTOCOL_ACTION, action)
498
                .putExtra(EXTRA_APPLICATION_ID, applicationId);
499
 
500
        if (isVersionCompatibleWithBucketedIntent(version)) {
501
            // This is a bucketed intent
502
            Bundle bridgeArguments = new Bundle();
503
            bridgeArguments.putString(BRIDGE_ARG_ACTION_ID_STRING, callId);
504
            bridgeArguments.putString(BRIDGE_ARG_APP_NAME_STRING, applicationName);
505
            intent.putExtra(EXTRA_PROTOCOL_BRIDGE_ARGS, bridgeArguments);
506
 
507
            Bundle methodArguments = (extras == null) ? new Bundle() : extras;
508
            intent.putExtra(EXTRA_PROTOCOL_METHOD_ARGS, methodArguments);
509
        } else {
510
            // This is the older flat intent
511
            intent.putExtra(EXTRA_PROTOCOL_CALL_ID, callId);
512
            intent.putExtra(EXTRA_APPLICATION_NAME, applicationName);
513
            intent.putExtras(extras);
514
        }
515
 
516
        return intent;
517
    }
518
 
519
    public static Intent createPlatformServiceIntent(Context context) {
520
        for (NativeAppInfo appInfo : facebookAppInfoList) {
521
            Intent intent = new Intent(INTENT_ACTION_PLATFORM_SERVICE)
522
                    .setPackage(appInfo.getPackage())
523
                    .addCategory(Intent.CATEGORY_DEFAULT);
524
            intent = validateServiceIntent(context, intent, appInfo);
525
            if (intent != null) {
526
                return intent;
527
            }
528
        }
529
        return null;
530
    }
531
 
532
    public static int getProtocolVersionFromIntent(Intent intent) {
533
        return intent.getIntExtra(EXTRA_PROTOCOL_VERSION, 0);
534
    }
535
 
536
    public static UUID getCallIdFromIntent(Intent intent) {
537
        if (intent == null) {
538
            return null;
539
        }
540
        int version = getProtocolVersionFromIntent(intent);
541
        String callIdString = null;
542
        if (isVersionCompatibleWithBucketedIntent(version)) {
543
            Bundle bridgeArgs = intent.getBundleExtra(EXTRA_PROTOCOL_BRIDGE_ARGS);
544
            if (bridgeArgs != null) {
545
                callIdString = bridgeArgs.getString(BRIDGE_ARG_ACTION_ID_STRING);
546
            }
547
        } else {
548
            callIdString = intent.getStringExtra(EXTRA_PROTOCOL_CALL_ID);
549
        }
550
 
551
        UUID callId = null;
552
        if (callIdString != null) {
553
            try {
554
                callId = UUID.fromString(callIdString);
555
            } catch (IllegalArgumentException exception) {
556
            }
557
        }
558
        return callId;
559
    }
560
 
561
    public static Bundle getBridgeArgumentsFromIntent(Intent intent) {
562
        int version = getProtocolVersionFromIntent(intent);
563
        if (!isVersionCompatibleWithBucketedIntent(version)) {
564
            return null;
565
        }
566
 
567
        return intent.getBundleExtra(EXTRA_PROTOCOL_BRIDGE_ARGS);
568
    }
569
 
570
    public static Bundle getSuccessResultsFromIntent(Intent resultIntent) {
571
        int version = getProtocolVersionFromIntent(resultIntent);
572
        Bundle extras = resultIntent.getExtras();
573
        if (!isVersionCompatibleWithBucketedIntent(version) || extras == null) {
574
            return extras;
575
        }
576
 
577
        return extras.getBundle(EXTRA_PROTOCOL_METHOD_RESULTS);
578
    }
579
 
580
    public static boolean isErrorResult(Intent resultIntent) {
581
        Bundle bridgeArgs = getBridgeArgumentsFromIntent(resultIntent);
582
        if (bridgeArgs != null) {
583
            return bridgeArgs.containsKey(BRIDGE_ARG_ERROR_BUNDLE);
584
        } else {
585
            return resultIntent.hasExtra(STATUS_ERROR_TYPE);
586
        }
587
    }
588
 
589
    public static Bundle getErrorDataFromResultIntent(Intent resultIntent) {
590
        if (!isErrorResult(resultIntent)) {
591
            return null;
592
        }
593
 
594
        Bundle bridgeArgs = getBridgeArgumentsFromIntent(resultIntent);
595
        if (bridgeArgs != null) {
596
            return bridgeArgs.getBundle(BRIDGE_ARG_ERROR_BUNDLE);
597
        }
598
 
599
        return resultIntent.getExtras();
600
    }
601
 
602
    public static Exception getExceptionFromErrorData(Bundle errorData) {
603
        if (errorData == null) {
604
            return null;
605
        }
606
 
607
        String type = errorData.getString(BRIDGE_ARG_ERROR_TYPE);
608
        if (type == null) {
609
            type = errorData.getString(STATUS_ERROR_TYPE);
610
        }
611
 
612
        String description = errorData.getString(BRIDGE_ARG_ERROR_DESCRIPTION);
613
        if (description == null) {
614
            description = errorData.getString(STATUS_ERROR_DESCRIPTION);
615
        }
616
 
617
        if (type != null && type.equalsIgnoreCase(ERROR_USER_CANCELED)) {
618
            return new FacebookOperationCanceledException(description);
619
        }
620
 
621
        /* TODO parse error values and create appropriate exception class */
622
        return new FacebookException(description);
623
    }
624
 
625
    public static int getLatestAvailableProtocolVersionForService(Context context, final int minimumVersion) {
626
        // Services are currently always against the Facebook App
627
        return getLatestAvailableProtocolVersionForAppInfoList(context, facebookAppInfoList, new int[] {minimumVersion});
628
    }
629
 
630
    public static int getLatestAvailableProtocolVersionForAction(Context context, String action, int[] versionSpec) {
631
        List<NativeAppInfo> appInfoList = actionToAppInfoMap.get(action);
632
        return getLatestAvailableProtocolVersionForAppInfoList(context, appInfoList, versionSpec);
633
    }
634
 
635
    private static int getLatestAvailableProtocolVersionForAppInfoList(Context context, List<NativeAppInfo> appInfoList,
636
            int[] versionSpec) {
637
        if (appInfoList == null) {
638
            return NO_PROTOCOL_AVAILABLE;
639
        }
640
 
641
        // Could potentially cache the NativeAppInfo to latestProtocolVersion
642
        for (NativeAppInfo appInfo : appInfoList) {
643
            int protocolVersion = getLatestAvailableProtocolVersionForAppInfo(context, appInfo, versionSpec);
644
            if (protocolVersion != NO_PROTOCOL_AVAILABLE) {
645
                return protocolVersion;
646
            }
647
        }
648
 
649
        return NO_PROTOCOL_AVAILABLE;
650
    }
651
 
652
    private static int getLatestAvailableProtocolVersionForAppInfo(
653
            Context context,
654
            NativeAppInfo appInfo,
655
            int[] versionSpec) {
656
        TreeSet<Integer> fbAppVersions = getAllAvailableProtocolVersionsForAppInfo(context, appInfo);
657
        return computeLatestAvailableVersionFromVersionSpec(fbAppVersions, getLatestKnownVersion(), versionSpec);
658
    }
659
 
660
    private static TreeSet<Integer> getAllAvailableProtocolVersionsForAppInfo(
661
            Context context,
662
            NativeAppInfo appInfo) {
663
        TreeSet<Integer> allAvailableVersions = new TreeSet<Integer>();
664
 
665
        ContentResolver contentResolver = context.getContentResolver();
666
 
667
        String [] projection = new String[]{ PLATFORM_PROVIDER_VERSION_COLUMN };
668
        Uri uri = buildPlatformProviderVersionURI(appInfo);
669
        Cursor c = null;
670
        try {
671
            c = contentResolver.query(uri, projection, null, null, null);
672
            if (c != null) {
673
                while (c.moveToNext()) {
674
                    int version = c.getInt(c.getColumnIndex(PLATFORM_PROVIDER_VERSION_COLUMN));
675
                    allAvailableVersions.add(version);
676
                }
677
            }
678
        } finally {
679
            if (c != null) {
680
                c.close();
681
            }
682
        }
683
 
684
        return allAvailableVersions;
685
    }
686
 
687
    /**
688
     * This is public to allow for testing. Developers are discouraged from using this method, since it may change without
689
     * notice.
690
     */
691
    public static int computeLatestAvailableVersionFromVersionSpec(
692
            TreeSet<Integer> allAvailableFacebookAppVersions,
693
            int latestSdkVersion,
694
            int[] versionSpec) {
695
        // Remember that these ranges are sorted in ascending order and can be unbounded. So we are starting
696
        // from the end of the version-spec array and working backwards, to try get the newest possible version
697
        int versionSpecIndex = versionSpec.length - 1;
698
        Iterator<Integer> fbAppVersionsIterator = allAvailableFacebookAppVersions.descendingIterator();
699
        int latestFacebookAppVersion = -1;
700
 
701
        while (fbAppVersionsIterator.hasNext()) {
702
            int fbAppVersion = fbAppVersionsIterator.next();
703
 
704
            // We're holding on to the greatest fb-app version available.
705
            latestFacebookAppVersion = Math.max(latestFacebookAppVersion, fbAppVersion);
706
 
707
            // If there is a newer version in the versionSpec, throw it away, we don't have it
708
            while (versionSpecIndex >= 0 && versionSpec[versionSpecIndex] > fbAppVersion) {
709
                versionSpecIndex--;
710
            }
711
 
712
            if (versionSpecIndex < 0) {
713
                // There was no fb app version that fell into any range in the versionSpec - or - the
714
                // versionSpec was empty, which means that this action is not supported.
715
                return NO_PROTOCOL_AVAILABLE;
716
            }
717
 
718
            // If we are here, we know we are within a range specified in the versionSpec. We should see if it is
719
            // a disabled or enabled range.
720
 
721
            if (versionSpec[versionSpecIndex] == fbAppVersion) {
722
                // if the versionSpecIndex is even, it is enabled; if odd, disabled
723
                return (
724
                        versionSpecIndex % 2 == 0 ?
725
                                Math.min(latestFacebookAppVersion, latestSdkVersion) :
726
                                NO_PROTOCOL_AVAILABLE
727
                );
728
            }
729
        }
730
 
731
        return NO_PROTOCOL_AVAILABLE;
732
    }
733
 
734
    private static Uri buildPlatformProviderVersionURI(NativeAppInfo appInfo) {
735
        return Uri.parse(CONTENT_SCHEME + appInfo.getPackage() + PLATFORM_PROVIDER_VERSIONS);
736
    }
737
}