com.amazon.appstream.sampleclient.SampleClientActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.amazon.appstream.sampleclient.SampleClientActivity.java

Source

/*
 * Copyright 2013-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Amazon Software License (the "License"). You may not use
 * this file except in compliance with the License. A copy of the License is
 * located at
 *
 *      http://aws.amazon.com/asl/
 *
 * This Software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.amazon.appstream.sampleclient;

import java.util.Locale;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.Toast;

import com.amazon.appstream.AppStreamInterface;
import com.amazon.appstream.DesQuery;
import com.amazon.appstream.HardwareDecoder;
import com.amazon.appstream.KeyRemap;

/**
 * The Activity for the AppStream Example Client on Android.
 *
 * After creating the basic OpenGL surface and starting the
 * AppStream server, the main task of SampleClientActivity is to
 * siphon up all relevant events and send them to the native
 * layer.
 */
public class SampleClientActivity extends FragmentActivity
        implements ConnectDialogFragment.ConnectDialogListener, android.view.GestureDetector.OnGestureListener,
        DesQuery.DesQueryListener, AppStreamInterface.AppStreamListener {

    static {
        System.loadLibrary("stlport_shared");
        System.loadLibrary("avutil");
        System.loadLibrary("avcodec");
        System.loadLibrary("avformat");
        System.loadLibrary("swresample");
        System.loadLibrary("XStxClientLibraryShared");
        System.loadLibrary("appstreamsample");
    }

    private static final String TAG = "SampleClientActivity";

    private boolean mStopped = false;

    private GL2JNIView mGlView = null;

    private String mServerAddress = null;
    private String mDESServerAddress = null;
    private boolean mUseAppServer = false;
    private static final String USE_APP_SERVER = "use_app_server";
    private static final String DES_SERVER_ADDRESS = "des_server_address";
    private final String SERVER_ADDRESS = "server_address";
    private final String APP_ID = "appid";
    private final String USER_ID = "userid";

    private ImageButton mKeyboardToggle;
    private boolean mKeyboardActive = false;

    private GestureDetector mGestureDetector = null;
    private int mKeyboardOffset = 0;

    private FrameLayout mActivityRootView;

    private ConnectDialogFragment mConnectDialog = null;
    private boolean mTouchscreenAvailable;

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        Log.i(TAG, "onNewIntent");
    }

    /**
     * Initialization. Sets up the app and spawns the connection
     * dialog.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.v(TAG, "onCreate");
        mGestureDetector = new GestureDetector(this, this);

        mGestureDetector.setIsLongpressEnabled(false);

        mTouchscreenAvailable = getPackageManager().hasSystemFeature("android.hardware.touchscreen");
        Log.v(TAG, "Touch screen available: " + mTouchscreenAvailable);

        SharedPreferences prefs = getSharedPreferences("main", MODE_PRIVATE);
        if (prefs.contains(SERVER_ADDRESS)) {
            mServerAddress = prefs.getString(SERVER_ADDRESS, null);
        }
        if (prefs.contains(DES_SERVER_ADDRESS)) {
            mDESServerAddress = prefs.getString(DES_SERVER_ADDRESS, null);
        }
        if (prefs.contains(USE_APP_SERVER)) {
            mUseAppServer = prefs.getBoolean(USE_APP_SERVER, false);
        }
        if (prefs.contains(APP_ID)) {
            mAppId = prefs.getString(APP_ID, null);
        }
        if (prefs.contains(USER_ID)) {
            mUserId = prefs.getString(USER_ID, null);
        }

        requestWindowFeature(Window.FEATURE_NO_TITLE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.v(TAG, "onStart");

        AppStreamInterface.setListener(this);

        setContentView(R.layout.activity_sample_client);

        attemptEnableHardwareDecode();

        openConnectDialog(null);
    }

    HardwareDecoder mHardwareDecoder = null;

    private void attemptEnableHardwareDecode() {
        if (mHardwareDecoder == null) {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                Log.i(TAG, "JellyBean or higher: Using Hardware Decoder");
                try {
                    mHardwareDecoder = new HardwareDecoder("video/avc", 1280, 720);
                } catch (RuntimeException e) {
                    Log.w(TAG, "Never mind. Can't create hardware decoder: " + e.getMessage());
                }
            }
        }
        AppStreamInterface.setHardwareDecoder(mHardwareDecoder);
    }

    private void disableHardwareDecode() {
        AppStreamInterface.setHardwareDecoder(null);
    }

    boolean mFnVisible = false;
    boolean mArrowBarVisible = false;

    Runnable mClearFullscreen = new Runnable() {
        @Override
        public void run() {
            Log.i(TAG, "Clearing Fullscreen");
            if (getWindow() != null) {
                Log.v(TAG, "Clearing/setting window flags");
                getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
            }
            mActivityRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
        }
    };

    /**
     * Switch to the "game" view. Sets the content view to the one
     * for your game; in the example, the content view contains only
     * a keyboard icon.
     */
    public void showGame() {
        setContentView(R.layout.game);

        mActivityRootView = (FrameLayout) findViewById(R.id.outer_frame);

        // If we have a touchscreen, then configure all the buttons.
        if (mTouchscreenAvailable) {
            // This little bit of magic is required to detect when the user hides the keyboard.
            // Unfortunately, the setSystemUiVisibility() has a minimum requirement of Honeycomb (3.0/API11).
            // As such, we have raised the minimum API to 11.
            //
            // A workaround that you could try if you need API10 support is to NOT specify a fullscreen
            // theme for the app. The keyboard detection code below doesn't work when the theme is fullscreen.
            // In fact, you could request a non-fullscreen theme on API10, though you may have to also disable
            // requestWindowFeature(Window.FEATURE_NO_TITLE) in onCreate().
            mActivityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @SuppressLint("NewApi")
                @Override
                public void onGlobalLayout() {
                    int heightDiff = mActivityRootView.getRootView().getHeight() - mActivityRootView.getHeight();
                    if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
                        Log.v(TAG, "keyboard found");
                        mKeyboardActive = true;
                        mKeyboardOffset = 0;
                    } else if (mKeyboardActive) {
                        Log.v(TAG, "keyboard not found");
                        mKeyboardActive = false;
                        AppStreamInterface.setKeyboardOffset(0);
                        runOnUiThread(mClearFullscreen);
                    }
                }
            });

            mKeyboardToggle = (ImageButton) findViewById(R.id.keyboard_toggle);
            mKeyboardToggle.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!mKeyboardActive) {
                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
                        mActivityRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
                    } else {
                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
                        mActivityRootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
                    }

                    InputMethodManager imm = (InputMethodManager) SampleClientActivity.this
                            .getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
                    AppStreamInterface.setKeyboardOffset(0);
                }
            });
        }

        // Keep the screen on when we're visible.
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mGlView = new GL2JNIView(getApplication());
        mActivityRootView.addView(mGlView, 0);
    }

    /**
     * Open (or reopen) the connection dialog. This dialog will
     * collect the server information from the user and then call
     * the onDialogConnectClick() function to handle the result.
     *
     * If the result is an error, then this function is called AGAIN
     * to change the state of the dialog from "waiting" to "there
     * was an error."
     *
     * @param error An optional error string to display as part of
     *              the dialog.
     */
    public void openConnectDialog(String error) {

        if (mConnectDialog != null) {
            mConnectDialog.setAddressError(error);
            mConnectDialog.resetProgress();
            return;
        }

        ConnectDialogFragment dialog = new ConnectDialogFragment();
        dialog.setAddress(mServerAddress);
        dialog.setAppID(mAppId);
        dialog.setUserId(mUserId);
        dialog.setAddressError(error);
        dialog.setDESAddress(mDESServerAddress);
        dialog.setUseAppServer(mUseAppServer);
        if (mHardwareDecoder == null) {
            dialog.disableUseHardware();
        }
        dialog.show(getSupportFragmentManager(), "ConnectDialogFragment");

        mConnectDialog = dialog;
    }

    PointerCoords mCoordHolder = new PointerCoords();

    /**
     * A "touch event" includes mouse motion when the mouse button
     * is down.
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        if (mKeyboardActive) {
            if (mGestureDetector.onTouchEvent(event)) {
                return true;
            }
        }

        if (super.dispatchTouchEvent(event))
            return true;

        int flags = 0;
        if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
            flags = AppStreamInterface.CET_TOUCH_FLAG;
        }

        event.getPointerCoords(0, mCoordHolder);
        switch (event.getAction()) {
        case MotionEvent.ACTION_MOVE:
            AppStreamInterface.mouseEvent((int) mCoordHolder.x, (int) mCoordHolder.y, flags);
            break;
        case MotionEvent.ACTION_DOWN:
            AppStreamInterface.mouseEvent((int) mCoordHolder.x, (int) mCoordHolder.y,
                    AppStreamInterface.CET_MOUSE_1_DOWN | flags);
            break;
        case MotionEvent.ACTION_UP:
            AppStreamInterface.mouseEvent((int) mCoordHolder.x, (int) mCoordHolder.y,
                    AppStreamInterface.CET_MOUSE_1_UP | flags);
            break;
        }
        return true;
    }

    /**
     * A "generic motion event" includes joystick and mouse motion
     * when the mouse button isn't down. In our simple sample, we're
     * not handling the joystick, but this is where any such code
     * would live.
     *
     * This will only ever be called in HONEYCOMB_MR1 (12) or later, so I'm marking the
     * function using \@TargetApi to allow it to call the super.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent event) {
        if (event.getSource() == InputDevice.SOURCE_MOUSE) {
            event.getPointerCoords(0, mCoordHolder);
            switch (event.getAction()) {
            case MotionEvent.ACTION_HOVER_MOVE:
                AppStreamInterface.mouseEvent((int) mCoordHolder.x, (int) mCoordHolder.y, 0);
                break;

            default:
                return super.dispatchGenericMotionEvent(event);
            }
            return true;
        }
        return super.dispatchGenericMotionEvent(event);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mConnectDialog != null) {
            Log.v(TAG, "onPause: mConnectDialog was non-null");
            mConnectDialog.dismiss();
            mConnectDialog = null;
        }

        if (mGlView != null) {
            mGlView.onPause();
        }

        savePrefs();
        AppStreamInterface.pause(true);
    }

    @Override
    protected void onStop() {
        Log.i(TAG, "onStop");
        super.onStop();
        stopAppStream();
    }

    public void stopAppStream() {
        if (!mStopped) {
            mStopped = true;
            AppStreamInterface.stop();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        mStopped = false;
        if (mGlView != null) {
            mGlView.onResume();
        } else {
            openConnectDialog(null);
        }
        AppStreamInterface.pause(false);
    }

    /**
     * Translate from an onKeyDown() or onKeyUp() message to a
     * Windows virtual key code and send it to the NDK layer.
     *
     * @param[in] msg Message to translate.
     * @param[in] down True if a key down message; false otherwise.
     *
     * @see AppStreamWrapper::keyPress
     */
    public boolean onKey(KeyEvent msg, boolean down) {
        return KeyRemap.handleAndroidKey(msg, down);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {
        if (onKey(msg, true)) {
            return true; // don't call super; it can translate this key to something else
        }
        // We don't know what this key means, so go ahead and let the OS
        // translate it, in case the translated key *is* something we understand.
        return super.onKeyDown(keyCode, msg);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent msg) {
        if (onKey(msg, false)) {
            return true;
        }
        return super.onKeyUp(keyCode, msg);
    }

    private DesQuery mDesQuery = new DesQuery();

    private String mAppId;

    private String mUserId;

    @Override
    public void onDialogConnectClick(String address, String appid, String userid, boolean hardwareEnabled) {

        Resources r = getResources();

        if (hardwareEnabled) {
            attemptEnableHardwareDecode();
        } else {
            disableHardwareDecode();
        }

        mStopped = false;

        if (address == null || address.isEmpty()) {
            openConnectDialog(r.getString(R.string.no_address));
        } else if (appid == null) {
            if (!address.matches("\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}")) {
                openConnectDialog(r.getString(R.string.invalid_address));
                return;
            }

            mServerAddress = address;
            mUseAppServer = true;

            String url = String.format(Locale.US, "ssm://%s:%d?sessionId=%s", address, 80, "9070-0");
            AppStreamInterface.connect(url);
            AppStreamInterface.newFrame();

            savePrefs();
            return;
        } else if (address != null && address.matches(
                "^(http[s]?://)?([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(:[0-9]+)?(/.*)?$")
                || address.matches("^(http[s]?://)?\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}[.]\\d{1,3}(:[0-9]+)?(/.*)?$")) {
            if (userid == null || (userid.isEmpty())) {
                openConnectDialog(r.getString(R.string.user_id_required));
                return;
            } else if (appid.isEmpty()) {
                openConnectDialog(r.getString(R.string.app_id_required));
                return;
            }
            mUseAppServer = false;
            mDESServerAddress = address;
            mAppId = appid;
            mUserId = userid;
            savePrefs();

            // we've received an entitlement server + path
            mDesQuery.setActivity(this);
            mDesQuery.setListener(this);
            mDesQuery.makeQuery(address, appid, userid);
        } else {
            openConnectDialog(r.getString(R.string.invalid_address));
        }
    }

    private void savePrefs() {
        SharedPreferences prefs = getSharedPreferences("main", MODE_PRIVATE);
        Editor e = prefs.edit();
        if (mServerAddress != null) {
            e.putString(SERVER_ADDRESS, mServerAddress);
        }
        if (mDESServerAddress != null) {
            e.putString(DES_SERVER_ADDRESS, mDESServerAddress);
        }
        if (mUserId != null) {
            e.putString(USER_ID, mUserId);
        }
        if (mAppId != null) {
            e.putString(APP_ID, mAppId);
        }
        e.putBoolean(USE_APP_SERVER, mUseAppServer);
        e.apply();
    }

    @Override
    public void onDesQuerySuccess(String address) {
        AppStreamInterface.connect(address);
        AppStreamInterface.newFrame();
    }

    @Override
    public void onDesQueryFailure(String error) {
        // if we fail, try and try again...
        openConnectDialog(error);
    }

    @Override
    public void onConnectSuccess() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (mConnectDialog != null) {
                    mConnectDialog.dismiss();
                    mConnectDialog = null;
                }
                showGame();
            }
        });
    }

    @Override
    public void onErrorMessage(final boolean fatal, final String message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (mStopped) {
                    Log.i(TAG, "Ignoring error during stopped state :" + (fatal ? "fatal" : "non fatal") + ":"
                            + message);
                    return; // ignore errors if we're stopped.
                }
                if (fatal) {
                    // Tell the app it needs to pause.
                    AppStreamInterface.pause(true);
                    ErrorDialogFragment dialog = new ErrorDialogFragment();
                    dialog.setMessage(message);
                    dialog.show(getSupportFragmentManager(), "ErrorDialogFragment");

                    // And finally stop AppStream; kill the interfaces to give us a clean slate.
                    stopAppStream();
                } else {
                    if (mConnectDialog != null) {
                        openConnectDialog(message);
                    } else {
                        Toast toast = Toast.makeText(SampleClientActivity.this, message, Toast.LENGTH_LONG);
                        toast.setGravity(Gravity.RIGHT | Gravity.BOTTOM, 10, 10);
                        toast.show();
                    }
                }
            }
        });
    }

    /**
     * Request an OpenGL frame.
     */
    @Override
    public void newFrame() {
        if (mGlView != null) {
            mGlView.requestRender();
        }
    }

    @Override
    public void onReconnecting(final String message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (message != null) {
                    openConnectDialog("");
                    mConnectDialog.setReconnectMessage(message);
                } else {
                    if (mConnectDialog != null) {
                        mConnectDialog.dismiss();
                        mConnectDialog = null;
                    }
                }
            }
        });
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        stopAppStream();
    }

    // GestureDetector interface methods. We only use onScroll, but all
    // must be implemented.
    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        if (mKeyboardActive) {
            mKeyboardOffset += (int) distanceY;
            mKeyboardOffset = Math.min(Math.max(0, mKeyboardOffset), mGlView.getHeightDelta());
            AppStreamInterface.setKeyboardOffset(mKeyboardOffset);
            return true;
        }
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }

}