package com.henrikrydgard.libnative; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.util.List; import java.util.Locale; import java.util.UUID; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Point; import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Vibrator; import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.InputDevice; import android.view.InputEvent; import android.view.KeyEvent; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.View.OnSystemUiVisibilityChangeListener; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.Toast; public class NativeActivity extends Activity { // Remember to loadLibrary your JNI .so in a static {} block // Adjust these as necessary private static String TAG = "NativeActivity"; // Allows us to skip a lot of initialization on secondary calls to onCreate. private static boolean initialized = false; // Graphics and audio interfaces private NativeGLView mGLSurfaceView; private NativeAudioPlayer audioPlayer; protected NativeRenderer nativeRenderer; private String shortcutParam = ""; public static String runCommand; public static String commandParameter; public static String installID; // Remember settings for best audio latency private int optimalFramesPerBuffer; private int optimalSampleRate; // audioFocusChangeListener to listen to changes in audio state private AudioFocusChangeListener audioFocusChangeListener; private AudioManager audioManager; private Vibrator vibrator; private boolean isXperiaPlay; // Allow for two connected gamepads but just consider them the same for now. // Actually this is not entirely true, see the code. InputDeviceState inputPlayerA; InputDeviceState inputPlayerB; InputDeviceState inputPlayerC; String inputPlayerADesc; // Functions for the app activity to override to change behaviour. public boolean useLowProfileButtons() { return true; } NativeRenderer getRenderer() { return nativeRenderer; } @TargetApi(17) private void detectOptimalAudioSettings() { try { optimalFramesPerBuffer = Integer.parseInt(this.audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)); } catch (NumberFormatException e) { // Ignore, if we can't parse it it's bogus and zero is a fine value (means we couldn't detect it). } try { optimalSampleRate = Integer.parseInt(this.audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)); } catch (NumberFormatException e) { // Ignore, if we can't parse it it's bogus and zero is a fine value (means we couldn't detect it). } } String getApplicationLibraryDir(ApplicationInfo application) { String libdir = null; try { // Starting from Android 2.3, nativeLibraryDir is available: Field field = ApplicationInfo.class.getField("nativeLibraryDir"); libdir = (String) field.get(application); } catch (SecurityException e1) { } catch (NoSuchFieldException e1) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } if (libdir == null) { // Fallback for Android < 2.3: libdir = application.dataDir + "/lib"; } return libdir; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) void GetScreenSizeJB(Point size, boolean real) { WindowManager w = getWindowManager(); if (real) { w.getDefaultDisplay().getRealSize(size); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) void GetScreenSizeHC(Point size, boolean real) { WindowManager w = getWindowManager(); if (real && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { GetScreenSizeJB(size, real); } else { w.getDefaultDisplay().getSize(size); } } @SuppressWarnings("deprecation") public void GetScreenSize(Point size) { boolean real = useImmersive(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { GetScreenSizeHC(size, real); } else { WindowManager w = getWindowManager(); Display d = w.getDefaultDisplay(); size.x = d.getWidth(); size.y = d.getHeight(); } } public void setShortcutParam(String shortcutParam) { this.shortcutParam = ((shortcutParam == null) ? "" : shortcutParam); } private boolean useOpenSL() { // Native OpenSL became available on Gingerbread. Let's use it! return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD); } public void Initialize() { // Initialize audio classes. Do this here since detectOptimalAudioSettings() // needs audioManager this.audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); this.audioFocusChangeListener = new AudioFocusChangeListener(); if (Build.VERSION.SDK_INT >= 17) { // Get the optimal buffer sz detectOptimalAudioSettings(); } // isLandscape is used to trigger GetAppInfo currently, we boolean landscape = NativeApp.isLandscape(); Log.d(TAG, "Landscape: " + landscape); // Get system information ApplicationInfo appInfo = null; PackageManager packMgmr = getPackageManager(); String packageName = getPackageName(); try { appInfo = packMgmr.getApplicationInfo(packageName, 0); } catch (NameNotFoundException e) { e.printStackTrace(); throw new RuntimeException("Unable to locate assets, aborting..."); } isXperiaPlay = IsXperiaPlay(); String libraryDir = getApplicationLibraryDir(appInfo); File sdcard = Environment.getExternalStorageDirectory(); String externalStorageDir = sdcard.getAbsolutePath(); String dataDir = this.getFilesDir().getAbsolutePath(); String apkFilePath = appInfo.sourceDir; String deviceType = Build.MANUFACTURER + ":" + Build.MODEL; String languageRegion = Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry(); Point displaySize = new Point(); GetScreenSize(displaySize); NativeApp.audioConfig(optimalFramesPerBuffer, optimalSampleRate); NativeApp.init(deviceType, displaySize.x, displaySize.y, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, shortcutParam, installID, useOpenSL(), Build.VERSION.SDK_INT); // OK, config should be initialized, we can query for screen rotation. if (Build.VERSION.SDK_INT >= 9) { updateScreenRotation(); } // Detect OpenGL support. // We don't currently use this detection for anything but good to have in the log. if (!detectOpenGLES20()) { Log.i(TAG, "OpenGL ES 2.0 NOT detected. Things will likely go badly."); } else { if (detectOpenGLES30()) { Log.i(TAG, "OpenGL ES 3.0 detected."); } else { Log.i(TAG, "OpenGL ES 2.0 detected."); } } vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE); if (Build.VERSION.SDK_INT >= 11) { checkForVibrator(); } } @TargetApi(9) private void updateScreenRotation() { // Query the native application on the desired rotation. int rot = 0; String rotString = NativeApp.queryConfig("screenRotation"); try { rot = Integer.parseInt(rotString); } catch (NumberFormatException e) { Log.e(TAG, "Invalid rotation: " + rotString); return; } switch (rot) { case 0: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); break; case 1: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); break; case 2: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); break; case 3: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); break; case 4: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT); break; } } private boolean useImmersive() { String immersive = NativeApp.queryConfig("immersiveMode"); return immersive.equals("1") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; } @SuppressLint("InlinedApi") @TargetApi(14) private void updateSystemUiVisibility() { int flags = 0; if (useLowProfileButtons()) { flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE; } if (useImmersive()) { flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } if (getWindow().getDecorView() != null) { getWindow().getDecorView().setSystemUiVisibility(flags); } else { Log.e(TAG, "updateSystemUiVisibility: decor view not yet created, ignoring"); } } // Need API 11 to check for existence of a vibrator? Zany. @TargetApi(11) public void checkForVibrator() { if (Build.VERSION.SDK_INT >= 11) { if (!vibrator.hasVibrator()) { vibrator = null; } } } // Override this to scale the backbuffer (use the Android hardware scaler) public void getDesiredBackbufferSize(Point sz) { sz.x = 0; sz.y = 0; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); installID = Installation.id(this); if (!initialized) { Initialize(); initialized = true; } // Keep the screen bright - very annoying if it goes dark when tilting away Window window = this.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); setVolumeControlStream(AudioManager.STREAM_MUSIC); // Initialize audio and tell PPSSPP to gain audio focus if (!useOpenSL()) { Log.w(TAG, "Falling back to AudioTrack"); audioPlayer = new NativeAudioPlayer(); } NativeAudioPlayer.gainAudioFocus(this.audioManager, this.audioFocusChangeListener); NativeApp.audioInit(); mGLSurfaceView = new NativeGLView(this); nativeRenderer = new NativeRenderer(this); Point sz = new Point(); getDesiredBackbufferSize(sz); if (sz.x > 0) { Log.i(TAG, "Requesting fixed size buffer: " + sz.x + "x" + sz.y); // Auto-calculates new DPI and forwards to the correct call on mGLSurfaceView.getHolder() nativeRenderer.setFixedSize(sz.x, sz.y, mGLSurfaceView); } mGLSurfaceView.setEGLContextClientVersion(2); // Setup the GLSurface and ask android for the correct // Number of bits for r, g, b, a, depth and stencil components // The PSP only has 16-bit Z so that should be enough. // Might want to change this for other apps (24-bit might be useful). // Actually, we might be able to do without both stencil and depth in // the back buffer, but that would kill non-buffered rendering. // It appears some gingerbread devices blow up if you use a config chooser at all ???? (Xperia Play) //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // On some (especially older devices), things blow up later (EGL_BAD_MATCH) if we don't set the format here, // if we specify that we want destination alpha in the config chooser, which we do. // http://grokbase.com/t/gg/android-developers/11bj40jm4w/fall-back // Needed to avoid banding on Ouya? if (Build.MANUFACTURER == "OUYA") { mGLSurfaceView.getHolder().setFormat(PixelFormat.RGBX_8888); mGLSurfaceView.setEGLConfigChooser(new NativeEGLConfigChooser()); } mGLSurfaceView.setRenderer(nativeRenderer); setContentView(mGLSurfaceView); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { updateSystemUiVisibility(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { setupSystemUiCallback(); } } } @TargetApi(19) void setupSystemUiCallback() { getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { if (visibility == 0) { updateSystemUiVisibility(); } } }); } @Override protected void onStop() { super.onStop(); Log.i(TAG, "onStop - do nothing special"); } @Override protected void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy"); mGLSurfaceView.onDestroy(); nativeRenderer.onDestroyed(); NativeApp.audioShutdown(); // Probably vain attempt to help the garbage collector... audioPlayer = null; mGLSurfaceView = null; audioFocusChangeListener = null; audioManager = null; } private boolean detectOpenGLES20() { ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo info = am.getDeviceConfigurationInfo(); return info.reqGlEsVersion >= 0x20000; } private boolean detectOpenGLES30() { ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo info = am.getDeviceConfigurationInfo(); return info.reqGlEsVersion >= 0x30000; } @Override protected void onPause() { super.onPause(); Log.i(TAG, "onPause"); NativeAudioPlayer.loseAudioFocus(this.audioManager, this.audioFocusChangeListener); if (audioPlayer != null) { audioPlayer.stop(); } NativeApp.pause(); mGLSurfaceView.onPause(); } @Override protected void onResume() { super.onResume(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { updateSystemUiVisibility(); } // OK, config should be initialized, we can query for screen rotation. if (Build.VERSION.SDK_INT >= 9) { updateScreenRotation(); } Log.i(TAG, "onResume"); if (mGLSurfaceView != null) { mGLSurfaceView.onResume(); } else { Log.e(TAG, "mGLSurfaceView really shouldn't be null in onResume"); } NativeAudioPlayer.gainAudioFocus(this.audioManager, this.audioFocusChangeListener); if (audioPlayer != null) { audioPlayer.play(); } NativeApp.resume(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { updateSystemUiVisibility(); } Point sz = new Point(); getDesiredBackbufferSize(sz); if (sz.x > 0) { mGLSurfaceView.getHolder().setFixedSize(sz.x/2, sz.y/2); } } // We simply grab the first input device to produce an event and ignore all others that are connected. @TargetApi(Build.VERSION_CODES.GINGERBREAD) private InputDeviceState getInputDeviceState(InputEvent event) { InputDevice device = event.getDevice(); if (device == null) { return null; } if (inputPlayerA == null) { Log.i(TAG, "Input player A registered"); inputPlayerA = new InputDeviceState(device); inputPlayerADesc = getInputDesc(device); } if (inputPlayerA.getDevice() == device) { return inputPlayerA; } if (inputPlayerB == null) { Log.i(TAG, "Input player B registered"); inputPlayerB = new InputDeviceState(device); } if (inputPlayerB.getDevice() == device) { return inputPlayerB; } if (inputPlayerC == null) { Log.i(TAG, "Input player C registered"); inputPlayerC = new InputDeviceState(device); } if (inputPlayerC.getDevice() == device) { return inputPlayerC; } return inputPlayerA; } public boolean IsXperiaPlay() { return android.os.Build.MODEL.equals("R800a") || android.os.Build.MODEL.equals("R800i") || android.os.Build.MODEL.equals("R800x") || android.os.Build.MODEL.equals("R800at") || android.os.Build.MODEL.equals("SO-01D") || android.os.Build.MODEL.equals("zeus"); } // We grab the keys before onKeyDown/... even see them. This is also better because it lets us // distinguish devices. @Override public boolean dispatchKeyEvent(KeyEvent event) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && !isXperiaPlay) { InputDeviceState state = getInputDeviceState(event); if (state == null) { return super.dispatchKeyEvent(event); } // Let's let volume and back through to dispatchKeyEvent. boolean passThrough = false; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_MENU: passThrough = true; break; default: break; } switch (event.getAction()) { case KeyEvent.ACTION_DOWN: if (state.onKeyDown(event) && !passThrough) { return true; } break; case KeyEvent.ACTION_UP: if (state.onKeyUp(event) && !passThrough) { return true; } break; } } // Let's go through the old path (onKeyUp, onKeyDown). return super.dispatchKeyEvent(event); } @TargetApi(16) static public String getInputDesc(InputDevice input) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { return input.getDescriptor(); } else { List motions = input.getMotionRanges(); String fakeid = ""; for (InputDevice.MotionRange range : motions) fakeid += range.getAxis(); return fakeid; } } @Override @TargetApi(12) public boolean onGenericMotionEvent(MotionEvent event) { // Log.d(TAG, "onGenericMotionEvent: " + event); if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { if (Build.VERSION.SDK_INT >= 12) { InputDeviceState state = getInputDeviceState(event); if (state == null) { Log.w(TAG, "Joystick event but failed to get input device state."); return super.onGenericMotionEvent(event); } state.onJoystickMotion(event); return true; } } if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_HOVER_MOVE: // process the mouse hover movement... return true; case MotionEvent.ACTION_SCROLL: NativeApp.mouseWheelEvent(event.getX(), event.getY()); return true; } } return super.onGenericMotionEvent(event); } @SuppressLint("NewApi") @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // Eat these keys, to avoid accidental exits / other screwups. // Maybe there's even more we need to eat on tablets? boolean repeat = event.getRepeatCount() > 0; switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (event.isAltPressed()) { NativeApp.keyDown(0, 1004, repeat); // special custom keycode } else if (NativeApp.isAtTopLevel()) { Log.i(TAG, "IsAtTopLevel returned true."); return super.onKeyDown(keyCode, event); } else { NativeApp.keyDown(0, keyCode, repeat); } return true; case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_SEARCH: NativeApp.keyDown(0, keyCode, repeat); return true; case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: // Joysticks are supported in Honeycomb MR1 and later via the onGenericMotionEvent method. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && event.getSource() == InputDevice.SOURCE_JOYSTICK) { return super.onKeyDown(keyCode, event); } // Fall through default: // send the rest of the keys through. // TODO: get rid of the three special cases above by adjusting the native side of the code. // Log.d(TAG, "Key down: " + keyCode + ", KeyEvent: " + event); return NativeApp.keyDown(0, keyCode, repeat); } } @SuppressLint("NewApi") @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (event.isAltPressed()) { NativeApp.keyUp(0, 1004); // special custom keycode } else if (NativeApp.isAtTopLevel()) { Log.i(TAG, "IsAtTopLevel returned true."); return super.onKeyUp(keyCode, event); } else { NativeApp.keyUp(0, keyCode); } return true; case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_SEARCH: // Search probably should also be ignored. We send it to the app. NativeApp.keyUp(0, keyCode); return true; case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: // Joysticks are supported in Honeycomb MR1 and later via the onGenericMotionEvent method. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && event.getSource() == InputDevice.SOURCE_JOYSTICK) { return super.onKeyUp(keyCode, event); } // Fall through default: // send the rest of the keys through. // TODO: get rid of the three special cases above by adjusting the native side of the code. // Log.d(TAG, "Key down: " + keyCode + ", KeyEvent: " + event); return NativeApp.keyUp(0, keyCode); } } @TargetApi(11) private AlertDialog.Builder createDialogBuilderWithTheme() { return new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK); } // The return value is sent elsewhere. TODO in java, in SendMessage in C++. public void inputBox(String title, String defaultText, String defaultAction) { final FrameLayout fl = new FrameLayout(this); final EditText input = new EditText(this); input.setGravity(Gravity.CENTER); FrameLayout.LayoutParams editBoxLayout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); editBoxLayout.setMargins(2, 20, 2, 20); fl.addView(input, editBoxLayout); input.setInputType(InputType.TYPE_CLASS_TEXT); input.setText(defaultText); input.selectAll(); AlertDialog.Builder bld = null; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) bld = new AlertDialog.Builder(this); else bld = createDialogBuilderWithTheme(); AlertDialog dlg = bld .setView(fl) .setTitle(title) .setPositiveButton(defaultAction, new DialogInterface.OnClickListener(){ public void onClick(DialogInterface d, int which) { NativeApp.sendMessage("inputbox_completed", input.getText().toString()); d.dismiss(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener(){ public void onClick(DialogInterface d, int which) { NativeApp.sendMessage("inputbox_failed", ""); d.cancel(); } }).create(); dlg.setCancelable(true); dlg.show(); } public boolean processCommand(String command, String params) { if (command.equals("launchBrowser")) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(params)); startActivity(i); return true; } else if (command.equals("launchEmail")) { Intent send = new Intent(Intent.ACTION_SENDTO); String uriText; uriText = "mailto:email@gmail.com" + "?subject=Your app is..." + "&body=great! Or?"; uriText = uriText.replace(" ", "%20"); Uri uri = Uri.parse(uriText); send.setData(uri); startActivity(Intent.createChooser(send, "E-mail the app author!")); return true; } else if (command.equals("sharejpeg")) { Intent share = new Intent(Intent.ACTION_SEND); share.setType("image/jpeg"); share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + params)); startActivity(Intent.createChooser(share, "Share Picture")); } else if (command.equals("sharetext")) { Intent sendIntent = new Intent(); sendIntent.setType("text/plain"); sendIntent.putExtra(Intent.EXTRA_TEXT, params); sendIntent.setAction(Intent.ACTION_SEND); startActivity(sendIntent); } else if (command.equals("showTwitter")) { String twitter_user_name = params; try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("twitter://user?screen_name=" + twitter_user_name))); } catch (Exception e) { startActivity(new Intent( Intent.ACTION_VIEW, Uri.parse("https://twitter.com/#!/" + twitter_user_name))); } } else if (command.equals("launchMarket")) { // Don't need this, can just use launchBrowser with a market: // http://stackoverflow.com/questions/3442366/android-link-to-market-from-inside-another-app // http://developer.android.com/guide/publishing/publishing.html#marketintent return false; } else if (command.equals("toast")) { Toast toast = Toast.makeText(this, params, Toast.LENGTH_SHORT); toast.show(); Log.i(TAG, params); return true; } else if (command.equals("showKeyboard")) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // No idea what the point of the ApplicationWindowToken is or if it // matters where we get it from... inputMethodManager.toggleSoftInputFromWindow( mGLSurfaceView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("hideKeyboard")) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.toggleSoftInputFromWindow( mGLSurfaceView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("inputbox")) { String title = "Input"; String defString = ""; String[] param = params.split(":"); if (param[0].length() > 0) title = param[0]; if (param.length > 1) defString = param[1]; Log.i(TAG, "Launching inputbox: " + title + " " + defString); inputBox(title, defString, "OK"); return true; } else if (command.equals("vibrate")) { int milliseconds = -1; if (params != "") { try { milliseconds = Integer.parseInt(params); } catch (NumberFormatException e) { } } // Special parameters to perform standard haptic feedback // operations // -1 = Standard keyboard press feedback // -2 = Virtual key press // -3 = Long press feedback // Note that these three do not require the VIBRATE Android // permission. switch (milliseconds) { case -1: mGLSurfaceView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); break; case -2: mGLSurfaceView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); break; case -3: mGLSurfaceView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; default: if (vibrator != null) { vibrator.vibrate(milliseconds); } break; } return true; } else if (command.equals("finish")) { finish(); } else if (command.equals("rotate")) { if (Build.VERSION.SDK_INT >= 9) { updateScreenRotation(); } } else if (command.equals("immersive")) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { updateSystemUiVisibility(); } } else if (command.equals("recreate")) { recreate(); } return false; } @SuppressLint("NewApi") @Override public void recreate() { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { super.recreate(); } else { startActivity(getIntent()); finish(); } } }