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;
18
 
19
import android.annotation.SuppressLint;
20
import android.app.Activity;
21
import android.content.*;
22
import android.content.pm.ResolveInfo;
23
import android.os.*;
24
import android.support.v4.app.Fragment;
25
import android.support.v4.content.LocalBroadcastManager;
26
import android.text.TextUtils;
27
import android.util.Log;
28
import com.facebook.internal.NativeProtocol;
29
import com.facebook.internal.SessionAuthorizationType;
30
import com.facebook.internal.Utility;
31
import com.facebook.internal.Validate;
32
import com.facebook.model.GraphMultiResult;
33
import com.facebook.model.GraphObject;
34
import com.facebook.model.GraphObjectList;
35
import org.json.JSONException;
36
import org.json.JSONObject;
37
 
38
import java.io.*;
39
import java.lang.ref.WeakReference;
40
import java.util.*;
41
 
42
/**
43
 * <p>
44
 * Session is used to authenticate a user and manage the user's session with
45
 * Facebook.
46
 * </p>
47
 * <p>
48
 * Sessions must be opened before they can be used to make a Request. When a
49
 * Session is created, it attempts to initialize itself from a TokenCachingStrategy.
50
 * Closing the session can optionally clear this cache.  The Session lifecycle
51
 * uses {@link SessionState SessionState} to indicate its state. Once a Session has
52
 * been closed, it can't be re-opened; a new Session must be created.
53
 * </p>
54
 * <p>
55
 * Instances of Session provide state change notification via a callback
56
 * interface, {@link Session.StatusCallback StatusCallback}.
57
 * </p>
58
 */
