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.widget;
18
 
19
import android.app.Activity;
20
import android.app.AlertDialog;
21
import android.content.Context;
22
import android.content.ContextWrapper;
23
import android.content.DialogInterface;
24
import android.content.Intent;
25
import android.content.res.TypedArray;
26
import android.graphics.Canvas;
27
import android.graphics.Typeface;
28
import android.os.AsyncTask;
29
import android.os.Bundle;
30
import android.support.v4.app.Fragment;
31
import android.util.AttributeSet;
32
import android.util.Log;
33
import android.util.TypedValue;
34
import android.view.Gravity;
35
import android.view.View;
36
import android.widget.Button;
37
import com.facebook.*;
38
import com.facebook.android.R;
39
import com.facebook.internal.AnalyticsEvents;
40
import com.facebook.internal.SessionAuthorizationType;
41
import com.facebook.internal.SessionTracker;
42
import com.facebook.internal.Utility;
43
import com.facebook.internal.Utility.FetchedAppSettings;
44
import com.facebook.model.GraphUser;
45
 
46
import java.util.Arrays;
47
import java.util.Collections;
48
import java.util.List;
49
 
50
/**
51
 * A Log In/Log Out button that maintains session state and logs
52
 * in/out for the app.
53
 * <p/>
54
 * This control will create and use the active session upon construction
55
 * if it has the available data (if the app ID is specified in the manifest).
56
 * It will also open the active session if it does not require user interaction
57
 * (i.e. if the session is in the {@link com.facebook.SessionState#CREATED_TOKEN_LOADED} state.
58
 * Developers can override the use of the active session by calling
59
 * the {@link #setSession(com.facebook.Session)} method.
60
 */
