Home | History | Annotate | Download | only in visualgamecontroller
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      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.example.android.visualgamecontroller;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.Activity;
     21 import android.content.Context;
     22 import android.hardware.input.InputManager;
     23 import android.hardware.input.InputManager.InputDeviceListener;
     24 import android.os.Build;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.util.Log;
     28 import android.view.InputDevice;
     29 import android.view.KeyEvent;
     30 import android.view.MotionEvent;
     31 import android.view.View;
     32 import android.view.WindowManager;
     33 
     34 import com.example.android.visualgamecontroller.util.SystemUiHider;
     35 
     36 import java.util.ArrayList;
     37 
     38 /**
     39  * An example full-screen activity that shows and hides the system UI (i.e.
     40  * status bar and navigation/system bar) with user interaction.
     41  *
     42  * @see SystemUiHider
     43  */
     44 public class FullscreenActivity extends Activity implements InputDeviceListener {
     45     private static final String TAG = "FullscreenActivity";
     46 
     47     /**
     48      * Whether or not the system UI should be auto-hidden after
     49      * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
     50      */
     51     private static final boolean AUTO_HIDE = true;
     52 
     53     /**
     54      * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
     55      * user interaction before hiding the system UI.
     56      */
     57     private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
     58 
     59     /**
     60      * If set, will toggle the system UI visibility upon interaction. Otherwise,
     61      * will show the system UI visibility upon interaction.
     62      */
     63     private static final boolean TOGGLE_ON_CLICK = true;
     64 
     65     /**
     66      * The flags to pass to {@link SystemUiHider#getInstance}.
     67      */
     68     private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
     69 
     70     /**
     71      * The instance of the {@link SystemUiHider} for this activity.
     72      */
     73     private SystemUiHider mSystemUiHider;
     74 
     75     private ControllerView mControllerView;
     76 
     77     public enum ButtonMapping {
     78         BUTTON_A(KeyEvent.KEYCODE_BUTTON_A),
     79         BUTTON_B(KeyEvent.KEYCODE_BUTTON_B),
     80         BUTTON_X(KeyEvent.KEYCODE_BUTTON_X),
     81         BUTTON_Y(KeyEvent.KEYCODE_BUTTON_Y),
     82         BUTTON_L1(KeyEvent.KEYCODE_BUTTON_L1),
     83         BUTTON_R1(KeyEvent.KEYCODE_BUTTON_R1),
     84         BUTTON_L2(KeyEvent.KEYCODE_BUTTON_L2),
     85         BUTTON_R2(KeyEvent.KEYCODE_BUTTON_R2),
     86         BUTTON_SELECT(KeyEvent.KEYCODE_BUTTON_SELECT),
     87         BUTTON_START(KeyEvent.KEYCODE_BUTTON_START),
     88         BUTTON_THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL),
     89         BUTTON_THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR),
     90         BACK(KeyEvent.KEYCODE_BACK),
     91         POWER(KeyEvent.KEYCODE_BUTTON_MODE);
     92 
     93         private final int mKeyCode;
     94 
     95         ButtonMapping(int keyCode) {
     96             mKeyCode = keyCode;
     97         }
     98 
     99         private int getKeycode() {
    100             return mKeyCode;
    101         }
    102     }
    103 
    104     public enum AxesMapping {
    105         AXIS_X(MotionEvent.AXIS_X),
    106         AXIS_Y(MotionEvent.AXIS_Y),
    107         AXIS_Z(MotionEvent.AXIS_Z),
    108         AXIS_RZ(MotionEvent.AXIS_RZ),
    109         AXIS_HAT_X(MotionEvent.AXIS_HAT_X),
    110         AXIS_HAT_Y(MotionEvent.AXIS_HAT_Y),
    111         AXIS_LTRIGGER(MotionEvent.AXIS_LTRIGGER),
    112         AXIS_RTRIGGER(MotionEvent.AXIS_RTRIGGER),
    113         AXIS_BRAKE(MotionEvent.AXIS_BRAKE),
    114         AXIS_GAS(MotionEvent.AXIS_GAS);
    115 
    116         private final int mMotionEvent;
    117 
    118         AxesMapping(int motionEvent) {
    119             mMotionEvent = motionEvent;
    120         }
    121 
    122         private int getMotionEvent() {
    123             return mMotionEvent;
    124         }
    125     }
    126 
    127     private int[] mButtons = new int[ButtonMapping.values().length];
    128     private float[] mAxes = new float[AxesMapping.values().length];
    129     private InputManager mInputManager;
    130     private ArrayList<Integer> mConnectedDevices = new ArrayList<Integer>();
    131     private int mCurrentDeviceId = -1;
    132 
    133     @Override
    134     protected void onCreate(Bundle savedInstanceState) {
    135         super.onCreate(savedInstanceState);
    136         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    137         setContentView(R.layout.activity_fullscreen);
    138 
    139         final View controlsView = findViewById(R.id.fullscreen_content_controls);
    140         final View contentView = findViewById(R.id.fullscreen_content);
    141 
    142         mControllerView = (ControllerView) findViewById(R.id.controller);
    143         for (int i = 0; i < mButtons.length; i++) {
    144             mButtons[i] = 0;
    145         }
    146         for (int i = 0; i < mAxes.length; i++) {
    147             mAxes[i] = 0.0f;
    148         }
    149         mControllerView.setButtonsAxes(mButtons, mAxes);
    150 
    151         // Set up an instance of SystemUiHider to control the system UI for
    152         // this activity.
    153         mSystemUiHider = SystemUiHider.getInstance(this, contentView, HIDER_FLAGS);
    154         mSystemUiHider.setup();
    155         mSystemUiHider
    156                 .setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener() {
    157                     // Cached values.
    158                     int mControlsHeight;
    159                     int mShortAnimTime;
    160 
    161                     @Override
    162                     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    163                     public void onVisibilityChange(boolean visible) {
    164                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
    165                             // If the ViewPropertyAnimator API is available
    166                             // (Honeycomb MR2 and later), use it to animate the
    167                             // in-layout UI controls at the bottom of the
    168                             // screen.
    169                             if (mControlsHeight == 0) {
    170                                 mControlsHeight = controlsView.getHeight();
    171                             }
    172                             if (mShortAnimTime == 0) {
    173                                 mShortAnimTime = getResources().getInteger(
    174                                         android.R.integer.config_shortAnimTime);
    175                             }
    176                             controlsView.animate()
    177                                     .translationY(visible ? 0 : mControlsHeight)
    178                                     .setDuration(mShortAnimTime);
    179                         } else {
    180                             // If the ViewPropertyAnimator APIs aren't
    181                             // available, simply show or hide the in-layout UI
    182                             // controls.
    183                             controlsView.setVisibility(visible ? View.VISIBLE : View.GONE);
    184                         }
    185 
    186                         if (visible && AUTO_HIDE) {
    187                             // Schedule a hide().
    188                             delayedHide(AUTO_HIDE_DELAY_MILLIS);
    189                         }
    190                     }
    191                 });
    192 
    193         // Set up the user interaction to manually show or hide the system UI.
    194         contentView.setOnClickListener(new View.OnClickListener() {
    195             @Override
    196             public void onClick(View view) {
    197                 if (TOGGLE_ON_CLICK) {
    198                     mSystemUiHider.toggle();
    199                 } else {
    200                     mSystemUiHider.show();
    201                 }
    202             }
    203         });
    204 
    205         mInputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
    206         checkGameControllers();
    207     }
    208 
    209     /**
    210      * Check for any game controllers that are connected already.
    211      */
    212     private void checkGameControllers() {
    213         Log.d(TAG, "checkGameControllers");
    214         int[] deviceIds = mInputManager.getInputDeviceIds();
    215         for (int deviceId : deviceIds) {
    216             InputDevice dev = InputDevice.getDevice(deviceId);
    217             int sources = dev.getSources();
    218 
    219             // Verify that the device has gamepad buttons, control sticks, or
    220             // both.
    221             if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
    222                     || ((sources & InputDevice.SOURCE_JOYSTICK)
    223                         == InputDevice.SOURCE_JOYSTICK)) {
    224                 // This device is a game controller. Store its device ID.
    225                 if (!mConnectedDevices.contains(deviceId)) {
    226                     mConnectedDevices.add(deviceId);
    227                     if (mCurrentDeviceId == -1) {
    228                         mCurrentDeviceId = deviceId;
    229                         mControllerView.setCurrentControllerNumber(dev.getControllerNumber());
    230                         mControllerView.invalidate();
    231                     }
    232                 }
    233             }
    234         }
    235     }
    236 
    237     @Override
    238     protected void onPostCreate(Bundle savedInstanceState) {
    239         super.onPostCreate(savedInstanceState);
    240 
    241         // Trigger the initial hide() shortly after the activity has been
    242         // created, to briefly hint to the user that UI controls
    243         // are available.
    244         delayedHide(100);
    245     }
    246 
    247     @Override
    248     protected void onResume() {
    249         super.onResume();
    250         mInputManager.registerInputDeviceListener(this, null);
    251     }
    252 
    253     @Override
    254     protected void onPause() {
    255         super.onPause();
    256         mInputManager.unregisterInputDeviceListener(this);
    257     }
    258 
    259     /**
    260      * Touch listener to use for in-layout UI controls to delay hiding the
    261      * system UI. This is to prevent the jarring behavior of controls going away
    262      * while interacting with activity UI.
    263      */
    264     View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
    265         @Override
    266         public boolean onTouch(View view, MotionEvent motionEvent) {
    267             if (AUTO_HIDE) {
    268                 delayedHide(AUTO_HIDE_DELAY_MILLIS);
    269             }
    270             return false;
    271         }
    272     };
    273 
    274     Handler mHideHandler = new Handler();
    275     Runnable mHideRunnable = new Runnable() {
    276         @Override
    277         public void run() {
    278             mSystemUiHider.hide();
    279         }
    280     };
    281 
    282     /**
    283      * Schedules a call to hide() in [delay] milliseconds, canceling any
    284      * previously scheduled calls.
    285      */
    286     private void delayedHide(int delayMillis) {
    287         mHideHandler.removeCallbacks(mHideRunnable);
    288         mHideHandler.postDelayed(mHideRunnable, delayMillis);
    289     }
    290 
    291     /*
    292      * (non-Javadoc)
    293      * @see android.app.Activity#onGenericMotionEvent(android.view.MotionEvent)
    294      */
    295     @Override
    296     public boolean onGenericMotionEvent(final MotionEvent ev) {
    297         // Log.d(TAG, "onGenericMotionEvent: " + ev);
    298         InputDevice device = ev.getDevice();
    299         // Only care about game controllers.
    300         if (device != null && device.getId() == mCurrentDeviceId) {
    301             if (isGamepad(device)) {
    302                 for (AxesMapping axesMapping : AxesMapping.values()) {
    303                     mAxes[axesMapping.ordinal()] = getCenteredAxis(ev, device,
    304                             axesMapping.getMotionEvent());
    305                 }
    306                 mControllerView.invalidate();
    307                 return true;
    308             }
    309         }
    310         return super.onGenericMotionEvent(ev);
    311     }
    312 
    313     /**
    314      * Get centered position for axis input by considering flat area.
    315      *
    316      * @param event
    317      * @param device
    318      * @param axis
    319      * @return
    320      */
    321     private float getCenteredAxis(MotionEvent event, InputDevice device, int axis) {
    322         InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
    323 
    324         // A joystick at rest does not always report an absolute position of
    325         // (0,0). Use the getFlat() method to determine the range of values
    326         // bounding the joystick axis center.
    327         if (range != null) {
    328             float flat = range.getFlat();
    329             float value = event.getAxisValue(axis);
    330 
    331             // Ignore axis values that are within the 'flat' region of the
    332             // joystick axis center.
    333             if (Math.abs(value) > flat) {
    334                 return value;
    335             }
    336         }
    337         return 0;
    338     }
    339 
    340     /*
    341      * (non-Javadoc)
    342      * @see android.support.v4.app.FragmentActivity#onKeyDown(int,
    343      * android.view.KeyEvent)
    344      */
    345     @Override
    346     public boolean onKeyDown(final int keyCode, KeyEvent ev) {
    347         // Log.d(TAG, "onKeyDown: " + ev);
    348         InputDevice device = ev.getDevice();
    349         // Only care about game controllers.
    350         if (device != null && device.getId() == mCurrentDeviceId) {
    351             if (isGamepad(device)) {
    352                 int index = getButtonMappingIndex(keyCode);
    353                 if (index >= 0) {
    354                     mButtons[index] = 1;
    355                     mControllerView.invalidate();
    356                 }
    357                 return true;
    358             }
    359         }
    360         return super.onKeyDown(keyCode, ev);
    361     }
    362 
    363     /*
    364      * (non-Javadoc)
    365      * @see android.app.Activity#onKeyUp(int, android.view.KeyEvent)
    366      */
    367     @Override
    368     public boolean onKeyUp(final int keyCode, KeyEvent ev) {
    369         // Log.d(TAG, "onKeyUp: " + ev);
    370         InputDevice device = ev.getDevice();
    371         // Only care about game controllers.
    372         if (device != null && device.getId() == mCurrentDeviceId) {
    373             if (isGamepad(device)) {
    374                 int index = getButtonMappingIndex(keyCode);
    375                 if (index >= 0) {
    376                     mButtons[index] = 0;
    377                     mControllerView.invalidate();
    378                 }
    379                 return true;
    380             }
    381         }
    382         return super.onKeyUp(keyCode, ev);
    383     }
    384 
    385     /**
    386      * Utility method to determine if input device is a gamepad.
    387      *
    388      * @param device
    389      * @return
    390      */
    391     private boolean isGamepad(InputDevice device) {
    392         if ((device.getSources() &
    393                 InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
    394                 || (device.getSources() &
    395                 InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) {
    396             return true;
    397         }
    398         return false;
    399     }
    400 
    401     /**
    402      * Get the array index for the key code.
    403      *
    404      * @param keyCode
    405      * @return
    406      */
    407     private int getButtonMappingIndex(int keyCode) {
    408         for (ButtonMapping buttonMapping : ButtonMapping.values()) {
    409             if (buttonMapping.getKeycode() == keyCode) {
    410                 return buttonMapping.ordinal();
    411             }
    412         }
    413         return -1;
    414     }
    415 
    416     /*
    417      * (non-Javadoc)
    418      * @see
    419      * android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded
    420      * (int)
    421      */
    422     @Override
    423     public void onInputDeviceAdded(int deviceId) {
    424         Log.d(TAG, "onInputDeviceAdded: " + deviceId);
    425         if (!mConnectedDevices.contains(deviceId)) {
    426             mConnectedDevices.add(new Integer(deviceId));
    427         }
    428         if (mCurrentDeviceId == -1) {
    429             mCurrentDeviceId = deviceId;
    430             InputDevice dev = InputDevice.getDevice(mCurrentDeviceId);
    431             if (dev != null) {
    432                 mControllerView.setCurrentControllerNumber(dev.getControllerNumber());
    433                 mControllerView.invalidate();
    434             }
    435         }
    436     }
    437 
    438     /*
    439      * (non-Javadoc)
    440      * @see
    441      * android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved
    442      * (int)
    443      */
    444     @Override
    445     public void onInputDeviceRemoved(int deviceId) {
    446         Log.d(TAG, "onInputDeviceRemoved: " + deviceId);
    447         mConnectedDevices.remove(new Integer(deviceId));
    448         if (mCurrentDeviceId == deviceId) {
    449             mCurrentDeviceId = -1;
    450         }
    451         if (mConnectedDevices.size() == 0) {
    452             mControllerView.setCurrentControllerNumber(-1);
    453             mControllerView.invalidate();
    454         } else {
    455             mCurrentDeviceId = mConnectedDevices.get(0);
    456             InputDevice dev = InputDevice.getDevice(mCurrentDeviceId);
    457             if (dev != null) {
    458                 mControllerView.setCurrentControllerNumber(dev.getControllerNumber());
    459                 mControllerView.invalidate();
    460             }
    461         }
    462     }
    463 
    464     /*
    465      * (non-Javadoc)
    466      * @see
    467      * android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged
    468      * (int)
    469      */
    470     @Override
    471     public void onInputDeviceChanged(int deviceId) {
    472         Log.d(TAG, "onInputDeviceChanged: " + deviceId);
    473         mControllerView.invalidate();
    474     }
    475 
    476 }
    477