59
public class Session implements Serializable {
60
    private static final long serialVersionUID = 1L;
61
 
62
    /**
63
     * The logging tag used by Session.
64
     */
65
    public static final String TAG = Session.class.getCanonicalName();
66
 
67
    /**
68
     * The default activity code used for authorization.
69
     *
70
     * @see #openForRead(OpenRequest)
71
     *      open
72
     */
73
    public static final int DEFAULT_AUTHORIZE_ACTIVITY_CODE = 0xface;
74
 
75
    /**
76
     * If Session authorization fails and provides a web view error code, the
77
     * web view error code is stored in the Bundle returned from
78
     * {@link #getAuthorizationBundle getAuthorizationBundle} under this key.
79
     */
80
    public static final String WEB_VIEW_ERROR_CODE_KEY = "com.facebook.sdk.WebViewErrorCode";
81
 
82
    /**
83
     * If Session authorization fails and provides a failing url, the failing
84
     * url is stored in the Bundle returned from {@link #getAuthorizationBundle
85
     * getAuthorizationBundle} under this key.
86
     */
87
    public static final String WEB_VIEW_FAILING_URL_KEY = "com.facebook.sdk.FailingUrl";
88
 
89
    /**
90
     * The action used to indicate that the active session has been set. This should
91
     * be used as an action in an IntentFilter and BroadcastReceiver registered with
92
     * the {@link android.support.v4.content.LocalBroadcastManager}.
93
     */
94
    public static final String ACTION_ACTIVE_SESSION_SET = "com.facebook.sdk.ACTIVE_SESSION_SET";
95
 
96
    /**
97
     * The action used to indicate that the active session has been set to null. This should
98
     * be used as an action in an IntentFilter and BroadcastReceiver registered with
99
     * the {@link android.support.v4.content.LocalBroadcastManager}.
100
     */
101
    public static final String ACTION_ACTIVE_SESSION_UNSET = "com.facebook.sdk.ACTIVE_SESSION_UNSET";
102
 
103
    /**
104
     * The action used to indicate that the active session has been opened. This should
105
     * be used as an action in an IntentFilter and BroadcastReceiver registered with
106
     * the {@link android.support.v4.content.LocalBroadcastManager}.
107
     */
108
    public static final String ACTION_ACTIVE_SESSION_OPENED = "com.facebook.sdk.ACTIVE_SESSION_OPENED";
109
 
110
    /**
111
     * The action used to indicate that the active session has been closed. This should
112
     * be used as an action in an IntentFilter and BroadcastReceiver registered with
113
     * the {@link android.support.v4.content.LocalBroadcastManager}.
114
     */
115
    public static final String ACTION_ACTIVE_SESSION_CLOSED = "com.facebook.sdk.ACTIVE_SESSION_CLOSED";
116
 
117
    private static final Object STATIC_LOCK = new Object();
118
    private static Session activeSession;
119
    private static volatile Context staticContext;
120
 
121
    // Token extension constants
122
    private static final int TOKEN_EXTEND_THRESHOLD_SECONDS = 24 * 60 * 60; // 1
123
    // day
124
    private static final int TOKEN_EXTEND_RETRY_SECONDS = 60 * 60; // 1 hour
125
 
126
    private static final String SESSION_BUNDLE_SAVE_KEY = "com.facebook.sdk.Session.saveSessionKey";
127
    private static final String AUTH_BUNDLE_SAVE_KEY = "com.facebook.sdk.Session.authBundleKey";
128
    private static final String PUBLISH_PERMISSION_PREFIX = "publish";
129
    private static final String MANAGE_PERMISSION_PREFIX = "manage";
130
 
131
    @SuppressWarnings("serial")
132
    private static final Set<String> OTHER_PUBLISH_PERMISSIONS = new HashSet<String>() {{
133
        add("ads_management");
134
        add("create_event");
135
        add("rsvp_event");
136
    }};
137
 
138
    private String applicationId;
139
    private SessionState state;
140
    private AccessToken tokenInfo;
141
    private Date lastAttemptedTokenExtendDate = new Date(0);
142
 
143
    private AuthorizationRequest pendingAuthorizationRequest;
144
    private AuthorizationClient authorizationClient;
145
 
146
    // The following are not serialized with the Session object
147
    private volatile Bundle authorizationBundle;
148
    private final List<StatusCallback> callbacks;
149
    private Handler handler;
150
    private AutoPublishAsyncTask autoPublishAsyncTask;
151
    // This is the object that synchronizes access to state and tokenInfo
152
    private final Object lock = new Object();
153
    private TokenCachingStrategy tokenCachingStrategy;
154
    private volatile TokenRefreshRequest currentTokenRefreshRequest;
155
    private AppEventsLogger appEventsLogger;
156
 
157
    /**
158
     * Serialization proxy for the Session class. This is version 1 of
159
     * serialization. Future serializations may differ in format. This
160
     * class should not be modified. If serializations formats change,
161
     * create a new class SerializationProxyVx.
162
     */
163
    private static class SerializationProxyV1 implements Serializable {
164
        private static final long serialVersionUID = 7663436173185080063L;
165
        private final String applicationId;
166
        private final SessionState state;
167
        private final AccessToken tokenInfo;
168
        private final Date lastAttemptedTokenExtendDate;
169
        private final boolean shouldAutoPublish;
170
        private final AuthorizationRequest pendingAuthorizationRequest;
171
 
172
        SerializationProxyV1(String applicationId, SessionState state,
173
                AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,
174
                boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest) {
175
            this.applicationId = applicationId;
176
            this.state = state;
177
            this.tokenInfo = tokenInfo;
178
            this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;
179
            this.shouldAutoPublish = shouldAutoPublish;
180
            this.pendingAuthorizationRequest = pendingAuthorizationRequest;
181
        }
182
 
183
        private Object readResolve() {
184
            return new Session(applicationId, state, tokenInfo,
185
                    lastAttemptedTokenExtendDate, shouldAutoPublish, pendingAuthorizationRequest);
186
        }
187
    }
188
 
189
    /**
190
     * Serialization proxy for the Session class. This is version 2 of
191
     * serialization. Future serializations may differ in format. This
192
     * class should not be modified. If serializations formats change,
193
     * create a new class SerializationProxyVx.
194
     */
195
    @SuppressWarnings("UnusedDeclaration")
196
    private static class SerializationProxyV2 implements Serializable {
197
        private static final long serialVersionUID = 7663436173185080064L;
198
        private final String applicationId;
199
        private final SessionState state;
200
        private final AccessToken tokenInfo;
201
        private final Date lastAttemptedTokenExtendDate;
202
        private final boolean shouldAutoPublish;
203
        private final AuthorizationRequest pendingAuthorizationRequest;
204
        private final Set<String> requestedPermissions;
205
 
206
        SerializationProxyV2(String applicationId, SessionState state,
207
                             AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,
208
                             boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest,
209
                             Set<String> requestedPermissions) {
210
            this.applicationId = applicationId;
211
            this.state = state;
212
            this.tokenInfo = tokenInfo;
213
            this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;
214
            this.shouldAutoPublish = shouldAutoPublish;
215
            this.pendingAuthorizationRequest = pendingAuthorizationRequest;
216
            this.requestedPermissions = requestedPermissions;
217
        }
218
 
219
        private Object readResolve() {
220
            return new Session(applicationId, state, tokenInfo,
221
                    lastAttemptedTokenExtendDate, shouldAutoPublish, pendingAuthorizationRequest, requestedPermissions);
222
        }
223
    }
224
 
225
    /**
226
     * Used by version 1 of the serialization proxy, do not modify.
227
     */
228
    private Session(String applicationId, SessionState state,
229
            AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,
230
            boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest) {
231
        this.applicationId = applicationId;
232
        this.state = state;
233
        this.tokenInfo = tokenInfo;
234
        this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;
235
        this.pendingAuthorizationRequest = pendingAuthorizationRequest;
236
        handler = new Handler(Looper.getMainLooper());
237
        currentTokenRefreshRequest = null;
238
        tokenCachingStrategy = null;
239
        callbacks = new ArrayList<StatusCallback>();
240
    }
241
 
242
    /**
243
     * Used by version 2 of the serialization proxy, do not modify.
244
     */
245
    private Session(String applicationId, SessionState state,
246
                    AccessToken tokenInfo, Date lastAttemptedTokenExtendDate,
247
                    boolean shouldAutoPublish, AuthorizationRequest pendingAuthorizationRequest,
248
                    Set<String> requestedPermissions) {
249
        this.applicationId = applicationId;
250
        this.state = state;
251
        this.tokenInfo = tokenInfo;
252
        this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;
253
        this.pendingAuthorizationRequest = pendingAuthorizationRequest;
254
        handler = new Handler(Looper.getMainLooper());
255
        currentTokenRefreshRequest = null;
256
        tokenCachingStrategy = null;
257
        callbacks = new ArrayList<StatusCallback>();
258
    }
259
 
260
    /**
261
     * Initializes a new Session with the specified context.
262
     *
263
     * @param currentContext The Activity or Service creating this Session.
264
     */
265
    public Session(Context currentContext) {
266
        this(currentContext, null, null, true);
267
    }
268
 
269
    Session(Context context, String applicationId, TokenCachingStrategy tokenCachingStrategy) {
270
        this(context, applicationId, tokenCachingStrategy, true);
271
    }
272
 
273
    Session(Context context, String applicationId, TokenCachingStrategy tokenCachingStrategy,
274
            boolean loadTokenFromCache) {
275
        // if the application ID passed in is null, try to get it from the
276
        // meta-data in the manifest.
277
        if ((context != null) && (applicationId == null)) {
278
            applicationId = Utility.getMetadataApplicationId(context);
279
        }
280
 
281
        Validate.notNull(applicationId, "applicationId");
282
 
283
        initializeStaticContext(context);
284
 
285
        if (tokenCachingStrategy == null) {
286
            tokenCachingStrategy = new SharedPreferencesTokenCachingStrategy(staticContext);
287
        }
288
 
289
        this.applicationId = applicationId;
290
        this.tokenCachingStrategy = tokenCachingStrategy;
291
        this.state = SessionState.CREATED;
292
        this.pendingAuthorizationRequest = null;
293
        this.callbacks = new ArrayList<StatusCallback>();
294
        this.handler = new Handler(Looper.getMainLooper());
295
 
296
        Bundle tokenState = loadTokenFromCache ? tokenCachingStrategy.load() : null;
297
        if (TokenCachingStrategy.hasTokenInformation(tokenState)) {
298
            Date cachedExpirationDate = TokenCachingStrategy
299
                    .getDate(tokenState, TokenCachingStrategy.EXPIRATION_DATE_KEY);
300
            Date now = new Date();
301
 
302
            if ((cachedExpirationDate == null) || cachedExpirationDate.before(now)) {
303
                // If expired or we require new permissions, clear out the
304
                // current token cache.
305
                tokenCachingStrategy.clear();
306
                this.tokenInfo = AccessToken.createEmptyToken();
307
            } else {
308
                // Otherwise we have a valid token, so use it.
309
                this.tokenInfo = AccessToken.createFromCache(tokenState);
310
                this.state = SessionState.CREATED_TOKEN_LOADED;
311
            }
312
        } else {
313
            this.tokenInfo = AccessToken.createEmptyToken();
314
        }
315
    }
316
 
317
    /**
318
     * Returns a Bundle containing data that was returned from Facebook during
319
     * authorization.
320
     *
321
     * @return a Bundle containing data that was returned from Facebook during
322
     *         authorization.
323
     */
324
    public final Bundle getAuthorizationBundle() {
325
        synchronized (this.lock) {
326
            return this.authorizationBundle;
327
        }
328
    }
329
 
330
    /**
331
     * Returns a boolean indicating whether the session is opened.
332
     *
333
     * @return a boolean indicating whether the session is opened.
334
     */
335
    public final boolean isOpened() {
336
        synchronized (this.lock) {
337
            return this.state.isOpened();
338
        }
339
    }
340
 
341
    public final boolean isClosed() {
342
        synchronized (this.lock) {
343
            return this.state.isClosed();
344
        }
345
    }
346
 
347
    /**
348
     * Returns the current state of the Session.
349
     * See {@link SessionState} for details.
350
     *
351
     * @return the current state of the Session.
352
     */
353
    public final SessionState getState() {
354
        synchronized (this.lock) {
355
            return this.state;
356
        }
357
    }
358
 
359
    /**
360
     * Returns the application id associated with this Session.
361
     *
362
     * @return the application id associated with this Session.
363
     */
364
    public final String getApplicationId() {
365
        return this.applicationId;
366
    }
367
 
368
    /**
369
     * Returns the access token String.
370
     *
371
     * @return the access token String, or null if there is no access token
372
     */
373
    public final String getAccessToken() {
374
        synchronized (this.lock) {
375
            return (this.tokenInfo == null) ? null : this.tokenInfo.getToken();
376
        }
377
    }
378
 
379
    /**
380
     * <p>
381
     * Returns the Date at which the current token will expire.
382
     * </p>
383
     * <p>
384
     * Note that Session automatically attempts to extend the lifetime of Tokens
385
     * as needed when Facebook requests are made.
386
     * </p>
387
     *
388
     * @return the Date at which the current token will expire, or null if there is no access token
389
     */
390
    public final Date getExpirationDate() {
391
        synchronized (this.lock) {
392
            return (this.tokenInfo == null) ? null : this.tokenInfo.getExpires();
393
        }
394
    }
395
 
396
    /**
397
     * <p>
398
     * Returns the list of permissions associated with the session.
399
     * </p>
400
     * <p>
401
     * If there is a valid token, this represents the permissions granted by
402
     * that token. This can change during calls to
403
     * {@link #requestNewReadPermissions}
404
     * or {@link #requestNewPublishPermissions}.
405
     * </p>
406
     *
407
     * @return the list of permissions associated with the session, or null if there is no access token
408
     */
409
    public final List<String> getPermissions() {
410
        synchronized (this.lock) {
411
            return (this.tokenInfo == null) ? null : this.tokenInfo.getPermissions();
412
        }
413
    }
414
 
415
    /**
416
     * <p>
417
     *     Returns whether a particular permission has been granted
418
     * </p>
419
     *
420
     * @param permission The permission to check for
421
     * @return true if the permission is granted, false otherwise
422
     */
423
    public boolean isPermissionGranted(String permission) {
424
        List<String> grantedPermissions = getPermissions();
425
        if (grantedPermissions != null) {
426
            return grantedPermissions.contains(permission);
427
        }
428
        return false;
429
    }
430
 
431
    /**
432
     * <p>
433
     * Returns the list of permissions that have been requested in this session but not granted
434
     * </p>
435
     *
436
     * @return the list of requested permissions that have been declined
437
     */
438
    public final List<String> getDeclinedPermissions() {
439
        synchronized (this.lock) {
440
            return (this.tokenInfo == null) ? null : this.tokenInfo.getDeclinedPermissions();
441
        }
442
    }
443
 
444
    /**
445
     * <p>
446
     * Logs a user in to Facebook.
447
     * </p>
448
     * <p>
449
     * A session may not be used with {@link Request Request} and other classes
450
     * in the SDK until it is open. If, prior to calling open, the session is in
451
     * the {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED}
452
     * state, and the requested permissions are a subset of the previously authorized
453
     * permissions, then the Session becomes usable immediately with no user interaction.
454
     * </p>
455
     * <p>
456
     * The permissions associated with the openRequest passed to this method must
457
     * be read permissions only (or null/empty). It is not allowed to pass publish
458
     * permissions to this method and will result in an exception being thrown.
459
     * </p>
460
     * <p>
461
     * Any open method must be called at most once, and cannot be called after the
462
     * Session is closed. Calling the method at an invalid time will result in
463
     * UnsuportedOperationException.
464
     * </p>
465
     *
466
     * @param openRequest the open request, can be null only if the Session is in the
467
     *                    {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED} state
468
     * @throws FacebookException if any publish or manage permissions are requested
469
     */
470
    public final void openForRead(OpenRequest openRequest) {
471
        open(openRequest, SessionAuthorizationType.READ);
472
    }
473
 
474
    /**
475
     * <p>
476
     * Logs a user in to Facebook.
477
     * </p>
478
     * <p>
479
     * A session may not be used with {@link Request Request} and other classes
480
     * in the SDK until it is open. If, prior to calling open, the session is in
481
     * the {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED}
482
     * state, and the requested permissions are a subset of the previously authorized
483
     * permissions, then the Session becomes usable immediately with no user interaction.
484
     * </p>
485
     * <p>
486
     * The permissions associated with the openRequest passed to this method must
487
     * be publish or manage permissions only and must be non-empty. Any read permissions
488
     * will result in a warning, and may fail during server-side authorization. Also, an application
489
     * must have at least basic read permissions prior to requesting publish permissions, so
490
     * this method should only be used if the application knows that the user has already granted
491
     * read permissions to the application; otherwise, openForRead should be used, followed by a
492
     * call to requestNewPublishPermissions. For more information on this flow, see
493
     * https://developers.facebook.com/docs/facebook-login/permissions/.
494
     * </p>
495
     * <p>
496
     * Any open method must be called at most once, and cannot be called after the
497
     * Session is closed. Calling the method at an invalid time will result in
498
     * UnsuportedOperationException.
499
     * </p>
500
     *
501
     * @param openRequest the open request, can be null only if the Session is in the
502
     *                    {@link SessionState#CREATED_TOKEN_LOADED CREATED_TOKEN_LOADED} state
503
     * @throws FacebookException if the passed in request is null or has no permissions set.
504
     */
505
    public final void openForPublish(OpenRequest openRequest) {
506
        open(openRequest, SessionAuthorizationType.PUBLISH);
507
    }
508
 
509
    /**
510
     * Opens a session based on an existing Facebook access token. This method should be used
511
     * only in instances where an application has previously obtained an access token and wishes
512
     * to import it into the Session/TokenCachingStrategy-based session-management system. An
513
     * example would be an application which previously did not use the Facebook SDK for Android
514
     * and implemented its own session-management scheme, but wishes to implement an upgrade path
515
     * for existing users so they do not need to log in again when upgrading to a version of
516
     * the app that uses the SDK.
517
     * <p/>
518
     * No validation is done that the token, token source, or permissions are actually valid.
519
     * It is the caller's responsibility to ensure that these accurately reflect the state of
520
     * the token that has been passed in, or calls to the Facebook API may fail.
521
     *
522
     * @param accessToken the access token obtained from Facebook
523
     * @param callback    a callback that will be called when the session status changes; may be null
524
     */
525
    public final void open(AccessToken accessToken, StatusCallback callback) {
526
        synchronized (this.lock) {
527
            if (pendingAuthorizationRequest != null) {
528
                throw new UnsupportedOperationException(
529
                        "Session: an attempt was made to open a session that has a pending request.");
530
            }
531
 
532
            if (state.isClosed()) {
533
                throw new UnsupportedOperationException(
534
                        "Session: an attempt was made to open a previously-closed session.");
535
            } else if (state != SessionState.CREATED && state != SessionState.CREATED_TOKEN_LOADED) {
536
                throw new UnsupportedOperationException(
537
                        "Session: an attempt was made to open an already opened session.");
538
            }
539
 
540
            if (callback != null) {
541
                addCallback(callback);
542
            }
543
 
544
            this.tokenInfo = accessToken;
545
 
546
            if (this.tokenCachingStrategy != null) {
547
                this.tokenCachingStrategy.save(accessToken.toCacheBundle());
548
            }
549
 
550
            final SessionState oldState = state;
551
            state = SessionState.OPENED;
552
            this.postStateChange(oldState, state, null);
553
        }
554
 
555
        autoPublishAsync();
556
    }
557
 
558
    /**
559
     * <p>
560
     * Issues a request to add new read permissions to the Session.
561
     * </p>
562
     * <p>
563
     * If successful, this will update the set of permissions on this session to
564
     * match the newPermissions. If this fails, the Session remains unchanged.
565
     * </p>
566
     * <p>
567
     * The permissions associated with the newPermissionsRequest passed to this method must
568
     * be read permissions only (or null/empty). It is not allowed to pass publish
569
     * permissions to this method and will result in an exception being thrown.
570
     * </p>
571
     *
572
     * @param newPermissionsRequest the new permissions request
573
     */
574
    public final void requestNewReadPermissions(NewPermissionsRequest newPermissionsRequest) {
575
        requestNewPermissions(newPermissionsRequest, SessionAuthorizationType.READ);
576
    }
577
 
578
    /**
579
     * <p>
580
     * Issues a request to add new publish or manage permissions to the Session.
581
     * </p>
582
     * <p>
583
     * If successful, this will update the set of permissions on this session to
584
     * match the newPermissions. If this fails, the Session remains unchanged.
585
     * </p>
586
     * <p>
587
     * The permissions associated with the newPermissionsRequest passed to this method must
588
     * be publish or manage permissions only and must be non-empty. Any read permissions
589
     * will result in a warning, and may fail during server-side authorization.
590
     * </p>
591
     *
592
     * @param newPermissionsRequest the new permissions request
593
     */
594
    public final void requestNewPublishPermissions(NewPermissionsRequest newPermissionsRequest) {
595
        requestNewPermissions(newPermissionsRequest, SessionAuthorizationType.PUBLISH);
596
    }
597
 
598
    /**
599
     * <p>
600
     * Issues a request to refresh the permissions on the session.
601
     * </p>
602
     * <p>
603
     * If successful, this will update the permissions and call the app back with
604
     * {@link SessionState#OPENED_TOKEN_UPDATED}.  The session can then be queried to see the granted and declined
605
     * permissions.  If this fails because the user has removed the app, the session will close.
606
     * </p>
607
     */
608
    public final void refreshPermissions() {
609
        Request request = new Request(this, "me/permissions");
610
        request.setCallback(new Request.Callback() {
611
            @Override
612
            public void onCompleted(Response response) {
613
                PermissionsPair permissionsPair = handlePermissionResponse(response);
614
                if (permissionsPair != null) {
615
                    // Update our token with the refreshed permissions
616
                    synchronized (lock) {
617
                        tokenInfo = AccessToken.createFromTokenWithRefreshedPermissions(tokenInfo,
618
                                permissionsPair.getGrantedPermissions(), permissionsPair.getDeclinedPermissions());
619
                        postStateChange(state, SessionState.OPENED_TOKEN_UPDATED, null);
620
                    }
621
                }
622
            }
623
        });
624
        request.executeAsync();
625
    }
626
 
627
    /**
628
     * Internal helper class that is used to hold two different permission lists (granted and declined)
629
     */
630
    static class PermissionsPair {
631
        List<String> grantedPermissions;
632
        List<String> declinedPermissions;
633
 
634
        public PermissionsPair(List<String> grantedPermissions, List<String> declinedPermissions) {
635
            this.grantedPermissions = grantedPermissions;
636
            this.declinedPermissions = declinedPermissions;
637
        }
638
 
639
        public List<String> getGrantedPermissions() {
640
            return grantedPermissions;
641
        }
642
 
643
        public List<String> getDeclinedPermissions() {
644
            return declinedPermissions;
645
        }
646
    }
647
    /**
648
     * This parses a server response to a call to me/permissions.  It will return the list of granted permissions.
649
     * It will optionally update a session with the requested permissions.  It also handles the distinction between
650
     * 1.0 and 2.0 calls to the endpoint.
651
     *
652
     * @param response The server response
653
     * @return A list of granted permissions or null if an error
654
     */
655
    static PermissionsPair handlePermissionResponse(Response response) {
656
        if (response.getError() != null) {
657
            return null;
658
        }
659
 
660
        GraphMultiResult result = response.getGraphObjectAs(GraphMultiResult.class);
661
        if (result == null) {
662
            return null;
663
        }
664
 
665
        GraphObjectList<GraphObject> data = result.getData();
666
        if (data == null || data.size() == 0) {
667
            return null;
668
        }
669
        List<String> grantedPermissions = new ArrayList<String>(data.size());
670
        List<String> declinedPermissions = new ArrayList<String>(data.size());
671
 
672
        // Check if we are dealing with v2.0 or v1.0 behavior until the server is updated
673
        GraphObject firstObject = data.get(0);
674
        if (firstObject.getProperty("permission") != null) { // v2.0
675
            for (GraphObject graphObject : data) {
676
                String permission = (String) graphObject.getProperty("permission");
677
                if (permission.equals("installed")) {
678
                    continue;
679
                }
680
                String status = (String) graphObject.getProperty("status");
681
                if(status.equals("granted")) {
682
                    grantedPermissions.add(permission);
683
                } else if (status.equals("declined")) {
684
                    declinedPermissions.add(permission);
685
                }
686
            }
687
        } else { // v1.0
688
            Map<String, Object> permissionsMap = firstObject.asMap();
689
            for (Map.Entry<String, Object> entry : permissionsMap.entrySet()) {
690
                if (entry.getKey().equals("installed")) {
691
                    continue;
692
                }
693
                if ((Integer)entry.getValue() == 1) {
694
                    grantedPermissions.add(entry.getKey());
695
                }
696
            }
697
        }
698
 
699
        return new PermissionsPair(grantedPermissions, declinedPermissions);
700
    }
701
 
702
    /**
703
     * Provides an implementation for {@link Activity#onActivityResult
704
     * onActivityResult} that updates the Session based on information returned
705
     * during the authorization flow. The Activity that calls open or
706
     * requestNewPermissions should forward the resulting onActivityResult call here to
707
     * update the Session state based on the contents of the resultCode and
708
     * data.
709
     *
710
     * @param currentActivity The Activity that is forwarding the onActivityResult call.
711
     * @param requestCode     The requestCode parameter from the forwarded call. When this
712
     *                        onActivityResult occurs as part of Facebook authorization
713
     *                        flow, this value is the activityCode passed to open or
714
     *                        authorize.
715
     * @param resultCode      An int containing the resultCode parameter from the forwarded
716
     *                        call.
717
     * @param data            The Intent passed as the data parameter from the forwarded
718
     *                        call.
719
     * @return A boolean indicating whether the requestCode matched a pending
720
     *         authorization request for this Session.
721
     */
722
    public final boolean onActivityResult(Activity currentActivity, int requestCode, int resultCode, Intent data) {
723
        Validate.notNull(currentActivity, "currentActivity");
724
 
725
        initializeStaticContext(currentActivity);
726
 
727
        synchronized (lock) {
728
            if (pendingAuthorizationRequest == null || (requestCode != pendingAuthorizationRequest.getRequestCode())) {
729
                return false;
730
            }
731
        }
732
 
733
        Exception exception = null;
734
        AuthorizationClient.Result.Code code = AuthorizationClient.Result.Code.ERROR;
735
 
736
        if (data != null) {
737
            AuthorizationClient.Result result = (AuthorizationClient.Result) data.getSerializableExtra(
738
                    LoginActivity.RESULT_KEY);
739
            if (result != null) {
740
                // This came from LoginActivity.
741
                handleAuthorizationResult(resultCode, result);
742
                return true;
743
            } else if (authorizationClient != null) {
744
                // Delegate to the auth client.
745
                authorizationClient.onActivityResult(requestCode, resultCode, data);
746
                return true;
747
            }
748
        } else if (resultCode == Activity.RESULT_CANCELED) {
749
            exception = new FacebookOperationCanceledException("User canceled operation.");
750
            code = AuthorizationClient.Result.Code.CANCEL;
751
        }
752
 
753
        if (exception == null) {
754
            exception = new FacebookException("Unexpected call to Session.onActivityResult");
755
        }
756
 
757
        logAuthorizationComplete(code, null, exception);
758
        finishAuthOrReauth(null, exception);
759
 
760
        return true;
761
    }
762
 
763
    /**
764
     * Closes the local in-memory Session object, but does not clear the
765
     * persisted token cache.
766
     */
767
    public final void close() {
768
        synchronized (this.lock) {
769
            final SessionState oldState = this.state;
770
 
771
            switch (this.state) {
772
                case CREATED:
773
                case OPENING:
774
                    this.state = SessionState.CLOSED_LOGIN_FAILED;
775
                    postStateChange(oldState, this.state, new FacebookException(
776
                            "Log in attempt aborted."));
777
                    break;
778
 
779
                case CREATED_TOKEN_LOADED:
780
                case OPENED:
781
                case OPENED_TOKEN_UPDATED:
782
                    this.state = SessionState.CLOSED;
783
                    postStateChange(oldState, this.state, null);
784
                    break;
785
 
786
                case CLOSED:
787
                case CLOSED_LOGIN_FAILED:
788
                    break;
789
            }
790
        }
791
    }
792
 
793
    /**
794
     * Closes the local in-memory Session object and clears any persisted token
795
     * cache related to the Session.
796
     */
797
    public final void closeAndClearTokenInformation() {
798
        if (this.tokenCachingStrategy != null) {
799
            this.tokenCachingStrategy.clear();
800
        }
801
        Utility.clearFacebookCookies(staticContext);
802
        Utility.clearCaches(staticContext);
803
        close();
804
    }
805
 
806
    /**
807
     * Adds a callback that will be called when the state of this Session changes.
808
     *
809
     * @param callback the callback
810
     */
811
    public final void addCallback(StatusCallback callback) {
812
        synchronized (callbacks) {
813
            if (callback != null && !callbacks.contains(callback)) {
814
                callbacks.add(callback);
815
            }
816
        }
817
    }
818
 
819
    /**
820
     * Removes a StatusCallback from this Session.
821
     *
822
     * @param callback the callback
823
     */
824
    public final void removeCallback(StatusCallback callback) {
825
        synchronized (callbacks) {
826
            callbacks.remove(callback);
827
        }
828
    }
829
 
830
    @Override
831
    public String toString() {
832
        return new StringBuilder().append("{Session").append(" state:").append(this.state).append(", token:")
833
                .append((this.tokenInfo == null) ? "null" : this.tokenInfo).append(", appId:")
834
                .append((this.applicationId == null) ? "null" : this.applicationId).append("}").toString();
835
    }
836
 
837
    void extendTokenCompleted(Bundle bundle) {
838
        synchronized (this.lock) {
839
            final SessionState oldState = this.state;
840
 
841
            switch (this.state) {
842
                case OPENED:
843
                    this.state = SessionState.OPENED_TOKEN_UPDATED;
844
                    postStateChange(oldState, this.state, null);
845
                    break;
846
                case OPENED_TOKEN_UPDATED:
847
                    break;
848
                default:
849
                    // Silently ignore attempts to refresh token if we are not open
850
                    Log.d(TAG, "refreshToken ignored in state " + this.state);
851
                    return;
852
            }
853
            this.tokenInfo = AccessToken.createFromRefresh(this.tokenInfo, bundle);
854
            if (this.tokenCachingStrategy != null) {
855
                this.tokenCachingStrategy.save(this.tokenInfo.toCacheBundle());
856
            }
857
        }
858
    }
859
 
860
    private Object writeReplace() {
861
        return new SerializationProxyV1(applicationId, state, tokenInfo,
862
                lastAttemptedTokenExtendDate, false, pendingAuthorizationRequest);
863
    }
864
 
865
    // have a readObject that throws to prevent spoofing
866
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
867
        throw new InvalidObjectException("Cannot readObject, serialization proxy required");
868
    }
