2 ==============================================================================
\r
4 This file is part of the JUCE library.
\r
5 Copyright (c) 2017 - ROLI Ltd.
\r
7 JUCE is an open source library subject to commercial or open-source
\r
10 The code included in this file is provided under the terms of the ISC license
\r
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
\r
12 To use, copy, modify, and/or distribute this software for any purpose with or
\r
13 without fee is hereby granted provided that the above copyright notice and
\r
14 this permission notice appear in all copies.
\r
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
\r
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
\r
20 ==============================================================================
\r
23 package com.juce.networkgraphicsdemo;
\r
25 import android.app.Activity;
\r
26 import android.app.AlertDialog;
\r
27 import android.content.DialogInterface;
\r
28 import android.content.Context;
\r
29 import android.content.Intent;
\r
30 import android.content.res.Configuration;
\r
31 import android.content.pm.PackageInfo;
\r
32 import android.content.pm.PackageManager;
\r
33 import android.net.http.SslError;
\r
34 import android.net.Uri;
\r
35 import android.os.Bundle;
\r
36 import android.os.Looper;
\r
37 import android.os.Handler;
\r
38 import android.os.Message;
\r
39 import android.os.ParcelUuid;
\r
40 import android.os.Environment;
\r
41 import android.view.*;
\r
42 import android.view.inputmethod.BaseInputConnection;
\r
43 import android.view.inputmethod.EditorInfo;
\r
44 import android.view.inputmethod.InputConnection;
\r
45 import android.view.inputmethod.InputMethodManager;
\r
46 import android.graphics.*;
\r
47 import android.text.ClipboardManager;
\r
48 import android.text.InputType;
\r
49 import android.util.DisplayMetrics;
\r
50 import android.util.Log;
\r
51 import android.util.Pair;
\r
52 import android.webkit.SslErrorHandler;
\r
53 import android.webkit.WebChromeClient;
\r
54 import android.webkit.WebView;
\r
55 import android.webkit.WebViewClient;
\r
56 import java.lang.Runnable;
\r
57 import java.lang.ref.WeakReference;
\r
58 import java.lang.reflect.*;
\r
61 import java.net.URL;
\r
62 import java.net.HttpURLConnection;
\r
63 import android.media.AudioManager;
\r
64 import android.Manifest;
\r
65 import java.util.concurrent.CancellationException;
\r
66 import java.util.concurrent.Future;
\r
67 import java.util.concurrent.Executors;
\r
68 import java.util.concurrent.ExecutorService;
\r
69 import java.util.concurrent.ExecutionException;
\r
70 import java.util.concurrent.TimeUnit;
\r
71 import java.util.concurrent.Callable;
\r
72 import java.util.concurrent.TimeoutException;
\r
73 import java.util.concurrent.locks.ReentrantLock;
\r
74 import java.util.concurrent.atomic.*;
\r
78 //==============================================================================
\r
79 public class JUCENetworkGraphicsDemo extends Activity
\r
81 //==============================================================================
\r
84 System.loadLibrary ("juce_jni");
\r
87 //==============================================================================
\r
88 public boolean isPermissionDeclaredInManifest (int permissionID)
\r
90 String permissionToCheck = getAndroidPermissionName(permissionID);
\r
94 PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
\r
96 if (info.requestedPermissions != null)
\r
97 for (String permission : info.requestedPermissions)
\r
98 if (permission.equals (permissionToCheck))
\r
101 catch (PackageManager.NameNotFoundException e)
\r
103 Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());
\r
106 Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);
\r
110 //==============================================================================
\r
111 // these have to match the values of enum PermissionID in C++ class RuntimePermissions:
\r
112 private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;
\r
113 private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
\r
114 private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3;
\r
115 private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4;
\r
117 private static String getAndroidPermissionName (int permissionID)
\r
119 switch (permissionID)
\r
121 case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO;
\r
122 case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION;
\r
123 // use string value as this is not defined in SDKs < 16
\r
124 case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE";
\r
125 case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE;
\r
128 // unknown permission ID!
\r
130 return new String();
\r
133 public boolean isPermissionGranted (int permissionID)
\r
135 return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
\r
138 private Map<Integer, Long> permissionCallbackPtrMap;
\r
140 public void requestRuntimePermission (int permissionID, long ptrToCallback)
\r
142 String permissionName = getAndroidPermissionName (permissionID);
\r
144 if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED)
\r
146 // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously
\r
147 permissionCallbackPtrMap.put (permissionID, ptrToCallback);
\r
148 requestPermissionsCompat (new String[]{permissionName}, permissionID);
\r
152 // permissions were already granted before, we can call callback directly
\r
153 androidRuntimePermissionsCallback (true, ptrToCallback);
\r
157 private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);
\r
160 //==============================================================================
\r
161 public interface JuceMidiPort
\r
163 boolean isInputPort();
\r
165 // start, stop does nothing on an output port
\r
171 // send will do nothing on an input port
\r
172 void sendMidi (byte[] msg, int offset, int count);
\r
175 //==============================================================================
\r
176 //==============================================================================
\r
177 public class BluetoothManager
\r
183 public String[] getMidiBluetoothAddresses()
\r
185 String[] bluetoothAddresses = new String[0];
\r
186 return bluetoothAddresses;
\r
189 public String getHumanReadableStringForBluetoothAddress (String address)
\r
194 public int getBluetoothDeviceStatus (String address)
\r
199 public void startStopScan (boolean shouldStart)
\r
203 public boolean pairBluetoothMidiDevice(String address)
\r
208 public void unpairBluetoothMidiDevice (String address)
\r
213 //==============================================================================
\r
214 public class MidiDeviceManager
\r
216 public MidiDeviceManager()
\r
220 public String[] getJuceAndroidMidiInputDevices()
\r
222 return new String[0];
\r
225 public String[] getJuceAndroidMidiOutputDevices()
\r
227 return new String[0];
\r
230 public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
\r
235 public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
\r
240 public String getInputPortNameForJuceIndex (int index)
\r
245 public String getOutputPortNameForJuceIndex (int index)
\r
252 public MidiDeviceManager getAndroidMidiDeviceManager()
\r
257 public BluetoothManager getAndroidBluetoothManager()
\r
262 //==============================================================================
\r
264 public void onCreate (Bundle savedInstanceState)
\r
266 super.onCreate (savedInstanceState);
\r
268 isScreenSaverEnabled = true;
\r
270 viewHolder = new ViewHolder (this);
\r
271 setContentView (viewHolder);
\r
273 setVolumeControlStream (AudioManager.STREAM_MUSIC);
\r
275 permissionCallbackPtrMap = new HashMap<Integer, Long>();
\r
279 protected void onDestroy()
\r
288 protected void onPause()
\r
294 Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down
\r
295 // openGL glitches when pausing/resuming apps..
\r
296 } catch (InterruptedException e) {}
\r
302 protected void onResume()
\r
307 // Ensure that navigation/status bar visibility is correctly restored.
\r
308 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
310 if (viewHolder.getChildAt (i) instanceof ComponentPeerView)
\r
311 ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
\r
316 public void onConfigurationChanged (Configuration cfg)
\r
318 super.onConfigurationChanged (cfg);
\r
319 setContentView (viewHolder);
\r
322 private void callAppLauncher()
\r
324 launchApp (getApplicationInfo().publicSourceDir,
\r
325 getApplicationInfo().dataDir);
\r
328 // Need to override this as the default implementation always finishes the activity.
\r
330 public void onBackPressed()
\r
332 ComponentPeerView focusedView = getViewWithFocusOrDefaultView();
\r
334 if (focusedView == null)
\r
337 focusedView.backButtonPressed();
\r
340 private ComponentPeerView getViewWithFocusOrDefaultView()
\r
342 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
344 if (viewHolder.getChildAt (i).hasFocus())
\r
345 return (ComponentPeerView) viewHolder.getChildAt (i);
\r
348 if (viewHolder.getChildCount() > 0)
\r
349 return (ComponentPeerView) viewHolder.getChildAt (0);
\r
354 //==============================================================================
\r
355 private void hideActionBar()
\r
357 // get "getActionBar" method
\r
358 java.lang.reflect.Method getActionBarMethod = null;
\r
361 getActionBarMethod = this.getClass().getMethod ("getActionBar");
\r
363 catch (SecurityException e) { return; }
\r
364 catch (NoSuchMethodException e) { return; }
\r
365 if (getActionBarMethod == null) return;
\r
367 // invoke "getActionBar" method
\r
368 Object actionBar = null;
\r
371 actionBar = getActionBarMethod.invoke (this);
\r
373 catch (java.lang.IllegalArgumentException e) { return; }
\r
374 catch (java.lang.IllegalAccessException e) { return; }
\r
375 catch (java.lang.reflect.InvocationTargetException e) { return; }
\r
376 if (actionBar == null) return;
\r
378 // get "hide" method
\r
379 java.lang.reflect.Method actionBarHideMethod = null;
\r
382 actionBarHideMethod = actionBar.getClass().getMethod ("hide");
\r
384 catch (SecurityException e) { return; }
\r
385 catch (NoSuchMethodException e) { return; }
\r
386 if (actionBarHideMethod == null) return;
\r
388 // invoke "hide" method
\r
391 actionBarHideMethod.invoke (actionBar);
\r
393 catch (java.lang.IllegalArgumentException e) {}
\r
394 catch (java.lang.IllegalAccessException e) {}
\r
395 catch (java.lang.reflect.InvocationTargetException e) {}
\r
398 void requestPermissionsCompat (String[] permissions, int requestCode)
\r
400 Method requestPermissionsMethod = null;
\r
403 requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",
\r
404 String[].class, int.class);
\r
406 catch (SecurityException e) { return; }
\r
407 catch (NoSuchMethodException e) { return; }
\r
408 if (requestPermissionsMethod == null) return;
\r
412 requestPermissionsMethod.invoke (this, permissions, requestCode);
\r
414 catch (java.lang.IllegalArgumentException e) {}
\r
415 catch (java.lang.IllegalAccessException e) {}
\r
416 catch (java.lang.reflect.InvocationTargetException e) {}
\r
419 //==============================================================================
\r
420 private native void launchApp (String appFile, String appDataDir);
\r
421 private native void quitApp();
\r
422 private native void suspendApp();
\r
423 private native void resumeApp();
\r
424 private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
\r
425 private native void appActivityResult (int requestCode, int resultCode, Intent data);
\r
426 private native void appNewIntent (Intent intent);
\r
428 //==============================================================================
\r
429 private ViewHolder viewHolder;
\r
430 private MidiDeviceManager midiDeviceManager = null;
\r
431 private BluetoothManager bluetoothManager = null;
\r
432 private boolean isScreenSaverEnabled;
\r
433 private java.util.Timer keepAliveTimer;
\r
435 public final ComponentPeerView createNewView (boolean opaque, long host)
\r
437 ComponentPeerView v = new ComponentPeerView (this, opaque, host);
\r
438 viewHolder.addView (v);
\r
442 public final void deleteView (ComponentPeerView view)
\r
446 ViewGroup group = (ViewGroup) (view.getParent());
\r
449 group.removeView (view);
\r
452 public final void deleteNativeSurfaceView (NativeSurfaceView view)
\r
454 ViewGroup group = (ViewGroup) (view.getParent());
\r
457 group.removeView (view);
\r
460 final class ViewHolder extends ViewGroup
\r
462 public ViewHolder (Context context)
\r
465 setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
\r
466 setFocusable (false);
\r
469 protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
471 setScreenSize (getWidth(), getHeight(), getDPI());
\r
475 isFirstResize = false;
\r
480 private final int getDPI()
\r
482 DisplayMetrics metrics = new DisplayMetrics();
\r
483 getWindowManager().getDefaultDisplay().getMetrics (metrics);
\r
484 return metrics.densityDpi;
\r
487 private boolean isFirstResize = true;
\r
490 public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
\r
492 canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
\r
495 //==============================================================================
\r
496 public final void setScreenSaver (boolean enabled)
\r
498 if (isScreenSaverEnabled != enabled)
\r
500 isScreenSaverEnabled = enabled;
\r
502 if (keepAliveTimer != null)
\r
504 keepAliveTimer.cancel();
\r
505 keepAliveTimer = null;
\r
510 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
514 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
516 // If no user input is received after about 3 seconds, the OS will lower the
\r
517 // task's priority, so this timer forces it to be kept active.
\r
518 keepAliveTimer = new java.util.Timer();
\r
520 keepAliveTimer.scheduleAtFixedRate (new TimerTask()
\r
525 android.app.Instrumentation instrumentation = new android.app.Instrumentation();
\r
529 instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
\r
531 catch (Exception e)
\r
540 public final boolean getScreenSaver()
\r
542 return isScreenSaverEnabled;
\r
545 //==============================================================================
\r
546 public final String getClipboardContent()
\r
548 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
549 return clipboard.getText().toString();
\r
552 public final void setClipboardContent (String newText)
\r
554 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
555 clipboard.setText (newText);
\r
558 //==============================================================================
\r
559 public final void showMessageBox (String title, String message, final long callback)
\r
561 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
562 builder.setTitle (title)
\r
563 .setMessage (message)
\r
564 .setCancelable (true)
\r
565 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
567 public void onCancel (DialogInterface dialog)
\r
569 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
572 .setPositiveButton ("OK", new DialogInterface.OnClickListener()
\r
574 public void onClick (DialogInterface dialog, int id)
\r
577 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
581 builder.create().show();
\r
584 public final void showOkCancelBox (String title, String message, final long callback,
\r
585 String okButtonText, String cancelButtonText)
\r
587 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
588 builder.setTitle (title)
\r
589 .setMessage (message)
\r
590 .setCancelable (true)
\r
591 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
593 public void onCancel (DialogInterface dialog)
\r
595 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
598 .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()
\r
600 public void onClick (DialogInterface dialog, int id)
\r
603 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);
\r
606 .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()
\r
608 public void onClick (DialogInterface dialog, int id)
\r
611 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
615 builder.create().show();
\r
618 public final void showYesNoCancelBox (String title, String message, final long callback)
\r
620 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
621 builder.setTitle (title)
\r
622 .setMessage (message)
\r
623 .setCancelable (true)
\r
624 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
626 public void onCancel (DialogInterface dialog)
\r
628 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
631 .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
\r
633 public void onClick (DialogInterface dialog, int id)
\r
636 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);
\r
639 .setNegativeButton ("No", new DialogInterface.OnClickListener()
\r
641 public void onClick (DialogInterface dialog, int id)
\r
644 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 2);
\r
647 .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
\r
649 public void onClick (DialogInterface dialog, int id)
\r
652 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
656 builder.create().show();
\r
659 public native void alertDismissed (long callback, int id);
\r
661 //==============================================================================
\r
662 public final class ComponentPeerView extends ViewGroup
\r
663 implements View.OnFocusChangeListener
\r
665 public ComponentPeerView (Context context, boolean opaque_, long host)
\r
669 setWillNotDraw (false);
\r
672 setFocusable (true);
\r
673 setFocusableInTouchMode (true);
\r
674 setOnFocusChangeListener (this);
\r
676 // swap red and blue colours to match internal opengl texture format
\r
677 ColorMatrix colorMatrix = new ColorMatrix();
\r
679 float[] colorTransform = { 0, 0, 1.0f, 0, 0,
\r
682 0, 0, 0, 1.0f, 0 };
\r
684 colorMatrix.set (colorTransform);
\r
685 paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
\r
687 java.lang.reflect.Method method = null;
\r
691 method = getClass().getMethod ("setLayerType", int.class, Paint.class);
\r
693 catch (SecurityException e) {}
\r
694 catch (NoSuchMethodException e) {}
\r
696 if (method != null)
\r
700 int layerTypeNone = 0;
\r
701 method.invoke (this, layerTypeNone, null);
\r
703 catch (java.lang.IllegalArgumentException e) {}
\r
704 catch (java.lang.IllegalAccessException e) {}
\r
705 catch (java.lang.reflect.InvocationTargetException e) {}
\r
709 //==============================================================================
\r
710 private native void handlePaint (long host, Canvas canvas, Paint paint);
\r
713 public void onDraw (Canvas canvas)
\r
718 handlePaint (host, canvas, paint);
\r
722 public boolean isOpaque()
\r
727 private boolean opaque;
\r
729 private Paint paint = new Paint();
\r
731 //==============================================================================
\r
732 private native void handleMouseDown (long host, int index, float x, float y, long time);
\r
733 private native void handleMouseDrag (long host, int index, float x, float y, long time);
\r
734 private native void handleMouseUp (long host, int index, float x, float y, long time);
\r
737 public boolean onTouchEvent (MotionEvent event)
\r
742 int action = event.getAction();
\r
743 long time = event.getEventTime();
\r
745 switch (action & MotionEvent.ACTION_MASK)
\r
747 case MotionEvent.ACTION_DOWN:
\r
748 handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
751 case MotionEvent.ACTION_CANCEL:
\r
752 case MotionEvent.ACTION_UP:
\r
753 handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
756 case MotionEvent.ACTION_MOVE:
\r
758 int n = event.getPointerCount();
\r
759 for (int i = 0; i < n; ++i)
\r
760 handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
765 case MotionEvent.ACTION_POINTER_UP:
\r
767 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
768 handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
772 case MotionEvent.ACTION_POINTER_DOWN:
\r
774 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
775 handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
786 //==============================================================================
\r
787 private native void handleKeyDown (long host, int keycode, int textchar);
\r
788 private native void handleKeyUp (long host, int keycode, int textchar);
\r
789 private native void handleBackButton (long host);
\r
790 private native void handleKeyboardHidden (long host);
\r
792 public void showKeyboard (String type)
\r
794 InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
\r
798 if (type.length() > 0)
\r
800 imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
\r
801 imm.setInputMethod (getWindowToken(), type);
\r
802 keyboardDismissListener.startListening();
\r
806 imm.hideSoftInputFromWindow (getWindowToken(), 0);
\r
807 keyboardDismissListener.stopListening();
\r
812 public void backButtonPressed()
\r
817 handleBackButton (host);
\r
821 public boolean onKeyDown (int keyCode, KeyEvent event)
\r
828 case KeyEvent.KEYCODE_VOLUME_UP:
\r
829 case KeyEvent.KEYCODE_VOLUME_DOWN:
\r
830 return super.onKeyDown (keyCode, event);
\r
831 case KeyEvent.KEYCODE_BACK:
\r
833 ((Activity) getContext()).onBackPressed();
\r
841 handleKeyDown (host, keyCode, event.getUnicodeChar());
\r
846 public boolean onKeyUp (int keyCode, KeyEvent event)
\r
851 handleKeyUp (host, keyCode, event.getUnicodeChar());
\r
856 public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
\r
861 if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
\r
862 return super.onKeyMultiple (keyCode, count, event);
\r
864 if (event.getCharacters() != null)
\r
866 int utf8Char = event.getCharacters().codePointAt (0);
\r
867 handleKeyDown (host, utf8Char, utf8Char);
\r
874 //==============================================================================
\r
875 private final class KeyboardDismissListener
\r
877 public KeyboardDismissListener (ComponentPeerView viewToUse)
\r
882 private void startListening()
\r
884 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);
\r
887 private void stopListening()
\r
889 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);
\r
892 private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener
\r
896 keyboardShown = false;
\r
900 public void onGlobalLayout()
\r
902 Rect r = new Rect();
\r
904 ViewGroup parentView = (ViewGroup) getParent();
\r
906 if (parentView == null)
\r
909 parentView.getWindowVisibleDisplayFrame (r);
\r
911 int diff = parentView.getHeight() - (r.bottom - r.top);
\r
913 // Arbitrary threshold, surely keyboard would take more than 20 pix.
\r
914 if (diff < 20 && keyboardShown)
\r
916 keyboardShown = false;
\r
917 handleKeyboardHidden (view.host);
\r
920 if (! keyboardShown && diff > 20)
\r
921 keyboardShown = true;
\r
924 private boolean keyboardShown;
\r
927 private ComponentPeerView view;
\r
928 private TreeObserver viewTreeObserver = new TreeObserver();
\r
931 private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);
\r
933 // this is here to make keyboard entry work on a Galaxy Tab2 10.1
\r
935 public InputConnection onCreateInputConnection (EditorInfo outAttrs)
\r
937 outAttrs.actionLabel = "";
\r
938 outAttrs.hintText = "";
\r
939 outAttrs.initialCapsMode = 0;
\r
940 outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
\r
941 outAttrs.label = "";
\r
942 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
\r
943 outAttrs.inputType = InputType.TYPE_NULL;
\r
945 return new BaseInputConnection (this, false);
\r
948 //==============================================================================
\r
950 protected void onSizeChanged (int w, int h, int oldw, int oldh)
\r
955 super.onSizeChanged (w, h, oldw, oldh);
\r
956 viewSizeChanged (host);
\r
960 protected void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
962 for (int i = getChildCount(); --i >= 0;)
\r
963 requestTransparentRegion (getChildAt (i));
\r
966 private native void viewSizeChanged (long host);
\r
969 public void onFocusChange (View v, boolean hasFocus)
\r
975 focusChanged (host, hasFocus);
\r
978 private native void focusChanged (long host, boolean hasFocus);
\r
980 public void setViewName (String newName) {}
\r
982 public void setSystemUiVisibilityCompat (int visibility)
\r
984 Method systemUIVisibilityMethod = null;
\r
987 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);
\r
989 catch (SecurityException e) { return; }
\r
990 catch (NoSuchMethodException e) { return; }
\r
991 if (systemUIVisibilityMethod == null) return;
\r
995 systemUIVisibilityMethod.invoke (this, visibility);
\r
997 catch (java.lang.IllegalArgumentException e) {}
\r
998 catch (java.lang.IllegalAccessException e) {}
\r
999 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1002 public boolean isVisible() { return getVisibility() == VISIBLE; }
\r
1003 public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
\r
1005 public boolean containsPoint (int x, int y)
\r
1007 return true; //xxx needs to check overlapping views
\r
1010 //==============================================================================
\r
1011 private native void handleAppResumed (long host);
\r
1013 public void appResumed()
\r
1018 handleAppResumed (host);
\r
1022 //==============================================================================
\r
1023 public static class NativeSurfaceView extends SurfaceView
\r
1024 implements SurfaceHolder.Callback
\r
1026 private long nativeContext = 0;
\r
1028 NativeSurfaceView (Context context, long nativeContextPtr)
\r
1031 nativeContext = nativeContextPtr;
\r
1034 public Surface getNativeSurface()
\r
1036 Surface retval = null;
\r
1038 SurfaceHolder holder = getHolder();
\r
1039 if (holder != null)
\r
1040 retval = holder.getSurface();
\r
1045 //==============================================================================
\r
1047 public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
\r
1049 surfaceChangedNative (nativeContext, holder, format, width, height);
\r
1053 public void surfaceCreated (SurfaceHolder holder)
\r
1055 surfaceCreatedNative (nativeContext, holder);
\r
1059 public void surfaceDestroyed (SurfaceHolder holder)
\r
1061 surfaceDestroyedNative (nativeContext, holder);
\r
1065 protected void dispatchDraw (Canvas canvas)
\r
1067 super.dispatchDraw (canvas);
\r
1068 dispatchDrawNative (nativeContext, canvas);
\r
1071 //==============================================================================
\r
1073 protected void onAttachedToWindow ()
\r
1075 super.onAttachedToWindow();
\r
1076 getHolder().addCallback (this);
\r
1080 protected void onDetachedFromWindow ()
\r
1082 super.onDetachedFromWindow();
\r
1083 getHolder().removeCallback (this);
\r
1086 //==============================================================================
\r
1087 private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
\r
1088 private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
\r
1089 private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
\r
1090 private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
\r
1091 int format, int width, int height);
\r
1094 public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
\r
1096 return new NativeSurfaceView (this, nativeSurfacePtr);
\r
1099 //==============================================================================
\r
1100 public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)
\r
1102 Path p = new Path();
\r
1104 char[] str = { glyph1, glyph2 };
\r
1105 paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);
\r
1107 RectF boundsF = new RectF();
\r
1108 p.computeBounds (boundsF, true);
\r
1109 matrix.mapRect (boundsF);
\r
1111 boundsF.roundOut (bounds);
\r
1115 final int w = bounds.width();
\r
1116 final int h = Math.max (1, bounds.height());
\r
1118 Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
\r
1120 Canvas c = new Canvas (bm);
\r
1121 matrix.postTranslate (-bounds.left, -bounds.top);
\r
1122 c.setMatrix (matrix);
\r
1123 c.drawPath (p, paint);
\r
1125 final int sizeNeeded = w * h;
\r
1126 if (cachedRenderArray.length < sizeNeeded)
\r
1127 cachedRenderArray = new int [sizeNeeded];
\r
1129 bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
\r
1131 return cachedRenderArray;
\r
1134 private int[] cachedRenderArray = new int [256];
\r
1136 //==============================================================================
\r
1137 public static class NativeInvocationHandler implements InvocationHandler
\r
1139 public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)
\r
1141 activity = activityToUse;
\r
1142 nativeContext = nativeContextRef;
\r
1145 public void nativeContextDeleted()
\r
1147 nativeContext = 0;
\r
1151 public void finalize()
\r
1153 activity.runOnUiThread (new Runnable()
\r
1158 if (nativeContext != 0)
\r
1159 dispatchFinalize (nativeContext);
\r
1165 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
\r
1167 return dispatchInvoke (nativeContext, proxy, method, args);
\r
1170 //==============================================================================
\r
1171 Activity activity;
\r
1172 private long nativeContext = 0;
\r
1174 private native void dispatchFinalize (long nativeContextRef);
\r
1175 private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);
\r
1178 public InvocationHandler createInvocationHandler (long nativeContextRef)
\r
1180 return new NativeInvocationHandler (this, nativeContextRef);
\r
1183 public void invocationHandlerContextDeleted (InvocationHandler handler)
\r
1185 ((NativeInvocationHandler) handler).nativeContextDeleted();
\r
1188 //==============================================================================
\r
1189 public static class HTTPStream
\r
1191 public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,
\r
1192 String headersToUse, int timeOutMsToUse,
\r
1193 int[] statusCodeToUse, StringBuffer responseHeadersToUse,
\r
1194 int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException
\r
1196 isPost = isPostToUse;
\r
1197 postData = postDataToUse;
\r
1198 headers = headersToUse;
\r
1199 timeOutMs = timeOutMsToUse;
\r
1200 statusCode = statusCodeToUse;
\r
1201 responseHeaders = responseHeadersToUse;
\r
1203 numRedirectsToFollow = numRedirectsToFollowToUse;
\r
1204 httpRequestCmd = httpRequestCmdToUse;
\r
1206 connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);
\r
1209 private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,
\r
1210 String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException
\r
1212 HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());
\r
1216 newConnection.setInstanceFollowRedirects (false);
\r
1217 newConnection.setConnectTimeout (timeOutMs);
\r
1218 newConnection.setReadTimeout (timeOutMs);
\r
1220 // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.
\r
1221 // So convert headers string to an array, with an element for each line
\r
1222 String headerLines[] = headers.split("\\n");
\r
1224 // Set request headers
\r
1225 for (int i = 0; i < headerLines.length; ++i)
\r
1227 int pos = headerLines[i].indexOf (":");
\r
1229 if (pos > 0 && pos < headerLines[i].length())
\r
1231 String field = headerLines[i].substring (0, pos);
\r
1232 String value = headerLines[i].substring (pos + 1);
\r
1234 if (value.length() > 0)
\r
1235 newConnection.setRequestProperty (field, value);
\r
1239 newConnection.setRequestMethod (httpRequestCmd);
\r
1243 newConnection.setDoOutput (true);
\r
1245 if (postData != null)
\r
1247 OutputStream out = newConnection.getOutputStream();
\r
1248 out.write(postData);
\r
1253 return newConnection;
\r
1255 catch (Throwable e)
\r
1257 newConnection.disconnect();
\r
1258 throw new IOException ("Connection error");
\r
1262 private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException
\r
1264 synchronized (createFutureLock)
\r
1266 if (hasBeenCancelled.get())
\r
1269 streamFuture = executor.submit (new Callable<BufferedInputStream>()
\r
1272 public BufferedInputStream call() throws IOException
\r
1274 return new BufferedInputStream (isInput ? connection.getInputStream()
\r
1275 : connection.getErrorStream());
\r
1282 return streamFuture.get();
\r
1284 catch (InterruptedException e)
\r
1288 catch (CancellationException e)
\r
1294 public final boolean connect()
\r
1296 boolean result = false;
\r
1297 int numFollowedRedirects = 0;
\r
1301 result = doConnect();
\r
1306 if (++numFollowedRedirects > numRedirectsToFollow)
\r
1309 int status = statusCode[0];
\r
1311 if (status == 301 || status == 302 || status == 303 || status == 307)
\r
1313 // Assumes only one occurrence of "Location"
\r
1314 int pos1 = responseHeaders.indexOf ("Location:") + 10;
\r
1315 int pos2 = responseHeaders.indexOf ("\n", pos1);
\r
1319 String currentLocation = connection.getURL().toString();
\r
1320 String newLocation = responseHeaders.substring (pos1, pos2);
\r
1324 // Handle newLocation whether it's absolute or relative
\r
1325 URL baseUrl = new URL (currentLocation);
\r
1326 URL newUrl = new URL (baseUrl, newLocation);
\r
1327 String transformedNewLocation = newUrl.toString();
\r
1329 if (transformedNewLocation != currentLocation)
\r
1331 // Clear responseHeaders before next iteration
\r
1332 responseHeaders.delete (0, responseHeaders.length());
\r
1334 synchronized (createStreamLock)
\r
1336 if (hasBeenCancelled.get())
\r
1339 connection.disconnect();
\r
1343 connection = createConnection (transformedNewLocation, isPost,
\r
1344 postData, headers, timeOutMs,
\r
1347 catch (Throwable e)
\r
1358 catch (Throwable e)
\r
1377 private final boolean doConnect()
\r
1379 synchronized (createStreamLock)
\r
1381 if (hasBeenCancelled.get())
\r
1388 inputStream = getCancellableStream (true);
\r
1390 catch (ExecutionException e)
\r
1392 if (connection.getResponseCode() < 400)
\r
1394 statusCode[0] = connection.getResponseCode();
\r
1395 connection.disconnect();
\r
1401 statusCode[0] = connection.getResponseCode();
\r
1406 if (statusCode[0] >= 400)
\r
1407 inputStream = getCancellableStream (false);
\r
1409 inputStream = getCancellableStream (true);
\r
1411 catch (ExecutionException e)
\r
1414 for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
\r
1416 if (entry.getKey() != null && entry.getValue() != null)
\r
1418 responseHeaders.append(entry.getKey() + ": "
\r
1419 + android.text.TextUtils.join(",", entry.getValue()) + "\n");
\r
1421 if (entry.getKey().compareTo ("Content-Length") == 0)
\r
1422 totalLength = Integer.decode (entry.getValue().get (0));
\r
1428 catch (IOException e)
\r
1435 static class DisconnectionRunnable implements Runnable
\r
1437 public DisconnectionRunnable (HttpURLConnection theConnection,
\r
1438 InputStream theInputStream,
\r
1439 ReentrantLock theCreateStreamLock,
\r
1440 Object theCreateFutureLock,
\r
1441 Future<BufferedInputStream> theStreamFuture)
\r
1443 connectionToDisconnect = theConnection;
\r
1444 inputStream = theInputStream;
\r
1445 createStreamLock = theCreateStreamLock;
\r
1446 createFutureLock = theCreateFutureLock;
\r
1447 streamFuture = theStreamFuture;
\r
1454 if (! createStreamLock.tryLock())
\r
1456 synchronized (createFutureLock)
\r
1458 if (streamFuture != null)
\r
1459 streamFuture.cancel (true);
\r
1462 createStreamLock.lock();
\r
1465 if (connectionToDisconnect != null)
\r
1466 connectionToDisconnect.disconnect();
\r
1468 if (inputStream != null)
\r
1469 inputStream.close();
\r
1471 catch (IOException e)
\r
1475 createStreamLock.unlock();
\r
1479 private HttpURLConnection connectionToDisconnect;
\r
1480 private InputStream inputStream;
\r
1481 private ReentrantLock createStreamLock;
\r
1482 private Object createFutureLock;
\r
1483 Future<BufferedInputStream> streamFuture;
\r
1486 public final void release()
\r
1488 DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,
\r
1494 synchronized (createStreamLock)
\r
1496 hasBeenCancelled.set (true);
\r
1498 connection = null;
\r
1501 Thread disconnectionThread = new Thread(disconnectionRunnable);
\r
1502 disconnectionThread.start();
\r
1505 public final int read (byte[] buffer, int numBytes)
\r
1511 synchronized (createStreamLock)
\r
1513 if (inputStream != null)
\r
1514 num = inputStream.read (buffer, 0, numBytes);
\r
1517 catch (IOException e)
\r
1526 public final long getPosition() { return position; }
\r
1527 public final long getTotalLength() { return totalLength; }
\r
1528 public final boolean isExhausted() { return false; }
\r
1529 public final boolean setPosition (long newPos) { return false; }
\r
1531 private boolean isPost;
\r
1532 private byte[] postData;
\r
1533 private String headers;
\r
1534 private int timeOutMs;
\r
1535 String httpRequestCmd;
\r
1536 private HttpURLConnection connection;
\r
1537 private int[] statusCode;
\r
1538 private StringBuffer responseHeaders;
\r
1539 private int totalLength;
\r
1540 private int numRedirectsToFollow;
\r
1541 private InputStream inputStream;
\r
1542 private long position;
\r
1543 private final ReentrantLock createStreamLock = new ReentrantLock();
\r
1544 private final Object createFutureLock = new Object();
\r
1545 private AtomicBoolean hasBeenCancelled = new AtomicBoolean();
\r
1547 private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());
\r
1548 Future<BufferedInputStream> streamFuture;
\r
1551 public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
\r
1552 String headers, int timeOutMs, int[] statusCode,
\r
1553 StringBuffer responseHeaders, int numRedirectsToFollow,
\r
1554 String httpRequestCmd)
\r
1556 // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
\r
1557 if (timeOutMs < 0)
\r
1559 else if (timeOutMs == 0)
\r
1560 timeOutMs = 30000;
\r
1566 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,
\r
1567 timeOutMs, statusCode, responseHeaders,
\r
1568 numRedirectsToFollow, httpRequestCmd);
\r
1570 return httpStream;
\r
1572 catch (Throwable e) {}
\r
1578 public final void launchURL (String url)
\r
1580 startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
\r
1583 private native boolean webViewPageLoadStarted (long host, WebView view, String url);
\r
1584 private native void webViewPageLoadFinished (long host, WebView view, String url);
\r
1585 private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
\r
1586 private native void webViewCloseWindowRequest (long host, WebView view);
\r
1587 private native void webViewCreateWindowRequest (long host, WebView view);
\r
1589 //==============================================================================
\r
1590 public class JuceWebViewClient extends WebViewClient
\r
1592 public JuceWebViewClient (long hostToUse)
\r
1597 public void hostDeleted()
\r
1599 synchronized (hostLock)
\r
1606 public void onPageFinished (WebView view, String url)
\r
1611 webViewPageLoadFinished (host, view, url);
\r
1615 public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
\r
1620 webViewReceivedSslError (host, view, handler, error);
\r
1624 public void onPageStarted (WebView view, String url, Bitmap favicon)
\r
1627 webViewPageLoadStarted (host, view, url);
\r
1630 private long host;
\r
1631 private final Object hostLock = new Object();
\r
1634 public class JuceWebChromeClient extends WebChromeClient
\r
1636 public JuceWebChromeClient (long hostToUse)
\r
1642 public void onCloseWindow (WebView window)
\r
1644 webViewCloseWindowRequest (host, window);
\r
1648 public boolean onCreateWindow (WebView view, boolean isDialog,
\r
1649 boolean isUserGesture, Message resultMsg)
\r
1651 webViewCreateWindowRequest (host, view);
\r
1655 private long host;
\r
1656 private final Object hostLock = new Object();
\r
1659 //==============================================================================
\r
1660 public static final String getLocaleValue (boolean isRegion)
\r
1662 java.util.Locale locale = java.util.Locale.getDefault();
\r
1664 return isRegion ? locale.getCountry()
\r
1665 : locale.getLanguage();
\r
1668 private static final String getFileLocation (String type)
\r
1670 return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
\r
1673 public static final String getDocumentsFolder()
\r
1675 if (getAndroidSDKVersion() >= 19)
\r
1676 return getFileLocation ("Documents");
\r
1678 return Environment.getDataDirectory().getAbsolutePath();
\r
1681 public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
\r
1682 public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
\r
1683 public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
\r
1684 public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
\r
1686 //==============================================================================
\r
1688 protected void onActivityResult (int requestCode, int resultCode, Intent data)
\r
1690 appActivityResult (requestCode, resultCode, data);
\r
1694 protected void onNewIntent (Intent intent)
\r
1696 super.onNewIntent(intent);
\r
1697 setIntent(intent);
\r
1699 appNewIntent (intent);
\r
1702 //==============================================================================
\r
1703 public final Typeface getTypeFaceFromAsset (String assetName)
\r
1707 return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
\r
1709 catch (Throwable e) {}
\r
1714 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
\r
1716 public static String bytesToHex (byte[] bytes)
\r
1718 char[] hexChars = new char[bytes.length * 2];
\r
1720 for (int j = 0; j < bytes.length; ++j)
\r
1722 int v = bytes[j] & 0xff;
\r
1723 hexChars[j * 2] = hexArray[v >>> 4];
\r
1724 hexChars[j * 2 + 1] = hexArray[v & 0x0f];
\r
1727 return new String (hexChars);
\r
1730 final private java.util.Map dataCache = new java.util.HashMap();
\r
1732 synchronized private final File getDataCacheFile (byte[] data)
\r
1736 java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
\r
1737 digest.update (data);
\r
1739 String key = bytesToHex (digest.digest());
\r
1741 if (dataCache.containsKey (key))
\r
1742 return (File) dataCache.get (key);
\r
1744 File f = new File (this.getCacheDir(), "bindata_" + key);
\r
1746 FileOutputStream os = new FileOutputStream (f);
\r
1747 os.write (data, 0, data.length);
\r
1748 dataCache.put (key, f);
\r
1751 catch (Throwable e) {}
\r
1756 private final void clearDataCache()
\r
1758 java.util.Iterator it = dataCache.values().iterator();
\r
1760 while (it.hasNext())
\r
1762 File f = (File) it.next();
\r
1767 public final Typeface getTypeFaceFromByteArray (byte[] data)
\r
1771 File f = getDataCacheFile (data);
\r
1774 return Typeface.createFromFile (f);
\r
1776 catch (Exception e)
\r
1778 Log.e ("JUCE", e.toString());
\r
1784 public static final int getAndroidSDKVersion()
\r
1786 return android.os.Build.VERSION.SDK_INT;
\r
1789 public final String audioManagerGetProperty (String property)
\r
1791 Object obj = getSystemService (AUDIO_SERVICE);
\r
1795 java.lang.reflect.Method method;
\r
1799 method = obj.getClass().getMethod ("getProperty", String.class);
\r
1801 catch (SecurityException e) { return null; }
\r
1802 catch (NoSuchMethodException e) { return null; }
\r
1804 if (method == null)
\r
1809 return (String) method.invoke (obj, property);
\r
1811 catch (java.lang.IllegalArgumentException e) {}
\r
1812 catch (java.lang.IllegalAccessException e) {}
\r
1813 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1818 public final boolean hasSystemFeature (String property)
\r
1820 return getPackageManager().hasSystemFeature (property);
\r