61
public class LoginButton extends Button {
62
 
63
    public static enum ToolTipMode {
64
        /**
65
         * Default display mode. A server query will determine if the tool tip should be displayed
66
         * and, if so, what the string shown to the user should be.
67
         */
68
        DEFAULT,
69
 
70
        /**
71
         * Display the tool tip with a local string--regardless of what the server returns 
72
         */
73
        DISPLAY_ALWAYS,
74
 
75
        /**
76
         * Never display the tool tip--regardless of what the server says
77
         */
78
        NEVER_DISPLAY
79
    }
80
 
81
    private static final String TAG = LoginButton.class.getName();
82
    private String applicationId = null;
83
    private SessionTracker sessionTracker;
84
    private GraphUser user = null;
85
    private Session userInfoSession = null; // the Session used to fetch the current user info
86
    private boolean confirmLogout;
87
    private boolean fetchUserInfo;
88
    private String loginText;
89
    private String logoutText;
90
    private UserInfoChangedCallback userInfoChangedCallback;
91
    private Fragment parentFragment;
92
    private LoginButtonProperties properties = new LoginButtonProperties();
93
    private String loginLogoutEventName = AnalyticsEvents.EVENT_LOGIN_VIEW_USAGE;
94
    private OnClickListener listenerCallback;
95
    private boolean nuxChecked;
96
    private ToolTipPopup.Style nuxStyle = ToolTipPopup.Style.BLUE;
97
    private ToolTipMode nuxMode = ToolTipMode.DEFAULT;
98
    private long nuxDisplayTime = ToolTipPopup.DEFAULT_POPUP_DISPLAY_TIME;
99
    private ToolTipPopup nuxPopup;
100
 
101
    static class LoginButtonProperties {
102
        private SessionDefaultAudience defaultAudience = SessionDefaultAudience.FRIENDS;
103
        private List<String> permissions = Collections.<String>emptyList();
104
        private SessionAuthorizationType authorizationType = null;
105
        private OnErrorListener onErrorListener;
106
        private SessionLoginBehavior loginBehavior = SessionLoginBehavior.SSO_WITH_FALLBACK;
107
        private Session.StatusCallback sessionStatusCallback;
108
 
109
        public void setOnErrorListener(OnErrorListener onErrorListener) {
110
            this.onErrorListener = onErrorListener;
111
        }
112
 
113
        public OnErrorListener getOnErrorListener() {
114
            return onErrorListener;
115
        }
116
 
117
        public void setDefaultAudience(SessionDefaultAudience defaultAudience) {
118
            this.defaultAudience = defaultAudience;
119
        }
120
 
121
        public SessionDefaultAudience getDefaultAudience() {
122
            return defaultAudience;
123
        }
124
 
125
        public void setReadPermissions(List<String> permissions, Session session) {
126
            if (SessionAuthorizationType.PUBLISH.equals(authorizationType)) {
127
                throw new UnsupportedOperationException(
128
                        "Cannot call setReadPermissions after setPublishPermissions has been called.");
129
            }
130
            if (validatePermissions(permissions, SessionAuthorizationType.READ, session)) {
131
                this.permissions = permissions;
132
                authorizationType = SessionAuthorizationType.READ;
133
            }
134
        }
135
 
136
        public void setPublishPermissions(List<String> permissions, Session session) {
137
            if (SessionAuthorizationType.READ.equals(authorizationType)) {
138
                throw new UnsupportedOperationException(
139
                        "Cannot call setPublishPermissions after setReadPermissions has been called.");
140
            }
141
            if (validatePermissions(permissions, SessionAuthorizationType.PUBLISH, session)) {
142
                this.permissions = permissions;
143
                authorizationType = SessionAuthorizationType.PUBLISH;
144
            }
145
        }
146
 
147
        private boolean validatePermissions(List<String> permissions,
148
                SessionAuthorizationType authType, Session currentSession) {
149
            if (SessionAuthorizationType.PUBLISH.equals(authType)) {
150
                if (Utility.isNullOrEmpty(permissions)) {
151
                    throw new IllegalArgumentException("Permissions for publish actions cannot be null or empty.");
152
                }
153
            }
154
            if (currentSession != null && currentSession.isOpened()) {
155
                if (!Utility.isSubset(permissions, currentSession.getPermissions())) {
156
                    Log.e(TAG, "Cannot set additional permissions when session is already open.");
157
                    return false;
158
                }
159
            }
160
            return true;
161
        }
162
 
163
        List<String> getPermissions() {
164
            return permissions;
165
        }
166
 
167
        public void clearPermissions() {
168
            permissions = null;
169
            authorizationType = null;
170
        }
171
 
172
        public void setLoginBehavior(SessionLoginBehavior loginBehavior) {
173
            this.loginBehavior = loginBehavior;
174
        }
175
 
176
        public SessionLoginBehavior getLoginBehavior() {
177
            return loginBehavior;
178
        }
179
 
180
        public void setSessionStatusCallback(Session.StatusCallback callback) {
181
            this.sessionStatusCallback = callback;
182
        }
183
 
184
        public Session.StatusCallback getSessionStatusCallback() {
185
            return sessionStatusCallback;
186
        }
187
    }
188
 
189
    /**
190
     * Specifies a callback interface that will be called when the button's notion of the current
191
     * user changes (if the fetch_user_info attribute is true for this control).
192
     */
193
    public interface UserInfoChangedCallback {
194
        /**
195
         * Called when the current user changes.
196
         * @param user  the current user, or null if there is no user
197
         */
198
        void onUserInfoFetched(GraphUser user);
199
    }
200
 
201
    /**
202
     * Callback interface that will be called when a network or other error is encountered
203
     * while logging in.
204
     */
205
    public interface OnErrorListener {
206
        /**
207
         * Called when a network or other error is encountered.
208
         * @param error     a FacebookException representing the error that was encountered.
209
         */
210
        void onError(FacebookException error);
211
    }
212
 
213
    /**
214
     * Create the LoginButton.
215
     *
216
     * @see View#View(Context)
217
     */
218
    public LoginButton(Context context) {
219
        super(context);
220
        initializeActiveSessionWithCachedToken(context);
221
        // since onFinishInflate won't be called, we need to finish initialization ourselves
222
        finishInit();
223
    }
224
 
225
    /**
226
     * Create the LoginButton by inflating from XML
227
     *
228
     * @see View#View(Context, AttributeSet)
229
     */
230
    public LoginButton(Context context, AttributeSet attrs) {
231
        super(context, attrs);
232
 
233
        if (attrs.getStyleAttribute() == 0) {
234
            // apparently there's no method of setting a default style in xml,
235
            // so in case the users do not explicitly specify a style, we need
236
            // to use sensible defaults.
237
            this.setGravity(Gravity.CENTER);
238
            this.setTextColor(getResources().getColor(R.color.com_facebook_loginview_text_color));
239
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX,
240
                    getResources().getDimension(R.dimen.com_facebook_loginview_text_size));
241
            this.setTypeface(Typeface.DEFAULT_BOLD);
242
            if (isInEditMode()) {
243
                // cannot use a drawable in edit mode, so setting the background color instead
244
                // of a background resource.
245
                this.setBackgroundColor(getResources().getColor(R.color.com_facebook_blue));
246
                // hardcoding in edit mode as getResources().getString() doesn't seem to work in IntelliJ
247
                loginText = "Log in with Facebook";
248
            } else {
249
                this.setBackgroundResource(R.drawable.com_facebook_button_blue);
250
                this.setCompoundDrawablesWithIntrinsicBounds(R.drawable.com_facebook_inverse_icon, 0, 0, 0);
251
                this.setCompoundDrawablePadding(
252
                        getResources().getDimensionPixelSize(R.dimen.com_facebook_loginview_compound_drawable_padding));
253
                this.setPadding(getResources().getDimensionPixelSize(R.dimen.com_facebook_loginview_padding_left),
254
                        getResources().getDimensionPixelSize(R.dimen.com_facebook_loginview_padding_top),
255
                        getResources().getDimensionPixelSize(R.dimen.com_facebook_loginview_padding_right),
256
                        getResources().getDimensionPixelSize(R.dimen.com_facebook_loginview_padding_bottom));
257
            }
258
        }
259
        parseAttributes(attrs);
260
        if (!isInEditMode()) {
261
            initializeActiveSessionWithCachedToken(context);
262
        }        
263
    }