869
 
870
    /**
871
     * Save the Session object into the supplied Bundle. This method is intended to be called from an
872
     * Activity or Fragment's onSaveInstanceState method in order to preserve Sessions across Activity lifecycle events.
873
     *
874
     * @param session the Session to save
875
     * @param bundle  the Bundle to save the Session to
876
     */
877
    public static final void saveSession(Session session, Bundle bundle) {
878
        if (bundle != null && session != null && !bundle.containsKey(SESSION_BUNDLE_SAVE_KEY)) {
879
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
880
            try {
881
                new ObjectOutputStream(outputStream).writeObject(session);
882
            } catch (IOException e) {
883
                throw new FacebookException("Unable to save session.", e);
884
            }
885
            bundle.putByteArray(SESSION_BUNDLE_SAVE_KEY, outputStream.toByteArray());
886
            bundle.putBundle(AUTH_BUNDLE_SAVE_KEY, session.authorizationBundle);
887
        }
888
    }
889
 
890
    /**
891
     * Restores the saved session from a Bundle, if any. Returns the restored Session or
892
     * null if it could not be restored. This method is intended to be called from an Activity or Fragment's
893
     * onCreate method when a Session has previously been saved into a Bundle via saveState to preserve a Session
894
     * across Activity lifecycle events.
895
     *
896
     * @param context         the Activity or Service creating the Session, must not be null
897
     * @param cachingStrategy the TokenCachingStrategy to use to load and store the token. If this is
898
     *                        null, a default token cachingStrategy that stores data in
899
     *                        SharedPreferences will be used
900
     * @param callback        the callback to notify for Session state changes, can be null
901
     * @param bundle          the bundle to restore the Session from
902
     * @return the restored Session, or null
903
     */
