/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
* copy, modify, and distribute this software in source code or binary form for use
* in connection with the web services and APIs provided by Facebook.
*
* As with any software that integrates with the Facebook platform, your use of
* this software is subject to the Facebook Developer Principles and Policies
* [http://developers.facebook.com/policy/]. This copyright notice shall be
* included in all copies or substantial portions of the software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.facebook;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import com.facebook.FacebookException;
import com.facebook.FacebookSdk;
import com.facebook.R;
import com.facebook.appevents.AppEventsLogger;
/**
* A base class for a facebook button.
*/
public abstract class FacebookButtonBase extends Button {
private String analyticsButtonCreatedEventName;
private OnClickListener externalOnClickListener;
private OnClickListener internalOnClickListener;
private boolean overrideCompoundPadding;
private int overrideCompoundPaddingLeft;
private int overrideCompoundPaddingRight;
private Fragment parentFragment;
private int requestCode;
protected FacebookButtonBase(
final Context context,
final AttributeSet attrs,
int defStyleAttr,
int defStyleRes,
final String analyticsButtonCreatedEventName,
final int requestCode) {
super(context, attrs, 0);
defStyleRes = (defStyleRes == 0 ? this.getDefaultStyleResource() : defStyleRes);
defStyleRes = (defStyleRes == 0 ? R.style.com_facebook_button : defStyleRes);
configureButton(context, attrs, defStyleAttr, defStyleRes);
this.analyticsButtonCreatedEventName = analyticsButtonCreatedEventName;
this.requestCode = requestCode;
}
/**
* Sets the fragment that contains this control. This allows the button to be embedded inside a
* Fragment, and will allow the fragment to receive the
* {@link Fragment#onActivityResult(int, int, android.content.Intent) onActivityResult}
* call rather than the Activity.
*
* @param fragment the fragment that contains this control
*/
public void setFragment(final Fragment fragment) {
parentFragment = fragment;
}
/**
* Gets the fragment that contains this control.
* @return The fragment that contains this control.
*/
public Fragment getFragment() {
return parentFragment;
}
@Override
public void setOnClickListener(final OnClickListener l) {
this.externalOnClickListener = l;
}
/**
* Set the request code for the startActivityForResult call. The requestCode should be
* outside of the range of those reserved for the Facebook SDK
* {@link com.facebook.FacebookSdk#isFacebookRequestCode(int)}. This method should also be
* called prior to registering any callbacks.
*
* @param requestCode the request code to use.
*/
protected void setRequestCode(final int requestCode) {
if (FacebookSdk.isFacebookRequestCode(requestCode)) {
throw new IllegalArgumentException("Request code " + requestCode +
" cannot be within the range reserved by the Facebook SDK.");
}
this.requestCode = requestCode;
}
/**
* Returns the request code used for this Button.
*
* @return the request code.
*/
public int getRequestCode() {
return requestCode;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
logButtonCreated(getContext());
}
@Override
protected void onDraw(Canvas canvas) {
boolean centered = (this.getGravity() & Gravity.CENTER_HORIZONTAL) != 0;
if (centered) {
// if the text is centered, we need to adjust the frame for the titleLabel based on the
// size of the text in order to keep the text centered in the button without adding
// extra blank space to the right when unnecessary
// 1. the text fits centered within the button without colliding with the image
// (imagePaddingWidth)
// 2. the text would run into the image, so adjust the insets to effectively left align
// it (textPaddingWidth)
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int compoundDrawablePadding = getCompoundDrawablePadding();
final int textX = compoundPaddingLeft + compoundDrawablePadding;
final int textContentWidth = getWidth() - textX - compoundPaddingRight;
final int textWidth = measureTextWidth(getText().toString());
final int textPaddingWidth = (textContentWidth - textWidth) / 2;
final int imagePaddingWidth = (compoundPaddingLeft - getPaddingLeft()) / 2;
final int inset = Math.min(textPaddingWidth, imagePaddingWidth);
this.overrideCompoundPaddingLeft = compoundPaddingLeft - inset;
this.overrideCompoundPaddingRight = compoundPaddingRight + inset;
this.overrideCompoundPadding = true;
}
super.onDraw(canvas);
this.overrideCompoundPadding = false;
}
@Override
public int getCompoundPaddingLeft() {
return (this.overrideCompoundPadding ?
this.overrideCompoundPaddingLeft :
super.getCompoundPaddingLeft());
}
@Override
public int getCompoundPaddingRight() {
return (this.overrideCompoundPadding ?
this.overrideCompoundPaddingRight :
super.getCompoundPaddingRight());
}
protected Activity getActivity() {
final Context context = getContext();
if (context instanceof Activity) {
return (Activity) context;
} else if (context instanceof ContextWrapper) {
Context baseContext = ((ContextWrapper) context).getBaseContext();
if (baseContext instanceof Activity) {
return (Activity) baseContext;
}
}
throw new FacebookException("Unable to get Activity.");
}
protected int getDefaultStyleResource() {
return 0;
}
protected int measureTextWidth(final String text) {
return (int)Math.ceil(getPaint().measureText(text));
}
protected void configureButton(
final Context context,
final AttributeSet attrs,
final int defStyleAttr,
final int defStyleRes) {
parseBackgroundAttributes(context, attrs, defStyleAttr, defStyleRes);
parseCompoundDrawableAttributes(context, attrs, defStyleAttr, defStyleRes);
parseContentAttributes(context, attrs, defStyleAttr, defStyleRes);
parseTextAttributes(context, attrs, defStyleAttr, defStyleRes);
setupOnClickListener();
}
protected void callExternalOnClickListener(final View v) {
if (externalOnClickListener != null) {
externalOnClickListener.onClick(v);
}
}
protected void setInternalOnClickListener(final OnClickListener l) {
internalOnClickListener = l;
}
private void logButtonCreated(final Context context) {
AppEventsLogger logger = AppEventsLogger.newLogger(context);
logger.logSdkEvent(analyticsButtonCreatedEventName, null, null);
}
private void parseBackgroundAttributes(
final Context context,
final AttributeSet attrs,
final int defStyleAttr,
final int defStyleRes) {
final int attrsResources[] = {
android.R.attr.background,
};
final TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
attrsResources,
defStyleAttr,
defStyleRes);
try {
if (a.hasValue(0)) {
int backgroundResource = a.getResourceId(0, 0);
if (backgroundResource != 0) {
setBackgroundResource(backgroundResource);
} else {
setBackgroundColor(a.getColor(0, 0));
}
} else {
// fallback, if no background specified, fill with Facebook blue
setBackgroundColor(a.getColor(0, R.color.com_facebook_blue));
}
} finally {
a.recycle();
}
}
private void parseCompoundDrawableAttributes(
final Context context,
final AttributeSet attrs,
final int defStyleAttr,
final int defStyleRes) {
final int attrsResources[] = {
android.R.attr.drawableLeft,
android.R.attr.drawableTop,
android.R.attr.drawableRight,
android.R.attr.drawableBottom,
android.R.attr.drawablePadding,
};
final TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
attrsResources,
defStyleAttr,
defStyleRes);
try {
setCompoundDrawablesWithIntrinsicBounds(
a.getResourceId(0, 0),
a.getResourceId(1, 0),
a.getResourceId(2, 0),
a.getResourceId(3, 0));
setCompoundDrawablePadding(a.getDimensionPixelSize(4, 0));
} finally {
a.recycle();
}
}
private void parseContentAttributes(
final Context context,
final AttributeSet attrs,
final int defStyleAttr,
final int defStyleRes) {
final int attrsResources[] = {
android.R.attr.paddingLeft,
android.R.attr.paddingTop,
android.R.attr.paddingRight,
android.R.attr.paddingBottom,
};
final TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
attrsResources,
defStyleAttr,
defStyleRes);
try {
setPadding(
a.getDimensionPixelSize(0, 0),
a.getDimensionPixelSize(1, 0),
a.getDimensionPixelSize(2, 0),
a.getDimensionPixelSize(3, 0));
} finally {
a.recycle();
}
}
private void parseTextAttributes(
final Context context,
final AttributeSet attrs,
final int defStyleAttr,
final int defStyleRes) {
final int colorResources[] = {
android.R.attr.textColor,
};
final TypedArray colorAttrs = context.getTheme().obtainStyledAttributes(
attrs,
colorResources,
defStyleAttr,
defStyleRes);
try {
setTextColor(colorAttrs.getColor(0, Color.WHITE));
} finally {
colorAttrs.recycle();
}
final int gravityResources[] = {
android.R.attr.gravity,
};
final TypedArray gravityAttrs = context.getTheme().obtainStyledAttributes(
attrs,
gravityResources,
defStyleAttr,
defStyleRes);
try {
setGravity(gravityAttrs.getInt(0, Gravity.CENTER));
} finally {
gravityAttrs.recycle();
}
final int attrsResources[] = {
android.R.attr.textSize,
android.R.attr.textStyle,
android.R.attr.text,
};
final TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
attrsResources,
defStyleAttr,
defStyleRes);
try {
setTextSize(TypedValue.COMPLEX_UNIT_PX, a.getDimensionPixelSize(0, 0));
setTypeface(Typeface.defaultFromStyle(a.getInt(1, Typeface.BOLD)));
setText(a.getString(2));
} finally {
a.recycle();
}
}
private void setupOnClickListener() {
// set the listener on super so that consumers can set another listener that this will
// forward to
super.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
if (FacebookButtonBase.this.internalOnClickListener != null) {
FacebookButtonBase.this.internalOnClickListener.onClick(v);
} else if (FacebookButtonBase.this.externalOnClickListener != null) {
FacebookButtonBase.this.externalOnClickListener.onClick(v);
}
}
});
}
}