264
 
265
    /**
266
     * Create the LoginButton by inflating from XML and applying a style.
267
     *
268
     * @see View#View(Context, AttributeSet, int)
269
     */
270
    public LoginButton(Context context, AttributeSet attrs, int defStyle) {
271
        super(context, attrs, defStyle);
272
        parseAttributes(attrs);
273
        initializeActiveSessionWithCachedToken(context);
274
    }
275
 
276
    /**
277
     * Sets an OnErrorListener for this instance of LoginButton to call into when
278
     * certain exceptions occur.
279
     *
280
     * @param onErrorListener The listener object to set
281
     */
282
    public void setOnErrorListener(OnErrorListener onErrorListener) {
283
        properties.setOnErrorListener(onErrorListener);
284
    }
285
 
286
    /**
287
     * Returns the current OnErrorListener for this instance of LoginButton.
288
     *
289
     * @return The OnErrorListener
290
     */
291
    public OnErrorListener getOnErrorListener() {
292
        return properties.getOnErrorListener();
293
    }
294
 
295
    /**
296
     * Sets the default audience to use when the session is opened.
297
     * This value is only useful when specifying write permissions for the native
298
     * login dialog.
299
     *
300
     * @param defaultAudience the default audience value to use
301
     */
302
    public void setDefaultAudience(SessionDefaultAudience defaultAudience) {
303
        properties.setDefaultAudience(defaultAudience);
304
    }
305
 
306
    /**
307
     * Gets the default audience to use when the session is opened.
308
     * This value is only useful when specifying write permissions for the native
309
     * login dialog.
310
     *
311
     * @return the default audience value to use
312
     */
313
    public SessionDefaultAudience getDefaultAudience() {
314
        return properties.getDefaultAudience();
315
    }
316
 
317
    /**
318
     * Set the permissions to use when the session is opened. The permissions here
319
     * can only be read permissions. If any publish permissions are included, the login
320
     * attempt by the user will fail. The LoginButton can only be associated with either
321
     * read permissions or publish permissions, but not both. Calling both
322
     * setReadPermissions and setPublishPermissions on the same instance of LoginButton
323
     * will result in an exception being thrown unless clearPermissions is called in between.
324
     * <p/>
325
     * This method is only meaningful if called before the session is open. If this is called
326
     * after the session is opened, and the list of permissions passed in is not a subset
327
     * of the permissions granted during the authorization, it will log an error.
328
     * <p/>
329
     * Since the session can be automatically opened when the LoginButton is constructed,
330
     * it's important to always pass in a consistent set of permissions to this method, or
331
     * manage the setting of permissions outside of the LoginButton class altogether
332
     * (by managing the session explicitly).
333
     *
334
     * @param permissions the read permissions to use
335
     *
336
     * @throws UnsupportedOperationException if setPublishPermissions has been called
337
     */
338
    public void setReadPermissions(List<String> permissions) {
339
        properties.setReadPermissions(permissions, sessionTracker.getSession());
340
    }
341
 