904
    public static final Session restoreSession(
905
            Context context, TokenCachingStrategy cachingStrategy, StatusCallback callback, Bundle bundle) {
906
        if (bundle == null) {
907
            return null;
908
        }
909
        byte[] data = bundle.getByteArray(SESSION_BUNDLE_SAVE_KEY);
910
        if (data != null) {
911
            ByteArrayInputStream is = new ByteArrayInputStream(data);
912
            try {
913
                Session session = (Session) (new ObjectInputStream(is)).readObject();
914
                initializeStaticContext(context);
915
                if (cachingStrategy != null) {
916
                    session.tokenCachingStrategy = cachingStrategy;
917
                } else {
918
                    session.tokenCachingStrategy = new SharedPreferencesTokenCachingStrategy(context);
919
                }
920
                if (callback != null) {
921
                    session.addCallback(callback);
922
                }
923
                session.authorizationBundle = bundle.getBundle(AUTH_BUNDLE_SAVE_KEY);
924
                return session;
925
            } catch (ClassNotFoundException e) {
926
                Log.w(TAG, "Unable to restore session", e);
927
            } catch (IOException e) {
928
                Log.w(TAG, "Unable to restore session.", e);
929
            }
930
        }
931
        return null;
932
    }
933
 
934
 
935
    /**
936
     * Returns the current active Session, or null if there is none.
937
     *
938
     * @return the current active Session, or null if there is none.
939
     */
940
    public static final Session getActiveSession() {
941
        synchronized (Session.STATIC_LOCK) {
942
            return Session.activeSession;
943
        }
944
    }
945
 
946
    /**
947
     * <p>
948
     * Sets the current active Session.
949
     * </p>
950
     * <p>
951
     * The active Session is used implicitly by predefined Request factory
952
     * methods as well as optionally by UI controls in the sdk.
953
     * </p>
954
     * <p>
955
     * It is legal to set this to null, or to a Session that is not yet open.
956
     * </p>
957
     *
958
     * @param session A Session to use as the active Session, or null to indicate
959
     *                that there is no active Session.
960
     */
961
    public static final void setActiveSession(Session session) {
962
        synchronized (Session.STATIC_LOCK) {
963
            if (session != Session.activeSession) {
964
                Session oldSession = Session.activeSession;
965
 
966
                if (oldSession != null) {
967
                    oldSession.close();
968
                }
969
 
970
                Session.activeSession = session;
971
 
972
                if (oldSession != null) {
973
                    postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_UNSET);
974
                }
975
 
976
                if (session != null) {
977
                    postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_SET);
978
 
979
                    if (session.isOpened()) {
980
                        postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);
981
                    }
982
                }
983
            }
984
        }
985
    }
986
 
987
    /**
988
     * If a cached token is available, creates and opens the session and makes it active without any user interaction,
989
     * otherwise this does nothing.
990
     *
991
     * @param context The Context creating this session
992
     * @return The new session or null if one could not be created
993
     */
994
    public static Session openActiveSessionFromCache(Context context) {
995
        return openActiveSession(context, false, null);
996
    }
997
 
998
    /**
999
     * If allowLoginUI is true, this will create a new Session, make it active, and
1000
     * open it. If the default token cache is not available, then this will request
1001
     * basic permissions. If the default token cache is available and cached tokens
1002
     * are loaded, this will use the cached token and associated permissions.
1003
     * <p/>
1004
     * If allowedLoginUI is false, this will only create the active session and open
1005
     * it if it requires no user interaction (i.e. the token cache is available and
1006
     * there are cached tokens).
1007
     *
1008
     * @param activity     The Activity that is opening the new Session.
1009
     * @param allowLoginUI if false, only sets the active session and opens it if it
1010
     *                     does not require user interaction
1011
     * @param callback     The {@link StatusCallback SessionStatusCallback} to
1012
     *                     notify regarding Session state changes. May be null.
1013
     * @return The new Session or null if one could not be created
1014
     */
1015
    public static Session openActiveSession(Activity activity, boolean allowLoginUI,
1016
            StatusCallback callback) {
1017
        return openActiveSession(activity, allowLoginUI, new OpenRequest(activity).setCallback(callback));
1018
    }
1019
 
1020
    /**
1021
     * If allowLoginUI is true, this will create a new Session, make it active, and
1022
     * open it. If the default token cache is not available, then this will request
1023
     * the permissions provided (and basic permissions of no permissions are provided).
1024
     * If the default token cache is available and cached tokens are loaded, this will
1025
     * use the cached token and associated permissions.
1026
     * <p/>
1027
     * If allowedLoginUI is false, this will only create the active session and open
1028
     * it if it requires no user interaction (i.e. the token cache is available and
1029
     * there are cached tokens).
1030
     *
1031
     * @param activity     The Activity that is opening the new Session.
1032
     * @param allowLoginUI if false, only sets the active session and opens it if it
1033
     *                     does not require user interaction
1034
     * @param permissions  The permissions to request for this Session
1035
     * @param callback     The {@link StatusCallback SessionStatusCallback} to
1036
     *                     notify regarding Session state changes. May be null.
1037
     * @return The new Session or null if one could not be created
1038
     */
1039
    public static Session openActiveSession(Activity activity, boolean allowLoginUI,
1040
            List<String> permissions, StatusCallback callback) {
1041
        return openActiveSession(
1042
                activity, 
1043
                allowLoginUI, 
1044
                new OpenRequest(activity).setCallback(callback).setPermissions(permissions));
1045
    }
1046
 
1047
    /**
1048
     * If allowLoginUI is true, this will create a new Session, make it active, and
1049
     * open it. If the default token cache is not available, then this will request
1050
     * basic permissions. If the default token cache is available and cached tokens
1051
     * are loaded, this will use the cached token and associated permissions.
1052
     * <p/>
1053
     * If allowedLoginUI is false, this will only create the active session and open
1054
     * it if it requires no user interaction (i.e. the token cache is available and
1055
     * there are cached tokens).
1056
     *
1057
     * @param context      The Activity or Service creating this Session
1058
     * @param fragment     The Fragment that is opening the new Session.
1059
     * @param allowLoginUI if false, only sets the active session and opens it if it
1060
     *                     does not require user interaction
1061
     * @param callback     The {@link StatusCallback SessionStatusCallback} to
1062
     *                     notify regarding Session state changes.
1063
     * @return The new Session or null if one could not be created
1064
     */
1065
    public static Session openActiveSession(Context context, Fragment fragment,
1066
            boolean allowLoginUI, StatusCallback callback) {
1067
        return openActiveSession(context, allowLoginUI, new OpenRequest(fragment).setCallback(callback));
1068
    }
1069
 
1070
    /**
1071
     * If allowLoginUI is true, this will create a new Session, make it active, and
1072
     * open it. If the default token cache is not available, then this will request
1073
     * the permissions provided (and basic permissions of no permissions are provided).
1074
     * If the default token cache is available and cached tokens are loaded, this will
1075
     * use the cached token and associated permissions.
1076
     * <p/>
1077
     * If allowedLoginUI is false, this will only create the active session and open
1078
     * it if it requires no user interaction (i.e. the token cache is available and
1079
     * there are cached tokens).
1080
     *
1081
     * @param context      The Activity or Service creating this Session
1082
     * @param fragment     The Fragment that is opening the new Session.
1083
     * @param allowLoginUI if false, only sets the active session and opens it if it
1084
     *                     does not require user interaction
1085
     * @param permissions  The permissions to request for this Session
1086
     * @param callback     The {@link StatusCallback SessionStatusCallback} to
1087
     *                     notify regarding Session state changes.
1088
     * @return The new Session or null if one could not be created
1089
     */
1090
    public static Session openActiveSession(Context context, Fragment fragment,
1091
            boolean allowLoginUI, List<String> permissions, StatusCallback callback) {
1092
        return openActiveSession(
1093
                context, 
1094
                allowLoginUI, 
1095
                new OpenRequest(fragment).setCallback(callback).setPermissions(permissions));
1096
    }
1097
 
1098
    /**
1099
     * Opens a session based on an existing Facebook access token, and also makes this session
1100
     * the currently active session. This method should be used
1101
     * only in instances where an application has previously obtained an access token and wishes
1102
     * to import it into the Session/TokenCachingStrategy-based session-management system. A primary
1103
     * example would be an application which previously did not use the Facebook SDK for Android
1104
     * and implemented its own session-management scheme, but wishes to implement an upgrade path
1105
     * for existing users so they do not need to log in again when upgrading to a version of
1106
     * the app that uses the SDK. In general, this method will be called only once, when the app
1107
     * detects that it has been upgraded -- after that, the usual Session lifecycle methods
1108
     * should be used to manage the session and its associated token.
1109
     * <p/>
1110
     * No validation is done that the token, token source, or permissions are actually valid.
1111
     * It is the caller's responsibility to ensure that these accurately reflect the state of
1112
     * the token that has been passed in, or calls to the Facebook API may fail.
1113
     *
1114
     * @param context     the Context to use for creation the session
1115
     * @param accessToken the access token obtained from Facebook
1116
     * @param callback    a callback that will be called when the session status changes; may be null
1117
     * @return The new Session or null if one could not be created
1118
     */
1119
    public static Session openActiveSessionWithAccessToken(Context context, AccessToken accessToken,
1120
            StatusCallback callback) {
1121
        Session session = new Session(context, null, null, false);
1122
 
1123
        setActiveSession(session);
1124
        session.open(accessToken, callback);
1125
 
1126
        return session;
1127
    }
1128
 
1129
    private static Session openActiveSession(Context context, boolean allowLoginUI, OpenRequest openRequest) {
1130
        Session session = new Builder(context).build();
1131
        if (SessionState.CREATED_TOKEN_LOADED.equals(session.getState()) || allowLoginUI) {
1132
            setActiveSession(session);
1133
            session.openForRead(openRequest);
1134
            return session;
1135
        }
1136
        return null;
1137
    }
1138
 
1139
    static Context getStaticContext() {
1140
        return staticContext;
1141
    }
1142
 
1143
    static void initializeStaticContext(Context currentContext) {
1144
        if ((currentContext != null) && (staticContext == null)) {
1145
            Context applicationContext = currentContext.getApplicationContext();
1146
            staticContext = (applicationContext != null) ? applicationContext : currentContext;
1147
        }
1148
    }
1149
 
