/** * 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.example.rps; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.*; import com.facebook.*; import com.facebook.login.DefaultAudience; import com.facebook.login.LoginManager; import com.facebook.login.LoginResult; import com.facebook.share.ShareApi; import com.facebook.share.Sharer; import com.facebook.share.model.ShareContent; import com.facebook.share.model.ShareLinkContent; import com.facebook.share.model.ShareOpenGraphAction; import com.facebook.share.model.ShareOpenGraphContent; import com.facebook.share.model.ShareOpenGraphObject; import com.facebook.share.model.SharePhoto; import com.facebook.share.model.AppInviteContent; import com.facebook.share.widget.AppInviteDialog; import com.facebook.share.widget.MessageDialog; import com.facebook.share.widget.ShareDialog; import java.util.ArrayList; import java.util.Arrays; import java.util.Random; import static com.example.rps.RpsGameUtils.*; public class RpsFragment extends Fragment { private static final String SHARE_GAME_LINK = "https://developers.facebook.com/docs/android"; private static final String SHARE_GAME_NAME = "Rock, Papers, Scissors Sample Application"; private static final String DEFAULT_GAME_OBJECT_TITLE = "an awesome game of Rock, Paper, Scissors"; private static final String WIN_KEY = "wins"; private static final String LOSS_KEY = "losses"; private static final String TIE_KEY = "ties"; private static final String PLAYER_CHOICE_KEY = "player_choice"; private static final String COMPUTER_CHOICE_KEY = "computer_choice"; private static final String STATE_KEY = "state"; private static final String RESULT_KEY = "result"; private static final String PENDING_PUBLISH_KEY = "pending_publish"; private static final String IMPLICIT_PUBLISH_KEY = "implicitly_publish"; private static final String ADDITIONAL_PERMISSIONS = "publish_actions"; private static final int INITIAL_DELAY_MILLIS = 500; private static final int DEFAULT_DELAY_MILLIS = 1000; private static final String TAG = RpsFragment.class.getName(); private static String[] PHOTO_URIS = {null, null, null}; private TextView[] gestureTextViews = new TextView[3]; private TextView shootTextView; private ImageView playerChoiceView; private ImageView computerChoiceView; private TextView resultTextView; private ViewGroup shootGroup; private ViewGroup resultGroup; private ViewGroup playerChoiceGroup; private Button againButton; private ImageButton[] gestureImages = new ImageButton[3]; private ImageButton fbButton; private TextView statsTextView; private ViewFlipper rpsFlipper; private int wins = 0; private int losses = 0; private int ties = 0; private int playerChoice = INVALID_CHOICE; private int computerChoice = INVALID_CHOICE; private RpsState currentState = RpsState.INIT; private RpsResult result = RpsResult.INVALID; private InitHandler handler = new InitHandler(); private Random random = new Random(System.currentTimeMillis()); private boolean pendingPublish; private boolean shouldImplicitlyPublish = true; private CallbackManager callbackManager; private ShareDialog shareDialog; private MessageDialog messageDialog; private AppInviteDialog appInviteDialog; private DialogInterface.OnClickListener canPublishClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { if (AccessToken.getCurrentAccessToken() != null) { // if they choose to publish, then we request for publish permissions shouldImplicitlyPublish = true; pendingPublish = true; LoginManager.getInstance() .setDefaultAudience(DefaultAudience.FRIENDS) .logInWithPublishPermissions( RpsFragment.this, Arrays.asList(ADDITIONAL_PERMISSIONS)); } } }; private DialogInterface.OnClickListener dontPublishClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // if they choose not to publish, then we save that choice, and don't prompt them // until they restart the app pendingPublish = false; shouldImplicitlyPublish = false; } }; private class InitHandler extends Handler { @Override public void handleMessage(Message msg) { if (!isResumed()) { // if we're not in between onResume and onPause, don't do animation transitions return; } switch (msg.what) { case ROCK: showViews(gestureTextViews[ROCK], gestureImages[ROCK]); sendNextMessage(PAPER); break; case PAPER: showViews(gestureTextViews[PAPER], gestureImages[PAPER]); sendNextMessage(SCISSORS); break; case SCISSORS: showViews(gestureTextViews[SCISSORS], gestureImages[SCISSORS]); sendNextMessage(SHOOT); break; case SHOOT: showViews(shootTextView); switchState(RpsState.PLAYING, false); break; default: Log.e(TAG, "Unexpected message received: " + msg.toString()); break; } } private void sendNextMessage(int what) { Message newMsg = new Message(); newMsg.what = what; sendMessageDelayed(newMsg, DEFAULT_DELAY_MILLIS); } } private void switchState(RpsState newState, boolean isOnResume) { if (!isResumed()) { // if we're not in between onResume and onPause, don't transition states return; } switch (newState) { case INIT: playerChoice = INVALID_CHOICE; computerChoice = INVALID_CHOICE; result = RpsResult.INVALID; showViews(shootGroup, playerChoiceGroup, rpsFlipper); rpsFlipper.startFlipping(); hideViews(gestureImages); hideViews(gestureTextViews); hideViews(resultGroup, shootTextView, againButton); enableViews(false, gestureImages); enableViews(false, againButton); Message initMessage = new Message(); initMessage.what = ROCK; handler.sendMessageDelayed(initMessage, INITIAL_DELAY_MILLIS); break; case PLAYING: enableViews(true, gestureImages); showViews(rpsFlipper); rpsFlipper.startFlipping(); break; case RESULT: hideViews(shootGroup, playerChoiceGroup); playerChoiceView.setImageResource(DRAWABLES_HUMAN[playerChoice]); computerChoiceView.setImageResource(DRAWABLES_COMPUTER[computerChoice]); resultTextView.setText(result.getStringId()); showViews(resultGroup, againButton); enableViews(true, againButton); if (!isOnResume) { // don't publish if we're switching states because onResumed is being called publishResult(); } break; default: Log.e(TAG, "Unexpected state reached: " + newState.toString()); break; } String statsFormat = getResources().getString(R.string.stats_format); statsTextView.setText(String.format(statsFormat, wins, losses, ties)); currentState = newState; } private void hideViews(View... views) { for (View view : views) { view.setVisibility(View.INVISIBLE); } } private void showViews(View... views) { for (View view : views) { view.setVisibility(View.VISIBLE); } } private void enableViews(boolean enabled, View... views) { for (View view : views) { view.setEnabled(enabled); } } private void playerPlayed(int choice) { playerChoice = choice; computerChoice = getComputerChoice(); result = RESULTS[playerChoice][computerChoice]; switch (result) { case WIN: wins++; break; case LOSS: losses++; break; case TIE: ties++; break; default: Log.e(TAG, "Unexpected result: " + result.toString()); break; } switchState(RpsState.RESULT, false); } private int getComputerChoice() { return random.nextInt(3); } private boolean canPublish() { final AccessToken accessToken = AccessToken.getCurrentAccessToken(); if (accessToken != null) { if (accessToken.getPermissions().contains(ADDITIONAL_PERMISSIONS)) { // if we already have publish permissions, then go ahead and publish return true; } else { // otherwise we ask the user if they'd like to publish to facebook new AlertDialog.Builder(getActivity()) .setTitle(R.string.share_with_friends_title) .setMessage(R.string.share_with_friends_message) .setPositiveButton(R.string.share_with_friends_yes, canPublishClickListener) .setNegativeButton(R.string.share_with_friends_no, dontPublishClickListener) .show(); return false; } } return false; } private void showError(int messageId) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.error_dialog_title). setMessage(messageId). setPositiveButton(R.string.error_ok_button, null); builder.show(); } private void publishResult() { if (shouldImplicitlyPublish && canPublish()) { String photoUri = PHOTO_URIS[playerChoice]; SharePhoto.Builder sharePhotoBuilder = new SharePhoto.Builder(); if (photoUri == null) { Bitmap bitmap = BitmapFactory.decodeResource( getResources(), DRAWABLES_HUMAN[playerChoice]); sharePhotoBuilder.setBitmap(bitmap); } else { sharePhotoBuilder.setImageUrl(Uri.parse(photoUri)); } sharePhotoBuilder.setUserGenerated(false); final SharePhoto gesturePhoto = sharePhotoBuilder.build(); ShareOpenGraphObject gameObject = createGameObject(gesturePhoto); ShareOpenGraphAction playAction = createPlayActionWithGame(gameObject); ShareOpenGraphContent content = new ShareOpenGraphContent.Builder() .setAction(playAction) .setPreviewPropertyName("game") .build(); ShareApi.share(content, new FacebookCallback<Sharer.Result>() { @Override public void onSuccess(Sharer.Result result) { Log.i(TAG, "Posted OG Action with id: " + result.getPostId()); } @Override public void onCancel() { // This should not happen } @Override public void onError(FacebookException error) { Log.e(TAG, "Play action creation failed: " + error.getMessage()); } }); } } private ShareOpenGraphObject createGameObject(final SharePhoto gesturePhoto) { return new ShareOpenGraphObject.Builder() .putString("og:title", DEFAULT_GAME_OBJECT_TITLE) .putString("og:type", "fb_sample_rps:game") .putString("fb_sample_rps:player_gesture", CommonObjects.BUILT_IN_OPEN_GRAPH_OBJECTS[playerChoice]) .putString("fb_sample_rps:opponent_gesture", CommonObjects.BUILT_IN_OPEN_GRAPH_OBJECTS[computerChoice]) .putString("fb_sample_rps:result", getString(result.getResultStringId())) .putPhotoArrayList("og:image", new ArrayList<SharePhoto>() {{ add(gesturePhoto); }}) .build(); } private ShareOpenGraphAction createPlayActionWithGame(ShareOpenGraphObject game) { return new ShareOpenGraphAction.Builder() .setActionType(OpenGraphConsts.PLAY_ACTION_TYPE) .putObject("game", game).build(); } private String getBuiltInGesture(int choice) { if (choice < 0 || choice >= CommonObjects.BUILT_IN_OPEN_GRAPH_OBJECTS.length) { throw new IllegalArgumentException("Invalid choice"); } return CommonObjects.BUILT_IN_OPEN_GRAPH_OBJECTS[choice]; } private ShareOpenGraphAction getThrowAction() { // The OG objects have their own bitmaps we could rely on, but in order to demonstrate // attaching an in-memory bitmap (e.g., a game screencap) we'll send the bitmap explicitly // ourselves. ImageButton view = gestureImages[playerChoice]; BitmapDrawable drawable = (BitmapDrawable) view.getBackground(); final Bitmap bitmap = drawable.getBitmap(); return new ShareOpenGraphAction.Builder() .setActionType(OpenGraphConsts.THROW_ACTION_TYPE) .putString("fb_sample_rps:gesture", getBuiltInGesture(playerChoice)) .putString("fb_sample_rps:opposing_gesture", getBuiltInGesture(computerChoice)) .putPhotoArrayList("og:image", new ArrayList<SharePhoto>() {{ add(new SharePhoto.Builder().setBitmap(bitmap).build()); }}) .build(); } private ShareOpenGraphContent getThrowActionContent() { return new ShareOpenGraphContent.Builder() .setAction(getThrowAction()) .setPreviewPropertyName(OpenGraphConsts.THROW_ACTION_PREVIEW_PROPERTY_NAME) .build(); } private ShareLinkContent getLinkContent() { return new ShareLinkContent.Builder() .setContentTitle(SHARE_GAME_NAME) .setContentUrl(Uri.parse(SHARE_GAME_LINK)) .build(); } public void shareUsingNativeDialog() { if (playerChoice == INVALID_CHOICE || computerChoice == INVALID_CHOICE) { ShareContent content = getLinkContent(); // share the app if (shareDialog.canShow(content, ShareDialog.Mode.NATIVE)) { shareDialog.show(content, ShareDialog.Mode.NATIVE); } else { showError(R.string.native_share_error); } } else { ShareContent content = getThrowActionContent(); if (shareDialog.canShow(content, ShareDialog.Mode.NATIVE)) { shareDialog.show(content, ShareDialog.Mode.NATIVE); } else { showError(R.string.native_share_error); } } } public void shareUsingMessengerDialog() { if (playerChoice == INVALID_CHOICE || computerChoice == INVALID_CHOICE) { ShareContent content = getLinkContent(); // share the app if (messageDialog.canShow(content)) { messageDialog.show(content); } } else { ShareContent content = getThrowActionContent(); if (messageDialog.canShow(content)) { messageDialog.show(content); } } } public void presentAppInviteDialog() { AppInviteContent content = new AppInviteContent.Builder() .setApplinkUrl("https://d3uu10x6fsg06w.cloudfront.net/hosting-rps/applink.html") .setPreviewImageUrl("https://d3uu10x6fsg06w.cloudfront.net/hosting-rps/rps-preview-image.jpg") .build(); if (AppInviteDialog.canShow()) { appInviteDialog.show(this, content); } else { showError(R.string.appinvite_error); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View view = inflater.inflate(R.layout.rps_fragment, container, false); gestureTextViews[ROCK] = (TextView) view.findViewById(R.id.text_rock); gestureTextViews[PAPER] = (TextView) view.findViewById(R.id.text_paper); gestureTextViews[SCISSORS] = (TextView) view.findViewById(R.id.text_scissors); shootTextView = (TextView) view.findViewById(R.id.shoot); playerChoiceView = (ImageView) view.findViewById(R.id.player_choice); computerChoiceView = (ImageView) view.findViewById(R.id.computer_choice); resultTextView = (TextView) view.findViewById(R.id.who_won); shootGroup = (ViewGroup) view.findViewById(R.id.shoot_display_group); resultGroup = (ViewGroup) view.findViewById(R.id.result_display_group); playerChoiceGroup = (ViewGroup) view.findViewById(R.id.player_choice_display_group); againButton = (Button) view.findViewById(R.id.again_button); gestureImages[ROCK] = (ImageButton) view.findViewById(R.id.player_rock); gestureImages[PAPER] = (ImageButton) view.findViewById(R.id.player_paper); gestureImages[SCISSORS] = (ImageButton) view.findViewById(R.id.player_scissors); fbButton = (ImageButton) view.findViewById(R.id.facebook_button); statsTextView = (TextView) view.findViewById(R.id.stats); rpsFlipper = (ViewFlipper) view.findViewById(R.id.rps_flipper); gestureImages[ROCK].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { playerPlayed(ROCK); } }); gestureImages[PAPER].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { playerPlayed(PAPER); } }); gestureImages[SCISSORS].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { playerPlayed(SCISSORS); } }); againButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { switchState(RpsState.INIT, false); } }); fbButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getActivity().openOptionsMenu(); } }); return view; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { wins = savedInstanceState.getInt(WIN_KEY); losses = savedInstanceState.getInt(LOSS_KEY); ties = savedInstanceState.getInt(TIE_KEY); computerChoice = savedInstanceState.getInt(COMPUTER_CHOICE_KEY); playerChoice = savedInstanceState.getInt(PLAYER_CHOICE_KEY); currentState = (RpsState) savedInstanceState.getSerializable(STATE_KEY); result = (RpsResult) savedInstanceState.getSerializable(RESULT_KEY); pendingPublish = savedInstanceState.getBoolean(PENDING_PUBLISH_KEY); shouldImplicitlyPublish = savedInstanceState.getBoolean(IMPLICIT_PUBLISH_KEY); } callbackManager = CallbackManager.Factory.create(); LoginManager.getInstance().registerCallback( callbackManager, new FacebookCallback<LoginResult>() { @Override public void onSuccess(LoginResult loginResult) { AccessToken accessToken = AccessToken.getCurrentAccessToken(); if (accessToken.getPermissions().contains(ADDITIONAL_PERMISSIONS)) { publishResult(); } else { handleError(); } } @Override public void onCancel() { handleError(); } @Override public void onError(FacebookException exception) { handleError(); } private void handleError() { // this means the user did not grant us write permissions, so // we don't do implicit publishes shouldImplicitlyPublish = false; pendingPublish = false; } } ); FacebookCallback<Sharer.Result> callback = new FacebookCallback<Sharer.Result>() { @Override public void onCancel() { Log.d(TAG, "Canceled"); } @Override public void onError(FacebookException error) { Log.d(TAG, String.format("Error: %s", error.toString())); } @Override public void onSuccess(Sharer.Result result) { Log.d(TAG, "Success!"); } }; shareDialog = new ShareDialog(this); shareDialog.registerCallback(callbackManager, callback); messageDialog = new MessageDialog(this); messageDialog.registerCallback(callbackManager, callback); FacebookCallback<AppInviteDialog.Result> appInviteCallback = new FacebookCallback<AppInviteDialog.Result>() { @Override public void onSuccess(AppInviteDialog.Result result) { Log.d(TAG, "Success!"); } @Override public void onCancel() { Log.d(TAG, "Canceled"); } @Override public void onError(FacebookException error) { Log.d(TAG, String.format("Error: %s", error.toString())); } }; appInviteDialog = new AppInviteDialog(this); appInviteDialog.registerCallback(callbackManager, appInviteCallback); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { callbackManager.onActivityResult(requestCode, resultCode, data); } @Override public void onResume() { super.onResume(); switchState(currentState, true); } @Override public void onSaveInstanceState(Bundle bundle) { super.onSaveInstanceState(bundle); bundle.putInt(WIN_KEY, wins); bundle.putInt(LOSS_KEY, losses); bundle.putInt(TIE_KEY, ties); bundle.putInt(COMPUTER_CHOICE_KEY, computerChoice); bundle.putInt(PLAYER_CHOICE_KEY, playerChoice); bundle.putSerializable(STATE_KEY, currentState); bundle.putSerializable(RESULT_KEY, result); bundle.putBoolean(PENDING_PUBLISH_KEY, pendingPublish); bundle.putBoolean(IMPLICIT_PUBLISH_KEY, shouldImplicitlyPublish); } }