342
    /**
343
     * Set the permissions to use when the session is opened. The permissions here
344
     * can only be read permissions. If any publish permissions are included, the login
345
     * attempt by the user will fail. The LoginButton can only be associated with either
346
     * read permissions or publish permissions, but not both. Calling both
347
     * setReadPermissions and setPublishPermissions on the same instance of LoginButton
348
     * will result in an exception being thrown unless clearPermissions is called in between.
349
     * <p/>
350
     * This method is only meaningful if called before the session is open. If this is called
351
     * after the session is opened, and the list of permissions passed in is not a subset
352
     * of the permissions granted during the authorization, it will log an error.
353
     * <p/>
354
     * Since the session can be automatically opened when the LoginButton is constructed,
355
     * it's important to always pass in a consistent set of permissions to this method, or
356
     * manage the setting of permissions outside of the LoginButton class altogether
357
     * (by managing the session explicitly).
358
     *
359
     * @param permissions the read permissions to use
360
     *
361
     * @throws UnsupportedOperationException if setPublishPermissions has been called
362
     */
363
    public void setReadPermissions(String... permissions) {
364
        properties.setReadPermissions(Arrays.asList(permissions), sessionTracker.getSession());
365
    }
366
 
367
 
368
    /**
369
     * Set the permissions to use when the session is opened. The permissions here
370
     * should only be publish permissions. If any read permissions are included, the login
371
     * attempt by the user may fail. The LoginButton can only be associated with either
372
     * read permissions or publish permissions, but not both. Calling both
373
     * setReadPermissions and setPublishPermissions on the same instance of LoginButton
374
     * will result in an exception being thrown unless clearPermissions is called in between.
375
     * <p/>
376
     * This method is only meaningful if called before the session is open. If this is called
377
     * after the session is opened, and the list of permissions passed in is not a subset
378
     * of the permissions granted during the authorization, it will log an error.
379
     * <p/>
380
     * Since the session can be automatically opened when the LoginButton is constructed,
381
     * it's important to always pass in a consistent set of permissions to this method, or
382
     * manage the setting of permissions outside of the LoginButton class altogether
383
     * (by managing the session explicitly).
384
     *
385
     * @param permissions the publish permissions to use
386
     *
387
     * @throws UnsupportedOperationException if setReadPermissions has been called
388
     * @throws IllegalArgumentException if permissions is null or empty
389
     */
390
    public void setPublishPermissions(List<String> permissions) {
391
        properties.setPublishPermissions(permissions, sessionTracker.getSession());
392
    }
393
 
394
    /**
395
     * Set the permissions to use when the session is opened. The permissions here
396
     * should only be publish permissions. If any read permissions are included, the login
397
     * attempt by the user may fail. The LoginButton can only be associated with either
398
     * read permissions or publish permissions, but not both. Calling both
399
     * setReadPermissions and setPublishPermissions on the same instance of LoginButton
400
     * will result in an exception being thrown unless clearPermissions is called in between.
401
     * <p/>
402
     * This method is only meaningful if called before the session is open. If this is called
403
     * after the session is opened, and the list of permissions passed in is not a subset
404
     * of the permissions granted during the authorization, it will log an error.
405
     * <p/>
406
     * Since the session can be automatically opened when the LoginButton is constructed,
407
     * it's important to always pass in a consistent set of permissions to this method, or
408
     * manage the setting of permissions outside of the LoginButton class altogether
409
     * (by managing the session explicitly).
410
     *
411
     * @param permissions the publish permissions to use
412
     *
413
     * @throws UnsupportedOperationException if setReadPermissions has been called
414
     * @throws IllegalArgumentException if permissions is null or empty
415
     */
416
    public void setPublishPermissions(String... permissions) {
417
        properties.setPublishPermissions(Arrays.asList(permissions), sessionTracker.getSession());
418
    }
419
 
420
 
421
    /**
422
     * Clears the permissions currently associated with this LoginButton.
423
     */
424
    public void clearPermissions() {
425
        properties.clearPermissions();
426
    }
427
 
428
    /**
429
     * Sets the login behavior for the session that will be opened. If null is specified,
430
     * the default ({@link SessionLoginBehavior SessionLoginBehavior.SSO_WITH_FALLBACK}
431
     * will be used.
432
     *
433
     * @param loginBehavior The {@link SessionLoginBehavior SessionLoginBehavior} that
434
     *                      specifies what behaviors should be attempted during
435
     *                      authorization.
436
     */