1150
    void authorize(AuthorizationRequest request) {
1151
        boolean started = false;
1152
 
1153
        request.setApplicationId(applicationId);
1154
 
1155
        autoPublishAsync();
1156
 
1157
        logAuthorizationStart();
1158
 
1159
        started = tryLoginActivity(request);
1160
 
1161
        pendingAuthorizationRequest.loggingExtras.put(AuthorizationClient.EVENT_EXTRAS_TRY_LOGIN_ACTIVITY,
1162
                started ? AppEventsConstants.EVENT_PARAM_VALUE_YES : AppEventsConstants.EVENT_PARAM_VALUE_NO);
1163
 
1164
        if (!started && request.isLegacy) {
1165
            pendingAuthorizationRequest.loggingExtras.put(AuthorizationClient.EVENT_EXTRAS_TRY_LEGACY,
1166
                    AppEventsConstants.EVENT_PARAM_VALUE_YES);
1167
 
1168
            tryLegacyAuth(request);
1169
            started = true;
1170
        }
1171
 
1172
        if (!started) {
1173
            synchronized (this.lock) {
1174
                final SessionState oldState = this.state;
1175
 
1176
                switch (this.state) {
1177
                    case CLOSED:
1178
                    case CLOSED_LOGIN_FAILED:
1179
                        return;
1180
 
1181
                    default:
1182
                        this.state = SessionState.CLOSED_LOGIN_FAILED;
1183
 
1184
                        Exception exception = new FacebookException(
1185
                                "Log in attempt failed: LoginActivity could not be started, and not legacy request");
1186
                        logAuthorizationComplete(AuthorizationClient.Result.Code.ERROR, null, exception);
1187
                        postStateChange(oldState, this.state, exception);
1188
                }
1189
            }
1190
        }
1191
    }
1192
 
1193
    private void open(OpenRequest openRequest, SessionAuthorizationType authType) {
1194
        validatePermissions(openRequest, authType);
1195
        validateLoginBehavior(openRequest);
1196
 
1197
        SessionState newState;
1198
        synchronized (this.lock) {
1199
            if (pendingAuthorizationRequest != null) {
1200
                postStateChange(state, state, new UnsupportedOperationException(
1201
                        "Session: an attempt was made to open a session that has a pending request."));
1202
                return;
1203
            }
1204
            final SessionState oldState = this.state;
1205
 
1206
            switch (this.state) {
1207
                case CREATED:
1208
                    this.state = newState = SessionState.OPENING;
1209
                    if (openRequest == null) {
1210
                        throw new IllegalArgumentException("openRequest cannot be null when opening a new Session");
1211
                    }
1212
                    pendingAuthorizationRequest = openRequest;
1213
                    break;
1214
                case CREATED_TOKEN_LOADED:
1215
                    if (openRequest != null && !Utility.isNullOrEmpty(openRequest.getPermissions())) {
1216
                        if (!Utility.isSubset(openRequest.getPermissions(), getPermissions())) {
1217
                            pendingAuthorizationRequest = openRequest;
1218
                        }
1219
                    }
1220
                    if (pendingAuthorizationRequest == null) {
1221
                        this.state = newState = SessionState.OPENED;
1222
                    } else {
1223
                        this.state = newState = SessionState.OPENING;
1224
                    }
1225
                    break;
1226
                default:
1227
                    throw new UnsupportedOperationException(
1228
                            "Session: an attempt was made to open an already opened session.");
1229
            }
1230
            if (openRequest != null) {
1231
                addCallback(openRequest.getCallback());
1232
            }
1233
            this.postStateChange(oldState, newState, null);
1234
        }
1235
 
1236
        if (newState == SessionState.OPENING) {
1237
            authorize(openRequest);
1238
        }
1239
    }
1240
 
1241
    private void requestNewPermissions(NewPermissionsRequest newPermissionsRequest, SessionAuthorizationType authType) {
1242
        validatePermissions(newPermissionsRequest, authType);
1243
        validateLoginBehavior(newPermissionsRequest);
1244
 
1245
        if (newPermissionsRequest != null) {
1246
            synchronized (this.lock) {
1247
                if (pendingAuthorizationRequest != null) {
1248
                    throw new UnsupportedOperationException(
1249
                            "Session: an attempt was made to request new permissions for a session that has a pending request.");
1250
                }
1251
                if (state.isOpened()) {
1252
                    pendingAuthorizationRequest = newPermissionsRequest;
1253
                } else if (state.isClosed()) {
1254
                    throw new UnsupportedOperationException(
1255
                            "Session: an attempt was made to request new permissions for a session that has been closed.");
1256
                } else {
1257
                    throw new UnsupportedOperationException(
1258
                            "Session: an attempt was made to request new permissions for a session that is not currently open.");
1259
                }
1260
            }
1261
 
1262
            newPermissionsRequest.setValidateSameFbidAsToken(getAccessToken());
1263
            addCallback(newPermissionsRequest.getCallback());
1264
            authorize(newPermissionsRequest);
1265
        }
1266
    }
1267
 
1268
    private void validateLoginBehavior(AuthorizationRequest request) {
1269
        if (request != null && !request.isLegacy) {
1270
            Intent intent = new Intent();
1271
            intent.setClass(getStaticContext(), LoginActivity.class);
1272
            if (!resolveIntent(intent)) {
1273
                throw new FacebookException(String.format(
1274
                        "Cannot use SessionLoginBehavior %s when %s is not declared as an activity in AndroidManifest.xml",
1275
                        request.getLoginBehavior(), LoginActivity.class.getName()));
1276
            }
1277
        }
1278
    }
1279
 
1280
    private void validatePermissions(AuthorizationRequest request, SessionAuthorizationType authType) {
1281
        if (request == null || Utility.isNullOrEmpty(request.getPermissions())) {
1282
            if (SessionAuthorizationType.PUBLISH.equals(authType)) {
1283
                throw new FacebookException("Cannot request publish or manage authorization with no permissions.");
1284
            }
1285
            return; // nothing to check
1286
        }
1287
        for (String permission : request.getPermissions()) {
1288
            if (isPublishPermission(permission)) {
1289
                if (SessionAuthorizationType.READ.equals(authType)) {
1290
                    throw new FacebookException(
1291
                            String.format(
1292
                                    "Cannot pass a publish or manage permission (%s) to a request for read authorization",
1293
                                    permission));
1294
                }
1295
            } else {
1296
                if (SessionAuthorizationType.PUBLISH.equals(authType)) {
1297
                    Log.w(TAG,
1298
                            String.format(
1299
                                    "Should not pass a read permission (%s) to a request for publish or manage authorization",
1300
                                    permission));
1301
                }
1302
            }
1303
        }
1304
    }
1305
 
1306
    public static boolean isPublishPermission(String permission) {
1307
        return permission != null &&
1308
                (permission.startsWith(PUBLISH_PERMISSION_PREFIX) ||
1309
                        permission.startsWith(MANAGE_PERMISSION_PREFIX) ||
1310
                        OTHER_PUBLISH_PERMISSIONS.contains(permission));
1311
 
1312
    }
1313
 
1314
    private void handleAuthorizationResult(int resultCode, AuthorizationClient.Result result) {
1315
        AccessToken newToken = null;
1316
        Exception exception = null;
1317
        if (resultCode == Activity.RESULT_OK) {
1318
            if (result.code == AuthorizationClient.Result.Code.SUCCESS) {
1319
                newToken = result.token;
1320
            } else {
1321
                exception = new FacebookAuthorizationException(result.errorMessage);
1322
            }
1323
        } else if (resultCode == Activity.RESULT_CANCELED) {
1324
            exception = new FacebookOperationCanceledException(result.errorMessage);
1325
        }
1326
 
1327
        logAuthorizationComplete(result.code, result.loggingExtras, exception);
1328
 
1329
        authorizationClient = null;
1330
        finishAuthOrReauth(newToken, exception);
1331
    }
1332
 
1333
    private void logAuthorizationStart() {
1334
        Bundle bundle = AuthorizationClient.newAuthorizationLoggingBundle(pendingAuthorizationRequest.getAuthId());
1335
        bundle.putLong(AuthorizationClient.EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());
1336
 
1337
        // Log what we already know about the call in start event
1338
        try {
1339
            JSONObject extras = new JSONObject();
1340
            extras.put(AuthorizationClient.EVENT_EXTRAS_LOGIN_BEHAVIOR,
1341
                    pendingAuthorizationRequest.loginBehavior.toString());
1342
            extras.put(AuthorizationClient.EVENT_EXTRAS_REQUEST_CODE, pendingAuthorizationRequest.requestCode);
1343
            extras.put(AuthorizationClient.EVENT_EXTRAS_IS_LEGACY, pendingAuthorizationRequest.isLegacy);
1344
            extras.put(AuthorizationClient.EVENT_EXTRAS_PERMISSIONS,
1345
                    TextUtils.join(",", pendingAuthorizationRequest.permissions));
1346
            extras.put(AuthorizationClient.EVENT_EXTRAS_DEFAULT_AUDIENCE,
1347
                    pendingAuthorizationRequest.defaultAudience.toString());
1348
            bundle.putString(AuthorizationClient.EVENT_PARAM_EXTRAS, extras.toString());
1349
        } catch (JSONException e) {
1350
        }
1351
 
1352
        AppEventsLogger logger = getAppEventsLogger();
1353
        logger.logSdkEvent(AuthorizationClient.EVENT_NAME_LOGIN_START, null, bundle);
1354
    }
1355
 
1356
    private void logAuthorizationComplete(AuthorizationClient.Result.Code result, Map<String, String> resultExtras,
1357
            Exception exception) {
1358
        Bundle bundle = null;
1359
        if (pendingAuthorizationRequest == null) {
1360
            // We don't expect this to happen, but if it does, log an event for diagnostic purposes.
1361
            bundle = AuthorizationClient.newAuthorizationLoggingBundle("");
1362
            bundle.putString(AuthorizationClient.EVENT_PARAM_LOGIN_RESULT,
1363
                    AuthorizationClient.Result.Code.ERROR.getLoggingValue());
1364
            bundle.putString(AuthorizationClient.EVENT_PARAM_ERROR_MESSAGE,
1365
                    "Unexpected call to logAuthorizationComplete with null pendingAuthorizationRequest.");
1366
        } else {
1367
            bundle = AuthorizationClient.newAuthorizationLoggingBundle(pendingAuthorizationRequest.getAuthId());
1368
            if (result != null) {
1369
                bundle.putString(AuthorizationClient.EVENT_PARAM_LOGIN_RESULT, result.getLoggingValue());
1370
            }
1371
            if (exception != null && exception.getMessage() != null) {
1372
                bundle.putString(AuthorizationClient.EVENT_PARAM_ERROR_MESSAGE, exception.getMessage());
1373
            }
1374
 
1375
            // Combine extras from the request and from the result.
1376
            JSONObject jsonObject = null;
1377
            if (pendingAuthorizationRequest.loggingExtras.isEmpty() == false) {
1378
                jsonObject = new JSONObject(pendingAuthorizationRequest.loggingExtras);
1379
            }
1380
            if (resultExtras != null) {
1381
                if (jsonObject == null) {
1382
                    jsonObject = new JSONObject();
1383
                }
1384
                try {
1385
                    for (Map.Entry<String, String> entry : resultExtras.entrySet()) {
1386
                        jsonObject.put(entry.getKey(), entry.getValue());
1387
                    }
1388
                } catch (JSONException e) {
1389
                }
1390
            }
1391
            if (jsonObject != null) {
1392
                bundle.putString(AuthorizationClient.EVENT_PARAM_EXTRAS, jsonObject.toString());
1393
            }
1394
        }
1395
        bundle.putLong(AuthorizationClient.EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());
1396
 
1397
        AppEventsLogger logger = getAppEventsLogger();
1398
        logger.logSdkEvent(AuthorizationClient.EVENT_NAME_LOGIN_COMPLETE, null, bundle);
1399
    }
1400
 
