com.senior.chromecastcheckers.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.senior.chromecastcheckers.MainActivity.java

Source

/*
 * Copyright (C) 2014 Google Inc. All Rights Reserved. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

//Some of this code was borrowed from an example file on GitHub called
//CastHelloText-Android, it was a simple application that had the Google
//Chromecast protocols almost in place for what we needed them for. So,
//the file was edited to suit our needs and our buttons / functions were
//added.

package com.senior.chromecastcheckers;

import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
import com.google.android.gms.cast.Cast.MessageReceivedCallback;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.MediaRouteActionProvider;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import org.json.JSONException;
import org.json.JSONObject;

//Main class that handles the user input AKA pressing the buttons
//and then constructing the JSON messages and handing them down to
//the functions that actually transmit the message to the Chromecast.
public class MainActivity extends ActionBarActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private static final int REQUEST_CODE = 1;

    private MediaRouter mMediaRouter;
    private MediaRouteSelector mMediaRouteSelector;
    private MediaRouter.Callback mMediaRouterCallback;
    private CastDevice mSelectedDevice;
    private GoogleApiClient mApiClient;
    private Cast.Listener mCastListener;
    private ConnectionCallbacks mConnectionCallbacks;
    private ConnectionFailedListener mConnectionFailedListener;
    private HelloWorldChannel mHelloWorldChannel;
    private boolean mApplicationStarted;
    private boolean mWaitingForReconnect;
    private String mSessionId;
    //The variables above were declared by the open source code / Google Code.

    //These were the variables we declared/used the most.
    private int UserID;
    //Gives the player a unique UserID, used so that not more than 2 players can join the game
    //and the receiver can tell which player is which so it knows who to block out and who to let
    //move.

    private int PlayerNum;
    //Tells the Android app which player it is based on what the receiver sends back in the JSON
    //message (1 or 2).

    private boolean PlayerTurn;
    //Tells the Android app which player's turn it is based on what the receiver sends back in the
    //JSON message (True/False).

    private String GameStatus;
    //Tells the Android app what the game status is based on what the receiver sends back in the
    //JSON message (active/finished).

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Set random UserID identifier
        Random rand = new Random();
        UserID = rand.nextInt(1000);

        ActionBar actionBar = getSupportActionBar();
        actionBar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(android.R.color.transparent)));

        // Configure Cast device discovery
        mMediaRouter = MediaRouter.getInstance(getApplicationContext());
        mMediaRouteSelector = new MediaRouteSelector.Builder().addControlCategory(
                CastMediaControlIntent.categoryForCast(getResources().getString(R.string.app_id))).build();
        mMediaRouterCallback = new MyMediaRouterCallback();
    }

    //This is linked to the Register button on the main screen
    //when the button is pressed this function is called and sends
    //two messages to the receiver app to make sure the user is
    //registered. Sends it's unique UserID.
    public void registerPlayer(View view) {
        JSONObject message = new JSONObject();

        try {
            message.put("UserID", UserID + "");
            message.put("commandtype", "register");
            message.put("direction", "none");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        sendMessage(message.toString());
        sendMessage(message.toString());
    }

    //These functions we unfortunately could not get to work, they were
    //supposed to be functions to restart the entire game and refresh the
    //receiver app to replay the game.

    /*
    public void restartGame()
    {
    JSONObject message = new JSONObject();
    try {
        message.put("UserID", UserID+"");
        message.put("commandtype", "restart");
        message.put("direction", "none");
    } catch (JSONException e) {
        e.printStackTrace();
    }
    sendMessage(message.toString());
    }
        
    public void promptForRestart()
    {
    new AlertDialog.Builder(this)
            .setIcon(R.drawable.ic_launcher)
            .setTitle("Play again?")
            .setMessage("The game is over, would you like to play again?")
            .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    restartGame();
                }
            })
            .setNegativeButton("No", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    teardown(true);
                }
            })
            .show();
    }
    */

    //This function is the main button that gets called whenever a button is clicked.
    //It is linked to nearly every button except for the register button.
    public void buttonClicked(View view) {
        JSONObject message = new JSONObject();
        if (view == findViewById(R.id.upButton)) {
            try {
                message.put("UserID", UserID + "");
                message.put("commandtype", "move");
                message.put("direction", "up");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            sendMessage(message.toString());

        }
        //When the button is clicked it checks through a series of if-elseif statements
        //which button was clicked. Then constructs the JSON message accordingly and then
        //sends it to the receiver app using the sendMessage function.
        else if (view == findViewById(R.id.downButton)) {
            try {
                message.put("UserID", UserID + "");
                message.put("commandtype", "move");
                message.put("direction", "down");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            sendMessage(message.toString());
        } else if (view == findViewById(R.id.leftButton)) {
            try {
                message.put("UserID", UserID + "");
                message.put("commandtype", "move");
                message.put("direction", "left");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            sendMessage(message.toString());
        } else if (view == findViewById(R.id.rightButton)) {
            try {
                message.put("UserID", UserID + "");
                message.put("commandtype", "move");
                message.put("direction", "right");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            sendMessage(message.toString());
        } else if (view == findViewById(R.id.selectButton)) {
            try {
                message.put("UserID", UserID + "");
                message.put("commandtype", "select");
                message.put("direction", "none");
            } catch (JSONException e) {
                e.printStackTrace();
            }
            sendMessage(message.toString());
        } else if (view == findViewById(R.id.forfeitButton)) {

            try {
                message.put("UserID", UserID + "");
                message.put("commandtype", "forfeit");
                message.put("direction", "none");
            } catch (JSONException e) {
                e.printStackTrace();
            }

            sendMessage(message.toString());

            //This function is the same as every other button function except for
            //when it is called, the app waits 6 seconds, and then calls the teardown
            //function which destroys the connection to the receiver app and shuts it down.

            //The waiting is to let the players clearly see that one of them has won the game.
            try {
                Thread.sleep(6000);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
            teardown(true);
        }
    }

    //This 3 functions were a part of the downloaded code, so the code that is
    //commented out was either to make what we wanted it to do work or to not use
    //whatever feature they had implemented.
    @Override
    protected void onStart() {
        super.onStart();
        // Start media router discovery
        mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
    }

    @Override
    protected void onStop() {
        // End media router discovery
        //mMediaRouter.removeCallback(mMediaRouterCallback);
        super.onStop();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        //teardown(true);
        super.onDestroy();
    }

    //This is code to, when the top menu bar generates it shows the Chromecast
    //logo so that you can activate it and start casting to a Chromecast.
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        super.onCreateOptionsMenu(menu);

        getMenuInflater().inflate(R.menu.main, menu);

        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);

        MediaRouteActionProvider mediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat
                .getActionProvider(mediaRouteMenuItem);

        // Set the MediaRouteActionProvider selector for device discovery.
        mediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);

        return true;
    }

    //Class that handles communication events between the receiver and sender.
    private class MyMediaRouterCallback extends MediaRouter.Callback {

        @Override
        public void onRouteSelected(MediaRouter router, RouteInfo info) {
            // Handle the user route selection.
            mSelectedDevice = CastDevice.getFromBundle(info.getExtras());

            launchReceiver();
        }

        //When the route is unselected it sets the device it is casting to to null.
        @Override
        public void onRouteUnselected(MediaRouter router, RouteInfo info) {
            Log.d(TAG, "onRouteUnselected: info=" + info);
            //teardown(false);
            mSelectedDevice = null;
        }
    }

    //Function to start the receiver app. All code provided from Google.
    //Utilizes the Chromecast API.
    private void launchReceiver() {
        try {
            mCastListener = new Cast.Listener() {

                @Override
                public void onApplicationDisconnected(int errorCode) {
                    Log.d(TAG, "application has stopped");
                    //teardown(true);
                }

            };
            // Connect to Google Play services
            mConnectionCallbacks = new ConnectionCallbacks();

            mConnectionFailedListener = new ConnectionFailedListener();

            Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(mSelectedDevice, mCastListener);

            mApiClient = new GoogleApiClient.Builder(this).addApi(Cast.API, apiOptionsBuilder.build())
                    .addConnectionCallbacks(mConnectionCallbacks)
                    .addOnConnectionFailedListener(mConnectionFailedListener).build();

            mApiClient.connect();
        } catch (Exception e) {
            Log.e(TAG, "Failed launchReceiver", e);
        }
    }

    //Handles the Google Play service callbacks.
    private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks {

        @Override
        public void onConnected(Bundle connectionHint) {
            Log.d(TAG, "onConnected");

            if (mApiClient == null) {
                // We got disconnected while this runnable was pending
                // execution.
                return;
            }

            try {
                if (mWaitingForReconnect) {
                    mWaitingForReconnect = false;

                    // Check if the receiver app is still running
                    if ((connectionHint != null) && connectionHint.getBoolean(Cast.EXTRA_APP_NO_LONGER_RUNNING)) {
                        Log.d(TAG, "App  is no longer running");
                        //teardown(true);
                    } else {
                        // Re-create the custom message channel
                        try {
                            Cast.CastApi.setMessageReceivedCallbacks(mApiClient, mHelloWorldChannel.getNamespace(),
                                    mHelloWorldChannel);
                        } catch (IOException e) {
                            Log.e(TAG, "Exception while creating channel", e);
                        }
                    }
                } else {
                    //Launching the receiver app if there are no problems.
                    //Google provided code, all we had to do was enter the Application ID.
                    Cast.CastApi.launchApplication(mApiClient, getString(R.string.app_id), false)
                            .setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() {
                                @Override
                                public void onResult(ApplicationConnectionResult result) {
                                    Status status = result.getStatus();
                                    Log.d(TAG, "ApplicationConnectionResultCallback.onResult:"
                                            + status.getStatusCode());
                                    if (status.isSuccess()) {
                                        ApplicationMetadata applicationMetadata = result.getApplicationMetadata();
                                        mSessionId = result.getSessionId();
                                        String applicationStatus = result.getApplicationStatus();
                                        boolean wasLaunched = result.getWasLaunched();
                                        Log.d(TAG,
                                                "application name: " + applicationMetadata.getName() + ", status: "
                                                        + applicationStatus + ", sessionId: " + mSessionId
                                                        + ", wasLaunched: " + wasLaunched);
                                        mApplicationStarted = true;

                                        //Custom channel creation, left the name as HelloWorld
                                        //because it doesn't matter what it's called.
                                        mHelloWorldChannel = new HelloWorldChannel();
                                        try {
                                            Cast.CastApi.setMessageReceivedCallbacks(mApiClient,
                                                    mHelloWorldChannel.getNamespace(), mHelloWorldChannel);
                                        } catch (IOException e) {
                                            Log.e(TAG, "Exception while creating channel", e);
                                        }
                                    } else {
                                        Log.e(TAG, "application could not launch");
                                        //teardown(true);
                                    }
                                }
                            });
                }
            } catch (Exception e) {
                Log.e(TAG, "Failed to launch application", e);
            }
        }

        //When the connection is suspended it always waits for the player to reconnect.
        @Override
        public void onConnectionSuspended(int cause) {
            Log.d(TAG, "onConnectionSuspended");
            mWaitingForReconnect = true;
        }
    }

    /**
     * Google Play services callbacks
     */
    private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener {

        @Override
        public void onConnectionFailed(ConnectionResult result) {
            Log.e(TAG, "onConnectionFailed ");

            //teardown(false);
        }
    }

    //Function that tears down the connection to the receiver by setting the channel,
    //ApiClient, SelectedDevice, and SessionId to null. Also sets WaitingForReconnect to false.
    private void teardown(boolean selectDefaultRoute) {
        Log.d(TAG, "teardown");
        if (mApiClient != null) {
            if (mApplicationStarted) {
                if (mApiClient.isConnected() || mApiClient.isConnecting()) {
                    try {
                        Cast.CastApi.stopApplication(mApiClient, mSessionId);
                        if (mHelloWorldChannel != null) {
                            Cast.CastApi.removeMessageReceivedCallbacks(mApiClient,
                                    mHelloWorldChannel.getNamespace());
                            mHelloWorldChannel = null;
                        }
                    } catch (IOException e) {
                        Log.e(TAG, "Exception while removing channel", e);
                    }
                    mApiClient.disconnect();
                }
                mApplicationStarted = false;
            }
            mApiClient = null;
        }
        if (selectDefaultRoute) {
            mMediaRouter.selectRoute(mMediaRouter.getDefaultRoute());
        }
        mSelectedDevice = null;
        mWaitingForReconnect = false;
        mSessionId = null;
    }

    //Function to send a string message to the receiver app.
    private void sendMessage(String message) {
        if (mApiClient != null && mHelloWorldChannel != null) {
            try {
                Cast.CastApi.sendMessage(mApiClient, mHelloWorldChannel.getNamespace(), message)
                        .setResultCallback(new ResultCallback<Status>() {
                            @Override
                            public void onResult(Status result) {
                                if (!result.isSuccess()) {
                                    Log.e(TAG, "Sending message failed");
                                }
                            }
                        });
            } catch (Exception e) {
                Log.e(TAG, "Exception while sending message", e);
            }
        } else {
            //Show the command that failed to send to the user on the screen if the Chromecast
            //is not connected or there is an error sending the message.
            Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
        }
    }

    //Custom message/channel class. Mostly handles messages sent back and forth.
    class HelloWorldChannel implements MessageReceivedCallback {

        public String getNamespace() {
            return getString(R.string.namespace);
        }

        //Gets called when the sender receives a message from the receiver.
        //This is the main portion that was implemented for our game and
        //player control.
        @Override
        public void onMessageReceived(CastDevice castDevice, String namespace, String message) {

            //Since the first thing you press is Register, after the sender receives
            //it's first message, we hide the Register button because it is of no use anymore.
            Button Register = (Button) findViewById(R.id.registerButton);
            Register.setVisibility(View.INVISIBLE);

            //What this does is it takes the string message that was received from the receiver
            //app that is formatted in JSON and turns it into an actual JSON object, that way
            //we can actually extract the information we want from it.
            try {
                JSONObject jObject = new JSONObject(message);
                PlayerNum = jObject.getInt("player");
                PlayerTurn = jObject.getBoolean("turn");
                GameStatus = jObject.getString("gamestatus");
            } catch (JSONException e) {
                e.printStackTrace();
            }

            //This was for error testing, only logs out in the debugger so the user does
            //not see this.
            Log.d("Info: ", "Player = " + PlayerNum);
            Log.d("Info: ", "Turn = " + PlayerTurn);
            Log.d("Info: ", "Game Status = " + GameStatus);

            //Then call the handleReceived function to build the message that is to be sent back.
            handleReceived();
        }

        public void handleReceived() {
            //To actually edit the text views and buttons on the screen you have to identify
            //them programatically.

            TextView playeridText = (TextView) findViewById(R.id.playeridText);
            TextView turnView = (TextView) findViewById(R.id.turnView);

            ImageButton Left = (ImageButton) findViewById(R.id.leftButton);
            ImageButton Right = (ImageButton) findViewById(R.id.rightButton);
            ImageButton Up = (ImageButton) findViewById(R.id.upButton);
            ImageButton Down = (ImageButton) findViewById(R.id.downButton);

            Button Select = (Button) findViewById(R.id.selectButton);
            Button Forfeit = (Button) findViewById(R.id.forfeitButton);

            playeridText.setText("Player " + PlayerNum);

            //Unimplemented code that was going to be used to implement a restart function.
            /*
            if (PlayerNum == 1 && GameStatus.equals("finished"))
            {
            promptForRestart();
            }
            */

            //If it is Player 1's turn, and then GameStatus is finished,
            //wait 6 seconds and then end them game.
            if (PlayerNum == 1 && GameStatus.equals("finished")) {
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
                teardown(true);
            }

            //If PlayerTurn == True, which means if it's that player's turn
            //Set the text of the turn textview to "Your turn" and make all
            //buttons clickable, if not, make the buttons unclickable and make
            //the textview say waiting for turn.
            if (PlayerTurn) {
                turnView.setText("Your turn");
                Left.setClickable(true);
                Right.setClickable(true);
                Up.setClickable(true);
                Down.setClickable(true);
                Select.setClickable(true);
                Forfeit.setClickable(true);
            } else {
                turnView.setText("Waiting for turn");
                Left.setClickable(false);
                Right.setClickable(false);
                Up.setClickable(false);
                Down.setClickable(false);
                Select.setClickable(false);
                Forfeit.setClickable(false);
            }

        }

    }

}