437
    public void setLoginBehavior(SessionLoginBehavior loginBehavior) {
438
        properties.setLoginBehavior(loginBehavior);
439
    }
440
 
441
    /**
442
     * Gets the login behavior for the session that will be opened. If null is returned,
443
     * the default ({@link SessionLoginBehavior SessionLoginBehavior.SSO_WITH_FALLBACK}
444
     * will be used.
445
     *
446
     * @return loginBehavior The {@link SessionLoginBehavior SessionLoginBehavior} that
447
     *                      specifies what behaviors should be attempted during
448
     *                      authorization.
449
     */
450
    public SessionLoginBehavior getLoginBehavior() {
451
        return properties.getLoginBehavior();
452
    }
453
 
454
    /**
455
     * Set the application ID to be used to open the session.
456
     *
457
     * @param applicationId the application ID to use
458
     */
459
    public void setApplicationId(String applicationId) {
460
        this.applicationId = applicationId;
461
    }
462
 
463
    /**
464
     * Gets the callback interface that will be called when the current user changes.
465
     * @return the callback interface
466
     */
467
    public UserInfoChangedCallback getUserInfoChangedCallback() {
468
        return userInfoChangedCallback;
469
    }
470
 
471
    /**
472
     * Sets the callback interface that will be called when the current user changes.
473
     *
474
     * @param userInfoChangedCallback   the callback interface
475
     */
476
    public void setUserInfoChangedCallback(UserInfoChangedCallback userInfoChangedCallback) {
477
        this.userInfoChangedCallback = userInfoChangedCallback;
478
    }
479
 
480
    /**
481
     * Sets the callback interface that will be called whenever the status of the Session
482
     * associated with this LoginButton changes. Note that updates will only be sent to the
483
     * callback while the LoginButton is actually attached to a window.
484
     *
485
     * @param callback the callback interface
486
     */
487
    public void setSessionStatusCallback(Session.StatusCallback callback) {
488
        properties.setSessionStatusCallback(callback);
489
    }
490
 
491
    /**
492
     * Sets the callback interface that will be called whenever the status of the Session
493
     * associated with this LoginButton changes.
494
 
495
     * @return the callback interface
496
     */
497
    public Session.StatusCallback getSessionStatusCallback() {
498
        return properties.getSessionStatusCallback();
499
    }
500
 
501
    /**
502
     * Sets the style (background) of the Tool Tip popup. Currently a blue style and a black
503
     * style are supported. Blue is default
504
     * @param nuxStyle The style of the tool tip popup.
505
     */
506
    public void setToolTipStyle(ToolTipPopup.Style nuxStyle) {
507
        this.nuxStyle = nuxStyle;
508
    }
509
 
510
    /**
511
     * Sets the mode of the Tool Tip popup. Currently supported modes are default (normal
512
     * behavior), always_on (popup remains up until forcibly dismissed), and always_off (popup
513
     * doesn't show)
514
     * @param nuxMode The new mode for the tool tip
515
     */
516
    public void setToolTipMode(ToolTipMode nuxMode) {
517
        this.nuxMode = nuxMode;
518
    }
519
 
520
    /**
521
     * Return the current {@link ToolTipMode} for this LoginButton
522
     * @return The {@link ToolTipMode}
523
     */
524
    public ToolTipMode getToolTipMode() {
525
        return nuxMode;
526
    }
527
 
528
    /**
529
     * Sets the amount of time (in milliseconds) that the tool tip will be shown to the user. The 
530
     * default is {@value ToolTipPopup#DEFAULT_POPUP_DISPLAY_TIME}. Any value that is less than or
531
     * equal to zero will cause the tool tip to be displayed indefinitely.
532
     * @param displayTime The amount of time (in milliseconds) that the tool tip will be displayed
533
     * to the user
534
     */
535
    public void setToolTipDisplayTime(long displayTime) {
536
        this.nuxDisplayTime = displayTime;
537
    }
538
 
539
    /**
540
     * Gets the current amount of time (in ms) that the tool tip will be displayed to the user
541
     * @return
542
     */
543
    public long getToolTipDisplayTime() {
544
        return nuxDisplayTime;
545
    }
546
 
547
    /**
548
     * Dismisses the Nux Tooltip if it is currently visible
549
     */
550
    public void dismissToolTip() {
551
        if (nuxPopup != null) {
552
            nuxPopup.dismiss();
553
            nuxPopup = null;
554
        }
555
    }