1401
    private boolean tryLoginActivity(AuthorizationRequest request) {
1402
        Intent intent = getLoginActivityIntent(request);
1403
 
1404
        if (!resolveIntent(intent)) {
1405
            return false;
1406
        }
1407
 
1408
        try {
1409
            request.getStartActivityDelegate().startActivityForResult(intent, request.getRequestCode());
1410
        } catch (ActivityNotFoundException e) {
1411
            return false;
1412
        }
1413
 
1414
        return true;
1415
    }
1416
 
1417
    private boolean resolveIntent(Intent intent) {
1418
        ResolveInfo resolveInfo = getStaticContext().getPackageManager().resolveActivity(intent, 0);
1419
        if (resolveInfo == null) {
1420
            return false;
1421
        }
1422
        return true;
1423
    }
1424
 
1425
    private Intent getLoginActivityIntent(AuthorizationRequest request) {
1426
        Intent intent = new Intent();
1427
        intent.setClass(getStaticContext(), LoginActivity.class);
1428
        intent.setAction(request.getLoginBehavior().toString());
1429
 
1430
        // Let LoginActivity populate extras appropriately
1431
        AuthorizationClient.AuthorizationRequest authClientRequest = request.getAuthorizationClientRequest();
1432
        Bundle extras = LoginActivity.populateIntentExtras(authClientRequest);
1433
        intent.putExtras(extras);
1434
 
1435
        return intent;
1436
    }
1437
 
1438
    private void tryLegacyAuth(final AuthorizationRequest request) {
1439
        authorizationClient = new AuthorizationClient();
1440
        authorizationClient.setOnCompletedListener(new AuthorizationClient.OnCompletedListener() {
1441
            @Override
1442
            public void onCompleted(AuthorizationClient.Result result) {
1443
                int activityResult;
1444
                if (result.code == AuthorizationClient.Result.Code.CANCEL) {
1445
                    activityResult = Activity.RESULT_CANCELED;
1446
                } else {
1447
                    activityResult = Activity.RESULT_OK;
1448
                }
1449
                handleAuthorizationResult(activityResult, result);
1450
            }
1451
        });
1452
        authorizationClient.setContext(getStaticContext());
1453
        authorizationClient.startOrContinueAuth(request.getAuthorizationClientRequest());
1454
    }
1455
 
1456
    void finishAuthOrReauth(AccessToken newToken, Exception exception) {
1457
        // If the token we came up with is expired/invalid, then auth failed.
1458
        if ((newToken != null) && newToken.isInvalid()) {
1459
            newToken = null;
1460
            exception = new FacebookException("Invalid access token.");
1461
        }
1462
 
1463
 
1464
        synchronized (this.lock) {
1465
            switch (this.state) {
1466
                case OPENING:
1467
                    // This means we are authorizing for the first time in this Session.
1468
                    finishAuthorization(newToken, exception);
1469
                    break;
1470
 
1471
                case OPENED:
1472
                case OPENED_TOKEN_UPDATED:
1473
                    // This means we are reauthorizing.
1474
                    finishReauthorization(newToken, exception);
1475
                    break;
1476
 
1477
                case CREATED:
1478
                case CREATED_TOKEN_LOADED:
1479
                case CLOSED:
1480
                case CLOSED_LOGIN_FAILED:
1481
                    Log.d(TAG, "Unexpected call to finishAuthOrReauth in state " + this.state);
1482
                    break;
1483
            }
1484
        }
1485
    }
1486
 
1487
    private void finishAuthorization(AccessToken newToken, Exception exception) {
1488
        final SessionState oldState = state;
1489
        if (newToken != null) {
1490
            tokenInfo = newToken;
1491
            saveTokenToCache(newToken);
1492
 
1493
            state = SessionState.OPENED;
1494
        } else if (exception != null) {
1495
            state = SessionState.CLOSED_LOGIN_FAILED;
1496
        }
1497
        pendingAuthorizationRequest = null;
1498
        postStateChange(oldState, state, exception);
1499
    }
1500
 
1501
    private void finishReauthorization(final AccessToken newToken, Exception exception) {
1502
        final SessionState oldState = state;
1503
 
1504
        if (newToken != null) {
1505
            tokenInfo = newToken;
1506
            saveTokenToCache(newToken);
1507
 
1508
            state = SessionState.OPENED_TOKEN_UPDATED;
1509
        }
1510
 
1511
        pendingAuthorizationRequest = null;
1512
        postStateChange(oldState, state, exception);
1513
    }
1514
 
1515
    private void saveTokenToCache(AccessToken newToken) {
1516
        if (newToken != null && tokenCachingStrategy != null) {
1517
            tokenCachingStrategy.save(newToken.toCacheBundle());
1518
        }
1519
    }
1520
 
1521
    void postStateChange(final SessionState oldState, final SessionState newState, final Exception exception) {
1522
        // When we request new permissions, we stay in SessionState.OPENED_TOKEN_UPDATED,
1523
        // but we still want notifications of the state change since permissions are
1524
        // different now.
1525
        if ((oldState == newState) &&
1526
                (oldState != SessionState.OPENED_TOKEN_UPDATED) &&
1527
                (exception == null)) {
1528
            return;
1529
        }
1530
 
1531
        if (newState.isClosed()) {
1532
            this.tokenInfo = AccessToken.createEmptyToken();
1533
        }
1534
 
1535
        // Need to schedule the callbacks inside the same queue to preserve ordering.
1536
        // Otherwise these callbacks could have been added to the queue before the SessionTracker
1537
        // gets the ACTIVE_SESSION_SET action.
1538
        Runnable runCallbacks = new Runnable() {
1539
            public void run() {
1540
                synchronized (callbacks) {
1541
                    for (final StatusCallback callback : callbacks) {
1542
                        Runnable closure = new Runnable() {
1543
                            public void run() {
1544
                                // This can be called inside a synchronized block.
1545
                                callback.call(Session.this, newState, exception);
1546
                            }
1547
                        };
1548
 
1549
                        runWithHandlerOrExecutor(handler, closure);
1550
                    }
1551
                }
1552
            }
1553
        };
1554
        runWithHandlerOrExecutor(handler, runCallbacks);
1555
 
1556
        if (this == Session.activeSession) {
1557
            if (oldState.isOpened() != newState.isOpened()) {
1558
                if (newState.isOpened()) {
1559
                    postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_OPENED);
1560
                } else {
1561
                    postActiveSessionAction(Session.ACTION_ACTIVE_SESSION_CLOSED);
1562
                }
1563
            }
1564
        }
1565
    }
1566
 
1567
    static void postActiveSessionAction(String action) {
1568
        final Intent intent = new Intent(action);
1569
 
1570
        LocalBroadcastManager.getInstance(getStaticContext()).sendBroadcast(intent);
1571
    }
1572
 
1573
    private static void runWithHandlerOrExecutor(Handler handler, Runnable runnable) {
1574
        if (handler != null) {
1575
            handler.post(runnable);
1576
        } else {
1577
            Settings.getExecutor().execute(runnable);
1578
        }
1579
    }
1580
 
1581
    void extendAccessTokenIfNeeded() {
1582
        if (shouldExtendAccessToken()) {
1583
            extendAccessToken();
1584
        }
1585
    }
1586
 
1587
    void extendAccessToken() {
1588
        TokenRefreshRequest newTokenRefreshRequest = null;
1589
        synchronized (this.lock) {
1590
            if (currentTokenRefreshRequest == null) {
1591
                newTokenRefreshRequest = new TokenRefreshRequest();
1592
                currentTokenRefreshRequest = newTokenRefreshRequest;
1593
            }
1594
        }
1595
 
1596
        if (newTokenRefreshRequest != null) {
1597
            newTokenRefreshRequest.bind();
1598
        }
1599
    }
1600
 
1601
    boolean shouldExtendAccessToken() {
1602
        if (currentTokenRefreshRequest != null) {
1603
            return false;
1604
        }
1605
 
1606
        boolean result = false;
1607
 
1608
        Date now = new Date();
1609
 
1610
        if (state.isOpened() && tokenInfo.getSource().canExtendToken()
1611
                && now.getTime() - lastAttemptedTokenExtendDate.getTime() > TOKEN_EXTEND_RETRY_SECONDS * 1000
1612
                && now.getTime() - tokenInfo.getLastRefresh().getTime() > TOKEN_EXTEND_THRESHOLD_SECONDS * 1000) {
1613
            result = true;
1614
        }
1615
 
1616
        return result;
1617
    }
1618
 
1619
    private AppEventsLogger getAppEventsLogger() {
1620
        synchronized (lock) {
1621
            if (appEventsLogger == null) {
1622
                appEventsLogger = AppEventsLogger.newLogger(staticContext, applicationId);
1623
            }
1624
            return appEventsLogger;
1625
        }
1626
    }
1627
 
1628
    AccessToken getTokenInfo() {
1629
        return tokenInfo;
1630
    }
1631
 
1632
    void setTokenInfo(AccessToken tokenInfo) {
1633
        this.tokenInfo = tokenInfo;
1634
    }
1635
 
1636
    Date getLastAttemptedTokenExtendDate() {
1637
        return lastAttemptedTokenExtendDate;
1638
    }
1639
 
1640
    void setLastAttemptedTokenExtendDate(Date lastAttemptedTokenExtendDate) {
1641
        this.lastAttemptedTokenExtendDate = lastAttemptedTokenExtendDate;
1642
    }
1643
 
1644
    void setCurrentTokenRefreshRequest(TokenRefreshRequest request) {
1645
        this.currentTokenRefreshRequest = request;
1646
    }
1647
 
1648
    class TokenRefreshRequest implements ServiceConnection {
1649
 
1650
        final Messenger messageReceiver = new Messenger(
1651
                new TokenRefreshRequestHandler(Session.this, this));
1652
 
1653
        Messenger messageSender = null;
1654
 
1655
        public void bind() {
1656
            Intent intent = NativeProtocol.createTokenRefreshIntent(getStaticContext());
1657
            if (intent != null
1658
                    && staticContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
1659
                setLastAttemptedTokenExtendDate(new Date());
1660
            } else {
1661
                cleanup();
1662
            }
1663
        }
1664
 
1665
        @Override
1666
        public void onServiceConnected(ComponentName className, IBinder service) {
1667
            messageSender = new Messenger(service);
1668
            refreshToken();
1669
        }
1670
 
1671
        @Override
1672
        public void onServiceDisconnected(ComponentName arg) {
1673
            cleanup();
1674
 
1675
            try {
1676
                // We returned an error so there's no point in
1677
                // keeping the binding open.
1678
                staticContext.unbindService(TokenRefreshRequest.this);
1679
            } catch (IllegalArgumentException ex) {
1680
                // Do nothing, the connection was already unbound
1681
            }
1682
        }
1683
 
1684
        private void cleanup() {
1685
            if (currentTokenRefreshRequest == this) {
1686
                currentTokenRefreshRequest = null;
1687
            }
1688
        }
1689
 
1690
        private void refreshToken() {
1691
            Bundle requestData = new Bundle();
1692
            requestData.putString(AccessToken.ACCESS_TOKEN_KEY, getTokenInfo().getToken());
1693
 
1694
            Message request = Message.obtain();
1695
            request.setData(requestData);
1696
            request.replyTo = messageReceiver;
1697
 
1698
            try {
1699
                messageSender.send(request);
1700
            } catch (RemoteException e) {
1701
                cleanup();
1702
            }
1703
        }
1704
 
1705
    }
1706
 
1707
    // Creating a static Handler class to reduce the possibility of a memory leak.
1708
    // Handler objects for the same thread all share a common Looper object, which they post messages
1709
    // to and read from. As messages contain target Handler, as long as there are messages with target
1710
    // handler in the message queue, the handler cannot be garbage collected. If handler is not static,
1711
    // the instance of the containing class also cannot be garbage collected even if it is destroyed.
1712
    static class TokenRefreshRequestHandler extends Handler {
1713
 
1714
        private WeakReference<Session> sessionWeakReference;
1715
        private WeakReference<TokenRefreshRequest> refreshRequestWeakReference;
1716
 
1717
        TokenRefreshRequestHandler(Session session, TokenRefreshRequest refreshRequest) {
1718
            super(Looper.getMainLooper());
1719
            sessionWeakReference = new WeakReference<Session>(session);
1720
            refreshRequestWeakReference = new WeakReference<TokenRefreshRequest>(refreshRequest);
1721
        }
1722
 
1723
        @Override
1724
        public void handleMessage(Message msg) {
1725
            String token = msg.getData().getString(AccessToken.ACCESS_TOKEN_KEY);
1726
            Session session = sessionWeakReference.get();
1727
 
1728
            if (session != null && token != null) {
1729
                session.extendTokenCompleted(msg.getData());
1730
            }
1731
 
1732
            TokenRefreshRequest request = refreshRequestWeakReference.get();
1733
            if (request != null) {
1734
                // The refreshToken function should be called rarely,
1735
                // so there is no point in keeping the binding open.
1736
                staticContext.unbindService(request);
1737
                request.cleanup();
1738
            }
1739
        }
1740
    }
1741
 
1742
    /**
1743
     * Provides asynchronous notification of Session state changes.
1744
     *
1745
     * @see Session#open open
1746
     */
1747
    public interface StatusCallback {
1748
        public void call(Session session, SessionState state, Exception exception);
1749
    }
1750
 
1751
    @Override
1752
    public int hashCode() {
1753
        return 0;
1754
    }
1755
 
1756
    @Override
1757
    public boolean equals(Object otherObj) {
1758
        if (!(otherObj instanceof Session)) {
1759
            return false;
1760
        }
1761
        Session other = (Session) otherObj;
1762
 
1763
        return areEqual(other.applicationId, applicationId) &&
1764
                areEqual(other.authorizationBundle, authorizationBundle) &&
1765
                areEqual(other.state, state) &&
1766
                areEqual(other.getExpirationDate(), getExpirationDate());
1767
    }
1768
 
1769
    private static boolean areEqual(Object a, Object b) {
1770
        if (a == null) {
1771
            return b == null;
1772
        } else {
1773
            return a.equals(b);
1774
        }
1775
    }
1776
 
1777
    /**
1778
     * Builder class used to create a Session.
1779
     */
1780
    public static final class Builder {
1781
        private final Context context;
1782
        private String applicationId;
1783
        private TokenCachingStrategy tokenCachingStrategy;
1784
 
1785
        /**
1786
         * Constructs a new Builder associated with the context.
1787
         *
1788
         * @param context the Activity or Service starting the Session
1789
         */
1790
        public Builder(Context context) {
1791
            this.context = context;
1792
        }
1793
 
1794
        /**
1795
         * Sets the application id for the Session.
1796
         *
1797
         * @param applicationId the application id
1798
         * @return the Builder instance
1799
         */
1800
        public Builder setApplicationId(final String applicationId) {
1801
            this.applicationId = applicationId;
1802
            return this;
1803
        }
1804
 
1805
        /**
1806
         * Sets the TokenCachingStrategy for the Session.
1807
         *
1808
         * @param tokenCachingStrategy the token cache to use
1809
         * @return the Builder instance
1810
         */
1811
        public Builder setTokenCachingStrategy(final TokenCachingStrategy tokenCachingStrategy) {
1812
            this.tokenCachingStrategy = tokenCachingStrategy;
1813
            return this;
1814
        }
1815
 
1816
        /**
1817
         * Build the Session.
1818
         *
1819
         * @return a new Session
1820
         */
1821
        public Session build() {
1822
            return new Session(context, applicationId, tokenCachingStrategy);
1823
        }
1824
    }
1825
 
1826
    interface StartActivityDelegate {
1827
        public void startActivityForResult(Intent intent, int requestCode);
1828
 
1829
        public Activity getActivityContext();
1830
    }
1831
 
1832
    @SuppressWarnings("deprecation")
1833
    private void autoPublishAsync() {
1834
        AutoPublishAsyncTask asyncTask = null;
1835
        synchronized (this) {
1836
            if (autoPublishAsyncTask == null && Settings.getShouldAutoPublishInstall()) {
1837
                // copy the application id to guarantee thread safety against our container.
1838
                String applicationId = Session.this.applicationId;
1839
 
1840
                // skip publish if we don't have an application id.
1841
                if (applicationId != null) {
1842
                    asyncTask = autoPublishAsyncTask = new AutoPublishAsyncTask(applicationId, staticContext);
1843
                }
1844
            }
1845
        }
1846
 
1847
        if (asyncTask != null) {
1848
            asyncTask.execute();
1849
        }
1850
    }
1851
 
1852
    /**
1853
     * Async implementation to allow auto publishing to not block the ui thread.
1854
     */
1855
    private class AutoPublishAsyncTask extends AsyncTask<Void, Void, Void> {
1856
        private final String mApplicationId;
1857
        private final Context mApplicationContext;
1858
 
1859
        public AutoPublishAsyncTask(String applicationId, Context context) {
1860
            mApplicationId = applicationId;
1861
            mApplicationContext = context.getApplicationContext();
1862
        }
1863
 
1864
        @Override
1865
        protected Void doInBackground(Void... voids) {
1866
            try {
1867
                Settings.publishInstallAndWaitForResponse(mApplicationContext, mApplicationId, true);
1868
            } catch (Exception e) {
1869
                Utility.logd("Facebook-publish", e);
1870
            }
1871
            return null;
1872
        }
1873
 
1874
        @Override
1875
        protected void onPostExecute(Void result) {
1876
            // always clear out the publisher to allow other invocations.
1877
            synchronized (Session.this) {
1878
                autoPublishAsyncTask = null;
1879
            }
1880
        }
1881
    }
1882
 
1883
    /**
1884
     * Base class for authorization requests {@link OpenRequest} and {@link NewPermissionsRequest}.
1885
     */
1886
    public static class AuthorizationRequest implements Serializable {
1887
 
1888
        private static final long serialVersionUID = 1L;
1889
 
1890
        private final StartActivityDelegate startActivityDelegate;
1891
        private SessionLoginBehavior loginBehavior = SessionLoginBehavior.SSO_WITH_FALLBACK;
1892
        private int requestCode = DEFAULT_AUTHORIZE_ACTIVITY_CODE;
1893
        private StatusCallback statusCallback;
1894
        private boolean isLegacy = false;
1895
        private List<String> permissions = Collections.emptyList();
1896
        private SessionDefaultAudience defaultAudience = SessionDefaultAudience.FRIENDS;
1897
        private String applicationId;
1898
        private String validateSameFbidAsToken;
1899
        private final String authId = UUID.randomUUID().toString();
1900
        private final Map<String, String> loggingExtras = new HashMap<String, String>();
1901
 
1902
        AuthorizationRequest(final Activity activity) {
1903
            startActivityDelegate = new StartActivityDelegate() {
1904
                @Override
1905
                public void startActivityForResult(Intent intent, int requestCode) {
1906
                    activity.startActivityForResult(intent, requestCode);
1907
                }
1908
 
1909
                @Override
1910
                public Activity getActivityContext() {
1911
                    return activity;
1912
                }
1913
            };
1914
        }
1915
 
1916
        AuthorizationRequest(final Fragment fragment) {
1917
            startActivityDelegate = new StartActivityDelegate() {
1918
                @Override
1919
                public void startActivityForResult(Intent intent, int requestCode) {
1920
                    fragment.startActivityForResult(intent, requestCode);
1921
                }
1922
 
1923
                @Override
1924
                public Activity getActivityContext() {
1925
                    return fragment.getActivity();
1926
                }
1927
            };
1928
        }
1929
 
1930
        /**
1931
         * Constructor to be used for V1 serialization only, DO NOT CHANGE.
1932
         */
1933
        private AuthorizationRequest(SessionLoginBehavior loginBehavior, int requestCode,
1934
                List<String> permissions, String defaultAudience, boolean isLegacy, String applicationId,
1935
                String validateSameFbidAsToken) {
1936
            startActivityDelegate = new StartActivityDelegate() {
1937
                @Override
1938
                public void startActivityForResult(Intent intent, int requestCode) {
1939
                    throw new UnsupportedOperationException(
1940
                            "Cannot create an AuthorizationRequest without a valid Activity or Fragment");
1941
                }
1942
 
1943
                @Override
1944
                public Activity getActivityContext() {
1945
                    throw new UnsupportedOperationException(
1946
                            "Cannot create an AuthorizationRequest without a valid Activity or Fragment");
1947
                }
1948
            };
1949
            this.loginBehavior = loginBehavior;
1950
            this.requestCode = requestCode;
1951
            this.permissions = permissions;
1952
            this.defaultAudience = SessionDefaultAudience.valueOf(defaultAudience);
1953
            this.isLegacy = isLegacy;
1954
            this.applicationId = applicationId;
1955
            this.validateSameFbidAsToken = validateSameFbidAsToken;
1956
        }
1957
 
1958
        /**
1959
         * Used for backwards compatibility with Facebook.java only, DO NOT USE.
1960
         *
1961
         * @param isLegacy
1962
         */
1963
        public void setIsLegacy(boolean isLegacy) {
1964
            this.isLegacy = isLegacy;
1965
        }
1966
 
1967
        boolean isLegacy() {
1968
            return isLegacy;
1969
        }
1970
 
1971
        AuthorizationRequest setCallback(StatusCallback statusCallback) {
1972
            this.statusCallback = statusCallback;
1973
            return this;
1974
        }
1975
 
1976
        StatusCallback getCallback() {
1977
            return statusCallback;
1978
        }
1979
 
1980
        AuthorizationRequest setLoginBehavior(SessionLoginBehavior loginBehavior) {
1981
            if (loginBehavior != null) {
1982
                this.loginBehavior = loginBehavior;
1983
            }
1984
            return this;
1985
        }
1986
 
1987
        SessionLoginBehavior getLoginBehavior() {
1988
            return loginBehavior;
1989
        }
1990
 
1991
        AuthorizationRequest setRequestCode(int requestCode) {
1992
            if (requestCode >= 0) {
1993
                this.requestCode = requestCode;
1994
            }
1995
            return this;
1996
        }
1997
 
1998
        int getRequestCode() {
1999
            return requestCode;
2000
        }
2001
 
2002
        AuthorizationRequest setPermissions(List<String> permissions) {
2003
            if (permissions != null) {
2004
                this.permissions = permissions;
2005
            }
2006
            return this;
2007
        }
2008
 
2009
        AuthorizationRequest setPermissions(String... permissions) {
2010
            return setPermissions(Arrays.asList(permissions));
2011
        }
2012
 
2013
        List<String> getPermissions() {
2014
            return permissions;
2015
        }
2016
 
2017
        AuthorizationRequest setDefaultAudience(SessionDefaultAudience defaultAudience) {
2018
            if (defaultAudience != null) {
2019
                this.defaultAudience = defaultAudience;
2020
            }
2021
            return this;
2022
        }
2023
 
2024
        SessionDefaultAudience getDefaultAudience() {
2025
            return defaultAudience;
2026
        }
2027
 
2028
        StartActivityDelegate getStartActivityDelegate() {
2029
            return startActivityDelegate;
2030
        }
2031
 
2032
        String getApplicationId() {
2033
            return applicationId;
2034
        }
2035
 
2036
        void setApplicationId(String applicationId) {
2037
            this.applicationId = applicationId;
2038
        }
2039
 
2040
        String getValidateSameFbidAsToken() {
2041
            return validateSameFbidAsToken;
2042
        }
2043
 
2044
        void setValidateSameFbidAsToken(String validateSameFbidAsToken) {
2045
            this.validateSameFbidAsToken = validateSameFbidAsToken;
2046
        }
2047
 
2048
        String getAuthId() {
2049
            return authId;
2050
        }
2051
 
2052
        AuthorizationClient.AuthorizationRequest getAuthorizationClientRequest() {
2053
            AuthorizationClient.StartActivityDelegate delegate = new AuthorizationClient.StartActivityDelegate() {
2054
                @Override
2055
                public void startActivityForResult(Intent intent, int requestCode) {
2056
                    startActivityDelegate.startActivityForResult(intent, requestCode);
2057
                }
2058
 
2059
                @Override
2060
                public Activity getActivityContext() {
2061
                    return startActivityDelegate.getActivityContext();
2062
                }
2063
            };
2064
            return new AuthorizationClient.AuthorizationRequest(loginBehavior, requestCode, isLegacy,
2065
                    permissions, defaultAudience, applicationId, validateSameFbidAsToken, delegate, authId);
2066
        }
2067
 
2068
        // package private so subclasses can use it
2069
        Object writeReplace() {
2070
            return new AuthRequestSerializationProxyV1(
2071
                    loginBehavior, requestCode, permissions, defaultAudience.name(), isLegacy, applicationId, validateSameFbidAsToken);
2072
        }
2073
 
2074
        // have a readObject that throws to prevent spoofing; must be private so serializer will call it (will be
2075
        // called automatically prior to any base class)
2076
        private void readObject(ObjectInputStream stream) throws InvalidObjectException {
2077
            throw new InvalidObjectException("Cannot readObject, serialization proxy required");
2078
        }
2079
 
2080
        private static class AuthRequestSerializationProxyV1 implements Serializable {
2081
            private static final long serialVersionUID = -8748347685113614927L;
2082
            private final SessionLoginBehavior loginBehavior;
2083
            private final int requestCode;
2084
            private boolean isLegacy;
2085
            private final List<String> permissions;
2086
            private final String defaultAudience;
2087
            private final String applicationId;
2088
            private final String validateSameFbidAsToken;
2089
 
2090
            private AuthRequestSerializationProxyV1(SessionLoginBehavior loginBehavior,
2091
                    int requestCode, List<String> permissions, String defaultAudience, boolean isLegacy,
2092
                    String applicationId, String validateSameFbidAsToken) {
2093
                this.loginBehavior = loginBehavior;
2094
                this.requestCode = requestCode;
2095
                this.permissions = permissions;
2096
                this.defaultAudience = defaultAudience;
2097
                this.isLegacy = isLegacy;
2098
                this.applicationId = applicationId;
2099
                this.validateSameFbidAsToken = validateSameFbidAsToken;
2100
            }
2101
 
2102
            private Object readResolve() {
2103
                return new AuthorizationRequest(loginBehavior, requestCode, permissions, defaultAudience, isLegacy,
2104
                        applicationId, validateSameFbidAsToken);
2105
            }
2106
        }
2107
    }