556
 
557
    /**
558
     * Provides an implementation for {@link Activity#onActivityResult
559
     * onActivityResult} that updates the Session based on information returned
560
     * during the authorization flow. The Activity containing this view
561
     * should forward the resulting onActivityResult call here to
562
     * update the Session state based on the contents of the resultCode and
563
     * data.
564
     *
565
     * @param requestCode
566
     *            The requestCode parameter from the forwarded call. When this
567
     *            onActivityResult occurs as part of Facebook authorization
568
     *            flow, this value is the activityCode passed to open or
569
     *            authorize.
570
     * @param resultCode
571
     *            An int containing the resultCode parameter from the forwarded
572
     *            call.
573
     * @param data
574
     *            The Intent passed as the data parameter from the forwarded
575
     *            call.
576
     * @return A boolean indicating whether the requestCode matched a pending
577
     *         authorization request for this Session.
578
     * @see Session#onActivityResult(Activity, int, int, Intent)
579
     */
580
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
581
        Session session = sessionTracker.getSession();
582
        if (session != null) {
583
            return session.onActivityResult((Activity)getContext(), requestCode,
584
                    resultCode, data);
585
        } else {
586
            return false;
587
        }
588
    }
589
 
590
    /**
591
     * Set the Session object to use instead of the active Session. Since a Session
592
     * cannot be reused, if the user logs out from this Session, and tries to
593
     * log in again, a new Active Session will be used instead.
594
     * <p/>
595
     * If the passed in session is currently opened, this method will also attempt to
596
     * load some user information for display (if needed).
597
     *
598
     * @param newSession the Session object to use
599
     * @throws FacebookException if errors occur during the loading of user information
600
     */
601
    public void setSession(Session newSession) {
602
        sessionTracker.setSession(newSession);
603
        fetchUserInfo();
604
        setButtonText();
605
    }
606
 
607
    @Override
608
    public void onFinishInflate() {
609
        super.onFinishInflate();
610
        finishInit();
611
    }
612
 
613
    private void finishInit() {
614
        super.setOnClickListener(new LoginClickListener());
615
        setButtonText();
616
        if (!isInEditMode()) {
617
            sessionTracker = new SessionTracker(getContext(), new LoginButtonCallback(), null, false);
618
            fetchUserInfo();
619
        }
620
    }
621
 
622
    /**
623
     * Sets the fragment that contains this control. This allows the LoginButton to be
624
     * embedded inside a Fragment, and will allow the fragment to receive the
625
     * {@link Fragment#onActivityResult(int, int, android.content.Intent) onActivityResult}
626
     * call rather than the Activity.
627
     *
628
     * @param fragment the fragment that contains this control
629
     */
630
    public void setFragment(Fragment fragment) {
631
        parentFragment = fragment;
632
    }
633
 
634
    @Override
635
    protected void onAttachedToWindow() {
636
        super.onAttachedToWindow();
637
        if (sessionTracker != null && !sessionTracker.isTracking()) {
638
            sessionTracker.startTracking();
639
            fetchUserInfo();
640
            setButtonText();
641
        }
642
    }
643
 
644
    @Override
645
    protected void onDraw(Canvas canvas) {
646
        super.onDraw(canvas);
647
 
648
        if (!nuxChecked && nuxMode != ToolTipMode.NEVER_DISPLAY && !isInEditMode()) {
649
            nuxChecked = true;
650
            checkNuxSettings();
651
        }
652
    }
653
 
654
    private void showNuxPerSettings(FetchedAppSettings settings) {
655
        if (settings != null && settings.getNuxEnabled() && getVisibility() == View.VISIBLE) {
656
            String nuxString = settings.getNuxContent();
657
            displayNux(nuxString);
658
        }
659
    }
660
 
661
    private void displayNux(String nuxString) {
662
        nuxPopup = new ToolTipPopup(nuxString, this);
663
        nuxPopup.setStyle(nuxStyle);
664
        nuxPopup.setNuxDisplayTime(nuxDisplayTime);
665
        nuxPopup.show();
666
    }
667
 