2108
 
2109
    /**
2110
     * A request used to open a Session.
2111
     */
2112
    public static final class OpenRequest extends AuthorizationRequest {
2113
        private static final long serialVersionUID = 1L;
2114
 
2115
        /**
2116
         * Constructs an OpenRequest.
2117
         *
2118
         * @param activity the Activity to use to open the Session
2119
         */
2120
        public OpenRequest(Activity activity) {
2121
            super(activity);
2122
        }
2123
 
2124
        /**
2125
         * Constructs an OpenRequest.
2126
         *
2127
         * @param fragment the Fragment to use to open the Session
2128
         */
2129
        public OpenRequest(Fragment fragment) {
2130
            super(fragment);
2131
        }
2132
 
2133
        /**
2134
         * Sets the StatusCallback for the OpenRequest.
2135
         *
2136
         * @param statusCallback The {@link StatusCallback SessionStatusCallback} to
2137
         *                       notify regarding Session state changes.
2138
         * @return the OpenRequest object to allow for chaining
2139
         */
2140
        public final OpenRequest setCallback(StatusCallback statusCallback) {
2141
            super.setCallback(statusCallback);
2142
            return this;
2143
        }
2144
 
2145
        /**
2146
         * Sets the login behavior for the OpenRequest.
2147
         *
2148
         * @param loginBehavior The {@link SessionLoginBehavior SessionLoginBehavior} that
2149
         *                      specifies what behaviors should be attempted during
2150
         *                      authorization.
2151
         * @return the OpenRequest object to allow for chaining
2152
         */
2153
        public final OpenRequest setLoginBehavior(SessionLoginBehavior loginBehavior) {
2154
            super.setLoginBehavior(loginBehavior);
2155
            return this;
2156
        }
2157
 
2158
        /**
2159
         * Sets the request code for the OpenRequest.
2160
         *
2161
         * @param requestCode An integer that identifies this request. This integer will be used
2162
         *                    as the request code in {@link Activity#onActivityResult
2163
         *                    onActivityResult}. This integer should be >= 0. If a value < 0 is
2164
         *                    passed in, then a default value will be used.
2165
         * @return the OpenRequest object to allow for chaining
2166
         */
2167
        public final OpenRequest setRequestCode(int requestCode) {
2168
            super.setRequestCode(requestCode);
2169
            return this;
2170
        }
2171
 
2172
        /**
2173
         * Sets the permissions for the OpenRequest.
2174
         *
2175
         * @param permissions A List&lt;String&gt; representing the permissions to request
2176
         *                    during the authentication flow. A null or empty List
2177
         *                    represents basic permissions.
2178
         * @return the OpenRequest object to allow for chaining
2179
         */
2180
        public final OpenRequest setPermissions(List<String> permissions) {
2181
            super.setPermissions(permissions);
2182
            return this;
2183
        }
2184
 
2185
        /**
2186
         * Sets the permissions for the OpenRequest.
2187
         *
2188
         * @param permissions the permissions to request during the authentication flow.
2189
         * @return the OpenRequest object to allow for chaining
2190
         */
2191
        public final OpenRequest setPermissions(String... permissions) {
2192
            super.setPermissions(permissions);
2193
            return this;
2194
        }
2195
 
2196
        /**
2197
         * Sets the defaultAudience for the OpenRequest.
2198
         * <p/>
2199
         * This is only used during Native login using a sufficiently recent facebook app.
2200
         *
2201
         * @param defaultAudience A SessionDefaultAudience representing the default audience setting to request.
2202
         * @return the OpenRequest object to allow for chaining
2203
         */
2204
        public final OpenRequest setDefaultAudience(SessionDefaultAudience defaultAudience) {
2205
            super.setDefaultAudience(defaultAudience);
2206
            return this;
2207
        }
2208
    }
2209
 
2210
    /**
2211
     * A request to be used to request new permissions for a Session.
2212
     */
2213
    public static final class NewPermissionsRequest extends AuthorizationRequest {
2214
        private static final long serialVersionUID = 1L;
2215
 
2216
        /**
2217
         * Constructs a NewPermissionsRequest.
2218
         *
2219
         * @param activity    the Activity used to issue the request
2220
         * @param permissions additional permissions to request
2221
         */
2222
        public NewPermissionsRequest(Activity activity, List<String> permissions) {
2223
            super(activity);
2224
            setPermissions(permissions);
2225
        }
2226
 
2227
        /**
2228
         * Constructs a NewPermissionsRequest.
2229
         *
2230
         * @param fragment    the Fragment used to issue the request
2231
         * @param permissions additional permissions to request
2232
         */
2233
        public NewPermissionsRequest(Fragment fragment, List<String> permissions) {
2234
            super(fragment);
2235
            setPermissions(permissions);
2236
        }
2237
 
2238
        /**
2239
         * Constructs a NewPermissionsRequest.
2240
         *
2241
         * @param activity    the Activity used to issue the request
2242
         * @param permissions additional permissions to request
2243
         */
2244
        public NewPermissionsRequest(Activity activity, String... permissions) {
2245
            super(activity);
2246
            setPermissions(permissions);
2247
        }
2248
 
2249
        /**
2250
         * Constructs a NewPermissionsRequest.
2251
         *
2252
         * @param fragment    the Fragment used to issue the request
2253
         * @param permissions additional permissions to request
2254
         */
2255
        public NewPermissionsRequest(Fragment fragment, String... permissions) {
2256
            super(fragment);
2257
            setPermissions(permissions);
2258
        }
2259
 
2260
        /**
2261
         * Sets the StatusCallback for the NewPermissionsRequest. Note that once the request is made, this callback
2262
         * will be added to the session, and will receive all future state changes on the session.
2263
         *
2264
         * @param statusCallback The {@link StatusCallback SessionStatusCallback} to
2265
         *                       notify regarding Session state changes.
2266
         * @return the NewPermissionsRequest object to allow for chaining
2267
         */
2268
        public final NewPermissionsRequest setCallback(StatusCallback statusCallback) {
2269
            super.setCallback(statusCallback);
2270
            return this;
2271
        }
2272
 
2273
        /**
2274
         * Sets the login behavior for the NewPermissionsRequest.
2275
         *
2276
         * @param loginBehavior The {@link SessionLoginBehavior SessionLoginBehavior} that
2277
         *                      specifies what behaviors should be attempted during
2278
         *                      authorization.
2279
         * @return the NewPermissionsRequest object to allow for chaining
2280
         */
2281
        public final NewPermissionsRequest setLoginBehavior(SessionLoginBehavior loginBehavior) {
2282
            super.setLoginBehavior(loginBehavior);
2283
            return this;
2284
        }
2285
 
2286
        /**
2287
         * Sets the request code for the NewPermissionsRequest.
2288
         *
2289
         * @param requestCode An integer that identifies this request. This integer will be used
2290
         *                    as the request code in {@link Activity#onActivityResult
2291
         *                    onActivityResult}. This integer should be >= 0. If a value < 0 is
2292
         *                    passed in, then a default value will be used.
2293
         * @return the NewPermissionsRequest object to allow for chaining
2294
         */
2295
        public final NewPermissionsRequest setRequestCode(int requestCode) {
2296
            super.setRequestCode(requestCode);
2297
            return this;
2298
        }
2299
 
2300
        /**
2301
         * Sets the defaultAudience for the OpenRequest.
2302
         *
2303
         * @param defaultAudience A SessionDefaultAudience representing the default audience setting to request.
2304
         * @return the NewPermissionsRequest object to allow for chaining
2305
         */
2306
        public final NewPermissionsRequest setDefaultAudience(SessionDefaultAudience defaultAudience) {
2307
            super.setDefaultAudience(defaultAudience);
2308
            return this;
2309
        }
2310
 
2311
        @Override
2312
        AuthorizationClient.AuthorizationRequest getAuthorizationClientRequest() {
2313
            AuthorizationClient.AuthorizationRequest request = super.getAuthorizationClientRequest();
2314
            request.setRerequest(true);
2315
            return request;
2316
        }
2317
    }
2318
}