668
    private void checkNuxSettings() {
669
        if (nuxMode == ToolTipMode.DISPLAY_ALWAYS) {
670
            String nuxString = getResources().getString(R.string.com_facebook_tooltip_default);
671
            displayNux(nuxString);
672
        } else {
673
            // kick off an async request
674
            final String appId = Utility.getMetadataApplicationId(getContext());
675
            AsyncTask<Void, Void, FetchedAppSettings> task = new AsyncTask<Void, Void, Utility.FetchedAppSettings>() {
676
                @Override
677
                protected FetchedAppSettings doInBackground(Void... params) {
678
                    FetchedAppSettings settings = Utility.queryAppSettings(appId, false);
679
                    return settings;
680
                }
681
 
682
                @Override
683
                protected void onPostExecute(FetchedAppSettings result) {
684
                    showNuxPerSettings(result);
685
                }
686
            };
687
            task.execute((Void[])null);
688
        }
689
 
690
    }
691
 
692
    @Override
693
    protected void onDetachedFromWindow() {
694
        super.onDetachedFromWindow();
695
        if (sessionTracker != null) {
696
            sessionTracker.stopTracking();
697
        }
698
        dismissToolTip();
699
    }
700
 
701
    @Override
702
    protected void onVisibilityChanged(View changedView, int visibility) {
703
        super.onVisibilityChanged(changedView, visibility);
704
        // If the visibility is not VISIBLE, we want to dismiss the nux if it is there
705
        if (visibility != VISIBLE) {
706
            dismissToolTip();
707
        }
708
    }
709
 
710
    // For testing purposes only
711
    List<String> getPermissions() {
712
        return properties.getPermissions();
713
    }
714
 
715
    void setProperties(LoginButtonProperties properties) {
716
        this.properties = properties;
717
    }
718
 
719
    void setLoginLogoutEventName(String eventName) {
720
        loginLogoutEventName = eventName;
721
    }
722
 
723
    private void parseAttributes(AttributeSet attrs) {
724
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.com_facebook_login_view);
725
        confirmLogout = a.getBoolean(R.styleable.com_facebook_login_view_confirm_logout, true);
726
        fetchUserInfo = a.getBoolean(R.styleable.com_facebook_login_view_fetch_user_info, true);
727
        loginText = a.getString(R.styleable.com_facebook_login_view_login_text);
728
        logoutText = a.getString(R.styleable.com_facebook_login_view_logout_text);
729
        a.recycle();
730
    }
731
 
732
    private void setButtonText() {
733
        if (sessionTracker != null && sessionTracker.getOpenSession() != null) {
734
            setText((logoutText != null) ? logoutText :
735
                    getResources().getString(R.string.com_facebook_loginview_log_out_button));
736
        } else {
737
            setText((loginText != null) ? loginText :
738
                    getResources().getString(R.string.com_facebook_loginview_log_in_button));
739
        }
740
    }
741
 
742
    private boolean initializeActiveSessionWithCachedToken(Context context) {
743
        if (context == null) {
744
            return false;
745
        }
746
 
747
        Session session = Session.getActiveSession();
748
        if (session != null) {
749
            return session.isOpened();
750
        }
751
 
752
        String applicationId = Utility.getMetadataApplicationId(context);
753
        if (applicationId == null) {
754
            return false;
755
        }
756
 
757
        return Session.openActiveSessionFromCache(context) != null;
758
    }
759
 
760
    private void fetchUserInfo() {
761
        if (fetchUserInfo) {
762
            final Session currentSession = sessionTracker.getOpenSession();
763
            if (currentSession != null) {
764
                if (currentSession != userInfoSession) {
765
                    Request request = Request.newMeRequest(currentSession, new Request.GraphUserCallback() {
766
                        @Override
767
                        public void onCompleted(GraphUser me,  Response response) {
768
                            if (currentSession == sessionTracker.getOpenSession()) {
769
                                user = me;
770
                                if (userInfoChangedCallback != null) {
771
                                    userInfoChangedCallback.onUserInfoFetched(user);
772
                                }
773
                            }
774
                            if (response.getError() != null) {
775
                                handleError(response.getError().getException());
776
                            }
777
                        }
778
                    });
779
                    Request.executeBatchAsync(request);
780
                    userInfoSession = currentSession;
781
                }
782
            } else {
783
                user = null;
784
                if (userInfoChangedCallback != null) {
785
                    userInfoChangedCallback.onUserInfoFetched(user);
786
                }
787
            }
788
        }
789
    }
790
 
791
    /**
792
     * Allow a developer to set the OnClickListener for the button.  This will be called back after we do any handling
793
     * internally for login
794
     * @param clickListener
795
     */
796
    @Override
797
    public void setOnClickListener(OnClickListener clickListener) {
798
        listenerCallback = clickListener;
799
    }
800
 
801
    private class LoginClickListener implements OnClickListener {
802
 
803
        @Override
804
        public void onClick(View v) {
805
            Context context = getContext();
806
            final Session openSession = sessionTracker.getOpenSession();
807
 
808
            if (openSession != null) {
809
                // If the Session is currently open, it must mean we need to log out
810
                if (confirmLogout) {
811
                    // Create a confirmation dialog
812
                    String logout = getResources().getString(R.string.com_facebook_loginview_log_out_action);
813
                    String cancel = getResources().getString(R.string.com_facebook_loginview_cancel_action);
814
                    String message;
815
                    if (user != null && user.getName() != null) {
816
                        message = String.format(getResources().getString(R.string.com_facebook_loginview_logged_in_as), user.getName());
817
                    } else {
818
                        message = getResources().getString(R.string.com_facebook_loginview_logged_in_using_facebook);
819
                    }
820
                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
821
                    builder.setMessage(message)
822
                           .setCancelable(true)
823
                           .setPositiveButton(logout, new DialogInterface.OnClickListener() {
824
                               public void onClick(DialogInterface dialog, int which) {
825
                                   openSession.closeAndClearTokenInformation();
826
                               }
827
                           })
828
                           .setNegativeButton(cancel, null);
829
                    builder.create().show();
830
                } else {
831
                    openSession.closeAndClearTokenInformation();
832
                }
833
            } else {
834
                Session currentSession = sessionTracker.getSession();
835
                if (currentSession == null || currentSession.getState().isClosed()) {
836
                    sessionTracker.setSession(null);
837
                    Session session = new Session.Builder(context).setApplicationId(applicationId).build();
838
                    Session.setActiveSession(session);
839
                    currentSession = session;
840
                }
841
                if (!currentSession.isOpened()) {
842
                    Session.OpenRequest openRequest = null;
843
                    if (parentFragment != null) {
844
                        openRequest = new Session.OpenRequest(parentFragment);
845
                    } else if (context instanceof Activity) {
846
                        openRequest = new Session.OpenRequest((Activity)context);
847
                    } else if (context instanceof ContextWrapper) {
848
                        Context baseContext = ((ContextWrapper)context).getBaseContext();
849
                        if (baseContext instanceof Activity) {
850
                            openRequest = new Session.OpenRequest((Activity)baseContext);
851
                        }
852
                    }
853
 
854
                    if (openRequest != null) {
855
                        openRequest.setDefaultAudience(properties.defaultAudience);
856
                        openRequest.setPermissions(properties.permissions);
857
                        openRequest.setLoginBehavior(properties.loginBehavior);
858
 
859
                        if (SessionAuthorizationType.PUBLISH.equals(properties.authorizationType)) {
860
                            currentSession.openForPublish(openRequest);
861
                        } else {
862
                            currentSession.openForRead(openRequest);
863
                        }
864
                    }
865
                }
866
            }
867
 
868
            AppEventsLogger logger = AppEventsLogger.newLogger(getContext());
869
 
870
            Bundle parameters = new Bundle();
871
            parameters.putInt("logging_in", (openSession != null) ? 0 : 1);
872
 
873
            logger.logSdkEvent(loginLogoutEventName, null, parameters);
874
 
875
            if (listenerCallback != null) {
876
                listenerCallback.onClick(v);
877
            }
878
        }
879
    }
880
 
881
    private class LoginButtonCallback implements Session.StatusCallback {
882
        @Override
883
        public void call(Session session, SessionState state,
884
                         Exception exception) {
885
            fetchUserInfo();
886
            setButtonText();
887
 
888
            // if the client has a status callback registered, call it, otherwise
889
            // call the default handleError method, but don't call both
890
            if (properties.sessionStatusCallback != null) {
891
                properties.sessionStatusCallback.call(session, state, exception);
892
            } else if (exception != null) {
893
                handleError(exception);
894
            }
895
        }
896
    };
897
 
898
    void handleError(Exception exception) {
899
        if (properties.onErrorListener != null) {
900
            if (exception instanceof FacebookException) {
901
                properties.onErrorListener.onError((FacebookException)exception);
902
            } else {
903
                properties.onErrorListener.onError(new FacebookException(exception));
904
            }
905
        }
906
    }
907
}