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
309 ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();
\r
313 public void onConfigurationChanged (Configuration cfg)
\r
315 super.onConfigurationChanged (cfg);
\r
316 setContentView (viewHolder);
\r
319 private void callAppLauncher()
\r
321 launchApp (getApplicationInfo().publicSourceDir,
\r
322 getApplicationInfo().dataDir);
\r
325 // Need to override this as the default implementation always finishes the activity.
\r
327 public void onBackPressed()
\r
329 ComponentPeerView focusedView = getViewWithFocusOrDefaultView();
\r
331 if (focusedView == null)
\r
334 focusedView.backButtonPressed();
\r
337 private ComponentPeerView getViewWithFocusOrDefaultView()
\r
339 for (int i = 0; i < viewHolder.getChildCount(); ++i)
\r
341 if (viewHolder.getChildAt (i).hasFocus())
\r
342 return (ComponentPeerView) viewHolder.getChildAt (i);
\r
345 if (viewHolder.getChildCount() > 0)
\r
346 return (ComponentPeerView) viewHolder.getChildAt (0);
\r
351 //==============================================================================
\r
352 private void hideActionBar()
\r
354 // get "getActionBar" method
\r
355 java.lang.reflect.Method getActionBarMethod = null;
\r
358 getActionBarMethod = this.getClass().getMethod ("getActionBar");
\r
360 catch (SecurityException e) { return; }
\r
361 catch (NoSuchMethodException e) { return; }
\r
362 if (getActionBarMethod == null) return;
\r
364 // invoke "getActionBar" method
\r
365 Object actionBar = null;
\r
368 actionBar = getActionBarMethod.invoke (this);
\r
370 catch (java.lang.IllegalArgumentException e) { return; }
\r
371 catch (java.lang.IllegalAccessException e) { return; }
\r
372 catch (java.lang.reflect.InvocationTargetException e) { return; }
\r
373 if (actionBar == null) return;
\r
375 // get "hide" method
\r
376 java.lang.reflect.Method actionBarHideMethod = null;
\r
379 actionBarHideMethod = actionBar.getClass().getMethod ("hide");
\r
381 catch (SecurityException e) { return; }
\r
382 catch (NoSuchMethodException e) { return; }
\r
383 if (actionBarHideMethod == null) return;
\r
385 // invoke "hide" method
\r
388 actionBarHideMethod.invoke (actionBar);
\r
390 catch (java.lang.IllegalArgumentException e) {}
\r
391 catch (java.lang.IllegalAccessException e) {}
\r
392 catch (java.lang.reflect.InvocationTargetException e) {}
\r
395 void requestPermissionsCompat (String[] permissions, int requestCode)
\r
397 Method requestPermissionsMethod = null;
\r
400 requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",
\r
401 String[].class, int.class);
\r
403 catch (SecurityException e) { return; }
\r
404 catch (NoSuchMethodException e) { return; }
\r
405 if (requestPermissionsMethod == null) return;
\r
409 requestPermissionsMethod.invoke (this, permissions, requestCode);
\r
411 catch (java.lang.IllegalArgumentException e) {}
\r
412 catch (java.lang.IllegalAccessException e) {}
\r
413 catch (java.lang.reflect.InvocationTargetException e) {}
\r
416 //==============================================================================
\r
417 private native void launchApp (String appFile, String appDataDir);
\r
418 private native void quitApp();
\r
419 private native void suspendApp();
\r
420 private native void resumeApp();
\r
421 private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
\r
422 private native void appActivityResult (int requestCode, int resultCode, Intent data);
\r
423 private native void appNewIntent (Intent intent);
\r
425 //==============================================================================
\r
426 private ViewHolder viewHolder;
\r
427 private MidiDeviceManager midiDeviceManager = null;
\r
428 private BluetoothManager bluetoothManager = null;
\r
429 private boolean isScreenSaverEnabled;
\r
430 private java.util.Timer keepAliveTimer;
\r
432 public final ComponentPeerView createNewView (boolean opaque, long host)
\r
434 ComponentPeerView v = new ComponentPeerView (this, opaque, host);
\r
435 viewHolder.addView (v);
\r
439 public final void deleteView (ComponentPeerView view)
\r
443 ViewGroup group = (ViewGroup) (view.getParent());
\r
446 group.removeView (view);
\r
449 public final void deleteNativeSurfaceView (NativeSurfaceView view)
\r
451 ViewGroup group = (ViewGroup) (view.getParent());
\r
454 group.removeView (view);
\r
457 final class ViewHolder extends ViewGroup
\r
459 public ViewHolder (Context context)
\r
462 setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
\r
463 setFocusable (false);
\r
466 protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
468 setScreenSize (getWidth(), getHeight(), getDPI());
\r
472 isFirstResize = false;
\r
477 private final int getDPI()
\r
479 DisplayMetrics metrics = new DisplayMetrics();
\r
480 getWindowManager().getDefaultDisplay().getMetrics (metrics);
\r
481 return metrics.densityDpi;
\r
484 private boolean isFirstResize = true;
\r
487 public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
\r
489 canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
\r
492 //==============================================================================
\r
493 public final void setScreenSaver (boolean enabled)
\r
495 if (isScreenSaverEnabled != enabled)
\r
497 isScreenSaverEnabled = enabled;
\r
499 if (keepAliveTimer != null)
\r
501 keepAliveTimer.cancel();
\r
502 keepAliveTimer = null;
\r
507 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
511 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
\r
513 // If no user input is received after about 3 seconds, the OS will lower the
\r
514 // task's priority, so this timer forces it to be kept active.
\r
515 keepAliveTimer = new java.util.Timer();
\r
517 keepAliveTimer.scheduleAtFixedRate (new TimerTask()
\r
522 android.app.Instrumentation instrumentation = new android.app.Instrumentation();
\r
526 instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
\r
528 catch (Exception e)
\r
537 public final boolean getScreenSaver()
\r
539 return isScreenSaverEnabled;
\r
542 //==============================================================================
\r
543 public final String getClipboardContent()
\r
545 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
546 return clipboard.getText().toString();
\r
549 public final void setClipboardContent (String newText)
\r
551 ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
\r
552 clipboard.setText (newText);
\r
555 //==============================================================================
\r
556 public final void showMessageBox (String title, String message, final long callback)
\r
558 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
559 builder.setTitle (title)
\r
560 .setMessage (message)
\r
561 .setCancelable (true)
\r
562 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
564 public void onCancel (DialogInterface dialog)
\r
566 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
569 .setPositiveButton ("OK", new DialogInterface.OnClickListener()
\r
571 public void onClick (DialogInterface dialog, int id)
\r
574 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
578 builder.create().show();
\r
581 public final void showOkCancelBox (String title, String message, final long callback,
\r
582 String okButtonText, String cancelButtonText)
\r
584 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
585 builder.setTitle (title)
\r
586 .setMessage (message)
\r
587 .setCancelable (true)
\r
588 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
590 public void onCancel (DialogInterface dialog)
\r
592 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
595 .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()
\r
597 public void onClick (DialogInterface dialog, int id)
\r
600 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);
\r
603 .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()
\r
605 public void onClick (DialogInterface dialog, int id)
\r
608 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
612 builder.create().show();
\r
615 public final void showYesNoCancelBox (String title, String message, final long callback)
\r
617 AlertDialog.Builder builder = new AlertDialog.Builder (this);
\r
618 builder.setTitle (title)
\r
619 .setMessage (message)
\r
620 .setCancelable (true)
\r
621 .setOnCancelListener (new DialogInterface.OnCancelListener()
\r
623 public void onCancel (DialogInterface dialog)
\r
625 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
628 .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
\r
630 public void onClick (DialogInterface dialog, int id)
\r
633 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);
\r
636 .setNegativeButton ("No", new DialogInterface.OnClickListener()
\r
638 public void onClick (DialogInterface dialog, int id)
\r
641 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 2);
\r
644 .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
\r
646 public void onClick (DialogInterface dialog, int id)
\r
649 JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);
\r
653 builder.create().show();
\r
656 public native void alertDismissed (long callback, int id);
\r
658 //==============================================================================
\r
659 public final class ComponentPeerView extends ViewGroup
\r
660 implements View.OnFocusChangeListener
\r
662 public ComponentPeerView (Context context, boolean opaque_, long host)
\r
666 setWillNotDraw (false);
\r
669 setFocusable (true);
\r
670 setFocusableInTouchMode (true);
\r
671 setOnFocusChangeListener (this);
\r
673 // swap red and blue colours to match internal opengl texture format
\r
674 ColorMatrix colorMatrix = new ColorMatrix();
\r
676 float[] colorTransform = { 0, 0, 1.0f, 0, 0,
\r
679 0, 0, 0, 1.0f, 0 };
\r
681 colorMatrix.set (colorTransform);
\r
682 paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
\r
684 java.lang.reflect.Method method = null;
\r
688 method = getClass().getMethod ("setLayerType", int.class, Paint.class);
\r
690 catch (SecurityException e) {}
\r
691 catch (NoSuchMethodException e) {}
\r
693 if (method != null)
\r
697 int layerTypeNone = 0;
\r
698 method.invoke (this, layerTypeNone, null);
\r
700 catch (java.lang.IllegalArgumentException e) {}
\r
701 catch (java.lang.IllegalAccessException e) {}
\r
702 catch (java.lang.reflect.InvocationTargetException e) {}
\r
706 //==============================================================================
\r
707 private native void handlePaint (long host, Canvas canvas, Paint paint);
\r
710 public void onDraw (Canvas canvas)
\r
715 handlePaint (host, canvas, paint);
\r
719 public boolean isOpaque()
\r
724 private boolean opaque;
\r
726 private Paint paint = new Paint();
\r
728 //==============================================================================
\r
729 private native void handleMouseDown (long host, int index, float x, float y, long time);
\r
730 private native void handleMouseDrag (long host, int index, float x, float y, long time);
\r
731 private native void handleMouseUp (long host, int index, float x, float y, long time);
\r
734 public boolean onTouchEvent (MotionEvent event)
\r
739 int action = event.getAction();
\r
740 long time = event.getEventTime();
\r
742 switch (action & MotionEvent.ACTION_MASK)
\r
744 case MotionEvent.ACTION_DOWN:
\r
745 handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
748 case MotionEvent.ACTION_CANCEL:
\r
749 case MotionEvent.ACTION_UP:
\r
750 handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
\r
753 case MotionEvent.ACTION_MOVE:
\r
755 int n = event.getPointerCount();
\r
756 for (int i = 0; i < n; ++i)
\r
757 handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
762 case MotionEvent.ACTION_POINTER_UP:
\r
764 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
765 handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
769 case MotionEvent.ACTION_POINTER_DOWN:
\r
771 int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
\r
772 handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
\r
783 //==============================================================================
\r
784 private native void handleKeyDown (long host, int keycode, int textchar);
\r
785 private native void handleKeyUp (long host, int keycode, int textchar);
\r
786 private native void handleBackButton (long host);
\r
787 private native void handleKeyboardHidden (long host);
\r
789 public void showKeyboard (String type)
\r
791 InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
\r
795 if (type.length() > 0)
\r
797 imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
\r
798 imm.setInputMethod (getWindowToken(), type);
\r
799 keyboardDismissListener.startListening();
\r
803 imm.hideSoftInputFromWindow (getWindowToken(), 0);
\r
804 keyboardDismissListener.stopListening();
\r
809 public void backButtonPressed()
\r
814 handleBackButton (host);
\r
818 public boolean onKeyDown (int keyCode, KeyEvent event)
\r
825 case KeyEvent.KEYCODE_VOLUME_UP:
\r
826 case KeyEvent.KEYCODE_VOLUME_DOWN:
\r
827 return super.onKeyDown (keyCode, event);
\r
828 case KeyEvent.KEYCODE_BACK:
\r
830 ((Activity) getContext()).onBackPressed();
\r
838 handleKeyDown (host, keyCode, event.getUnicodeChar());
\r
843 public boolean onKeyUp (int keyCode, KeyEvent event)
\r
848 handleKeyUp (host, keyCode, event.getUnicodeChar());
\r
853 public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
\r
858 if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
\r
859 return super.onKeyMultiple (keyCode, count, event);
\r
861 if (event.getCharacters() != null)
\r
863 int utf8Char = event.getCharacters().codePointAt (0);
\r
864 handleKeyDown (host, utf8Char, utf8Char);
\r
871 //==============================================================================
\r
872 private final class KeyboardDismissListener
\r
874 public KeyboardDismissListener (ComponentPeerView viewToUse)
\r
879 private void startListening()
\r
881 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);
\r
884 private void stopListening()
\r
886 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);
\r
889 private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener
\r
892 public void onGlobalLayout()
\r
894 Rect r = new Rect();
\r
896 view.getWindowVisibleDisplayFrame(r);
\r
898 int diff = view.getHeight() - (r.bottom - r.top);
\r
900 // Arbitrary threshold, surely keyboard would take more than 20 pix.
\r
902 handleKeyboardHidden (view.host);
\r
906 private ComponentPeerView view;
\r
907 private TreeObserver viewTreeObserver = new TreeObserver();
\r
910 private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);
\r
912 // this is here to make keyboard entry work on a Galaxy Tab2 10.1
\r
914 public InputConnection onCreateInputConnection (EditorInfo outAttrs)
\r
916 outAttrs.actionLabel = "";
\r
917 outAttrs.hintText = "";
\r
918 outAttrs.initialCapsMode = 0;
\r
919 outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
\r
920 outAttrs.label = "";
\r
921 outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
\r
922 outAttrs.inputType = InputType.TYPE_NULL;
\r
924 return new BaseInputConnection (this, false);
\r
927 //==============================================================================
\r
929 protected void onSizeChanged (int w, int h, int oldw, int oldh)
\r
934 super.onSizeChanged (w, h, oldw, oldh);
\r
935 viewSizeChanged (host);
\r
939 protected void onLayout (boolean changed, int left, int top, int right, int bottom)
\r
941 for (int i = getChildCount(); --i >= 0;)
\r
942 requestTransparentRegion (getChildAt (i));
\r
945 private native void viewSizeChanged (long host);
\r
948 public void onFocusChange (View v, boolean hasFocus)
\r
954 focusChanged (host, hasFocus);
\r
957 private native void focusChanged (long host, boolean hasFocus);
\r
959 public void setViewName (String newName) {}
\r
961 public void setSystemUiVisibilityCompat (int visibility)
\r
963 Method systemUIVisibilityMethod = null;
\r
966 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);
\r
968 catch (SecurityException e) { return; }
\r
969 catch (NoSuchMethodException e) { return; }
\r
970 if (systemUIVisibilityMethod == null) return;
\r
974 systemUIVisibilityMethod.invoke (this, visibility);
\r
976 catch (java.lang.IllegalArgumentException e) {}
\r
977 catch (java.lang.IllegalAccessException e) {}
\r
978 catch (java.lang.reflect.InvocationTargetException e) {}
\r
981 public boolean isVisible() { return getVisibility() == VISIBLE; }
\r
982 public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
\r
984 public boolean containsPoint (int x, int y)
\r
986 return true; //xxx needs to check overlapping views
\r
989 //==============================================================================
\r
990 private native void handleAppResumed (long host);
\r
992 public void appResumed()
\r
997 handleAppResumed (host);
\r
1001 //==============================================================================
\r
1002 public static class NativeSurfaceView extends SurfaceView
\r
1003 implements SurfaceHolder.Callback
\r
1005 private long nativeContext = 0;
\r
1007 NativeSurfaceView (Context context, long nativeContextPtr)
\r
1010 nativeContext = nativeContextPtr;
\r
1013 public Surface getNativeSurface()
\r
1015 Surface retval = null;
\r
1017 SurfaceHolder holder = getHolder();
\r
1018 if (holder != null)
\r
1019 retval = holder.getSurface();
\r
1024 //==============================================================================
\r
1026 public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
\r
1028 surfaceChangedNative (nativeContext, holder, format, width, height);
\r
1032 public void surfaceCreated (SurfaceHolder holder)
\r
1034 surfaceCreatedNative (nativeContext, holder);
\r
1038 public void surfaceDestroyed (SurfaceHolder holder)
\r
1040 surfaceDestroyedNative (nativeContext, holder);
\r
1044 protected void dispatchDraw (Canvas canvas)
\r
1046 super.dispatchDraw (canvas);
\r
1047 dispatchDrawNative (nativeContext, canvas);
\r
1050 //==============================================================================
\r
1052 protected void onAttachedToWindow ()
\r
1054 super.onAttachedToWindow();
\r
1055 getHolder().addCallback (this);
\r
1059 protected void onDetachedFromWindow ()
\r
1061 super.onDetachedFromWindow();
\r
1062 getHolder().removeCallback (this);
\r
1065 //==============================================================================
\r
1066 private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
\r
1067 private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
\r
1068 private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
\r
1069 private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
\r
1070 int format, int width, int height);
\r
1073 public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
\r
1075 return new NativeSurfaceView (this, nativeSurfacePtr);
\r
1078 //==============================================================================
\r
1079 public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)
\r
1081 Path p = new Path();
\r
1083 char[] str = { glyph1, glyph2 };
\r
1084 paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);
\r
1086 RectF boundsF = new RectF();
\r
1087 p.computeBounds (boundsF, true);
\r
1088 matrix.mapRect (boundsF);
\r
1090 boundsF.roundOut (bounds);
\r
1094 final int w = bounds.width();
\r
1095 final int h = Math.max (1, bounds.height());
\r
1097 Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
\r
1099 Canvas c = new Canvas (bm);
\r
1100 matrix.postTranslate (-bounds.left, -bounds.top);
\r
1101 c.setMatrix (matrix);
\r
1102 c.drawPath (p, paint);
\r
1104 final int sizeNeeded = w * h;
\r
1105 if (cachedRenderArray.length < sizeNeeded)
\r
1106 cachedRenderArray = new int [sizeNeeded];
\r
1108 bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
\r
1110 return cachedRenderArray;
\r
1113 private int[] cachedRenderArray = new int [256];
\r
1115 //==============================================================================
\r
1116 public static class NativeInvocationHandler implements InvocationHandler
\r
1118 public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)
\r
1120 activity = activityToUse;
\r
1121 nativeContext = nativeContextRef;
\r
1124 public void nativeContextDeleted()
\r
1126 nativeContext = 0;
\r
1130 public void finalize()
\r
1132 activity.runOnUiThread (new Runnable()
\r
1137 if (nativeContext != 0)
\r
1138 dispatchFinalize (nativeContext);
\r
1144 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable
\r
1146 return dispatchInvoke (nativeContext, proxy, method, args);
\r
1149 //==============================================================================
\r
1150 Activity activity;
\r
1151 private long nativeContext = 0;
\r
1153 private native void dispatchFinalize (long nativeContextRef);
\r
1154 private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);
\r
1157 public InvocationHandler createInvocationHandler (long nativeContextRef)
\r
1159 return new NativeInvocationHandler (this, nativeContextRef);
\r
1162 public void invocationHandlerContextDeleted (InvocationHandler handler)
\r
1164 ((NativeInvocationHandler) handler).nativeContextDeleted();
\r
1167 //==============================================================================
\r
1168 public static class HTTPStream
\r
1170 public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,
\r
1171 String headersToUse, int timeOutMsToUse,
\r
1172 int[] statusCodeToUse, StringBuffer responseHeadersToUse,
\r
1173 int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException
\r
1175 isPost = isPostToUse;
\r
1176 postData = postDataToUse;
\r
1177 headers = headersToUse;
\r
1178 timeOutMs = timeOutMsToUse;
\r
1179 statusCode = statusCodeToUse;
\r
1180 responseHeaders = responseHeadersToUse;
\r
1182 numRedirectsToFollow = numRedirectsToFollowToUse;
\r
1183 httpRequestCmd = httpRequestCmdToUse;
\r
1185 connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);
\r
1188 private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,
\r
1189 String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException
\r
1191 HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());
\r
1195 newConnection.setInstanceFollowRedirects (false);
\r
1196 newConnection.setConnectTimeout (timeOutMs);
\r
1197 newConnection.setReadTimeout (timeOutMs);
\r
1199 // 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
1200 // So convert headers string to an array, with an element for each line
\r
1201 String headerLines[] = headers.split("\\n");
\r
1203 // Set request headers
\r
1204 for (int i = 0; i < headerLines.length; ++i)
\r
1206 int pos = headerLines[i].indexOf (":");
\r
1208 if (pos > 0 && pos < headerLines[i].length())
\r
1210 String field = headerLines[i].substring (0, pos);
\r
1211 String value = headerLines[i].substring (pos + 1);
\r
1213 if (value.length() > 0)
\r
1214 newConnection.setRequestProperty (field, value);
\r
1218 newConnection.setRequestMethod (httpRequestCmd);
\r
1222 newConnection.setDoOutput (true);
\r
1224 if (postData != null)
\r
1226 OutputStream out = newConnection.getOutputStream();
\r
1227 out.write(postData);
\r
1232 return newConnection;
\r
1234 catch (Throwable e)
\r
1236 newConnection.disconnect();
\r
1237 throw new IOException ("Connection error");
\r
1241 private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException
\r
1243 synchronized (createFutureLock)
\r
1245 if (hasBeenCancelled.get())
\r
1248 streamFuture = executor.submit (new Callable<BufferedInputStream>()
\r
1251 public BufferedInputStream call() throws IOException
\r
1253 return new BufferedInputStream (isInput ? connection.getInputStream()
\r
1254 : connection.getErrorStream());
\r
1261 return streamFuture.get();
\r
1263 catch (InterruptedException e)
\r
1267 catch (CancellationException e)
\r
1273 public final boolean connect()
\r
1275 boolean result = false;
\r
1276 int numFollowedRedirects = 0;
\r
1280 result = doConnect();
\r
1285 if (++numFollowedRedirects > numRedirectsToFollow)
\r
1288 int status = statusCode[0];
\r
1290 if (status == 301 || status == 302 || status == 303 || status == 307)
\r
1292 // Assumes only one occurrence of "Location"
\r
1293 int pos1 = responseHeaders.indexOf ("Location:") + 10;
\r
1294 int pos2 = responseHeaders.indexOf ("\n", pos1);
\r
1298 String currentLocation = connection.getURL().toString();
\r
1299 String newLocation = responseHeaders.substring (pos1, pos2);
\r
1303 // Handle newLocation whether it's absolute or relative
\r
1304 URL baseUrl = new URL (currentLocation);
\r
1305 URL newUrl = new URL (baseUrl, newLocation);
\r
1306 String transformedNewLocation = newUrl.toString();
\r
1308 if (transformedNewLocation != currentLocation)
\r
1310 // Clear responseHeaders before next iteration
\r
1311 responseHeaders.delete (0, responseHeaders.length());
\r
1313 synchronized (createStreamLock)
\r
1315 if (hasBeenCancelled.get())
\r
1318 connection.disconnect();
\r
1322 connection = createConnection (transformedNewLocation, isPost,
\r
1323 postData, headers, timeOutMs,
\r
1326 catch (Throwable e)
\r
1337 catch (Throwable e)
\r
1356 private final boolean doConnect()
\r
1358 synchronized (createStreamLock)
\r
1360 if (hasBeenCancelled.get())
\r
1367 inputStream = getCancellableStream (true);
\r
1369 catch (ExecutionException e)
\r
1371 if (connection.getResponseCode() < 400)
\r
1373 statusCode[0] = connection.getResponseCode();
\r
1374 connection.disconnect();
\r
1380 statusCode[0] = connection.getResponseCode();
\r
1385 if (statusCode[0] >= 400)
\r
1386 inputStream = getCancellableStream (false);
\r
1388 inputStream = getCancellableStream (true);
\r
1390 catch (ExecutionException e)
\r
1393 for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
\r
1395 if (entry.getKey() != null && entry.getValue() != null)
\r
1397 responseHeaders.append(entry.getKey() + ": "
\r
1398 + android.text.TextUtils.join(",", entry.getValue()) + "\n");
\r
1400 if (entry.getKey().compareTo ("Content-Length") == 0)
\r
1401 totalLength = Integer.decode (entry.getValue().get (0));
\r
1407 catch (IOException e)
\r
1414 static class DisconnectionRunnable implements Runnable
\r
1416 public DisconnectionRunnable (HttpURLConnection theConnection,
\r
1417 InputStream theInputStream,
\r
1418 ReentrantLock theCreateStreamLock,
\r
1419 Object theCreateFutureLock,
\r
1420 Future<BufferedInputStream> theStreamFuture)
\r
1422 connectionToDisconnect = theConnection;
\r
1423 inputStream = theInputStream;
\r
1424 createStreamLock = theCreateStreamLock;
\r
1425 createFutureLock = theCreateFutureLock;
\r
1426 streamFuture = theStreamFuture;
\r
1433 if (! createStreamLock.tryLock())
\r
1435 synchronized (createFutureLock)
\r
1437 if (streamFuture != null)
\r
1438 streamFuture.cancel (true);
\r
1441 createStreamLock.lock();
\r
1444 if (connectionToDisconnect != null)
\r
1445 connectionToDisconnect.disconnect();
\r
1447 if (inputStream != null)
\r
1448 inputStream.close();
\r
1450 catch (IOException e)
\r
1454 createStreamLock.unlock();
\r
1458 private HttpURLConnection connectionToDisconnect;
\r
1459 private InputStream inputStream;
\r
1460 private ReentrantLock createStreamLock;
\r
1461 private Object createFutureLock;
\r
1462 Future<BufferedInputStream> streamFuture;
\r
1465 public final void release()
\r
1467 DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,
\r
1473 synchronized (createStreamLock)
\r
1475 hasBeenCancelled.set (true);
\r
1477 connection = null;
\r
1480 Thread disconnectionThread = new Thread(disconnectionRunnable);
\r
1481 disconnectionThread.start();
\r
1484 public final int read (byte[] buffer, int numBytes)
\r
1490 synchronized (createStreamLock)
\r
1492 if (inputStream != null)
\r
1493 num = inputStream.read (buffer, 0, numBytes);
\r
1496 catch (IOException e)
\r
1505 public final long getPosition() { return position; }
\r
1506 public final long getTotalLength() { return totalLength; }
\r
1507 public final boolean isExhausted() { return false; }
\r
1508 public final boolean setPosition (long newPos) { return false; }
\r
1510 private boolean isPost;
\r
1511 private byte[] postData;
\r
1512 private String headers;
\r
1513 private int timeOutMs;
\r
1514 String httpRequestCmd;
\r
1515 private HttpURLConnection connection;
\r
1516 private int[] statusCode;
\r
1517 private StringBuffer responseHeaders;
\r
1518 private int totalLength;
\r
1519 private int numRedirectsToFollow;
\r
1520 private InputStream inputStream;
\r
1521 private long position;
\r
1522 private final ReentrantLock createStreamLock = new ReentrantLock();
\r
1523 private final Object createFutureLock = new Object();
\r
1524 private AtomicBoolean hasBeenCancelled = new AtomicBoolean();
\r
1526 private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());
\r
1527 Future<BufferedInputStream> streamFuture;
\r
1530 public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
\r
1531 String headers, int timeOutMs, int[] statusCode,
\r
1532 StringBuffer responseHeaders, int numRedirectsToFollow,
\r
1533 String httpRequestCmd)
\r
1535 // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
\r
1536 if (timeOutMs < 0)
\r
1538 else if (timeOutMs == 0)
\r
1539 timeOutMs = 30000;
\r
1545 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,
\r
1546 timeOutMs, statusCode, responseHeaders,
\r
1547 numRedirectsToFollow, httpRequestCmd);
\r
1549 return httpStream;
\r
1551 catch (Throwable e) {}
\r
1557 public final void launchURL (String url)
\r
1559 startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
\r
1562 private native boolean webViewPageLoadStarted (long host, WebView view, String url);
\r
1563 private native void webViewPageLoadFinished (long host, WebView view, String url);
\r
1564 private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);
\r
1565 private native void webViewCloseWindowRequest (long host, WebView view);
\r
1566 private native void webViewCreateWindowRequest (long host, WebView view);
\r
1568 //==============================================================================
\r
1569 public class JuceWebViewClient extends WebViewClient
\r
1571 public JuceWebViewClient (long hostToUse)
\r
1576 public void hostDeleted()
\r
1578 synchronized (hostLock)
\r
1585 public void onPageFinished (WebView view, String url)
\r
1590 webViewPageLoadFinished (host, view, url);
\r
1594 public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)
\r
1599 webViewReceivedSslError (host, view, handler, error);
\r
1603 public void onPageStarted (WebView view, String url, Bitmap favicon)
\r
1606 webViewPageLoadStarted (host, view, url);
\r
1609 private long host;
\r
1610 private final Object hostLock = new Object();
\r
1613 public class JuceWebChromeClient extends WebChromeClient
\r
1615 public JuceWebChromeClient (long hostToUse)
\r
1621 public void onCloseWindow (WebView window)
\r
1623 webViewCloseWindowRequest (host, window);
\r
1627 public boolean onCreateWindow (WebView view, boolean isDialog,
\r
1628 boolean isUserGesture, Message resultMsg)
\r
1630 webViewCreateWindowRequest (host, view);
\r
1634 private long host;
\r
1635 private final Object hostLock = new Object();
\r
1638 //==============================================================================
\r
1639 public static final String getLocaleValue (boolean isRegion)
\r
1641 java.util.Locale locale = java.util.Locale.getDefault();
\r
1643 return isRegion ? locale.getCountry()
\r
1644 : locale.getLanguage();
\r
1647 private static final String getFileLocation (String type)
\r
1649 return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
\r
1652 public static final String getDocumentsFolder()
\r
1654 if (getAndroidSDKVersion() >= 19)
\r
1655 return getFileLocation ("Documents");
\r
1657 return Environment.getDataDirectory().getAbsolutePath();
\r
1660 public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
\r
1661 public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
\r
1662 public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
\r
1663 public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
\r
1665 //==============================================================================
\r
1667 protected void onActivityResult (int requestCode, int resultCode, Intent data)
\r
1669 appActivityResult (requestCode, resultCode, data);
\r
1673 protected void onNewIntent (Intent intent)
\r
1675 super.onNewIntent(intent);
\r
1676 setIntent(intent);
\r
1678 appNewIntent (intent);
\r
1681 //==============================================================================
\r
1682 public final Typeface getTypeFaceFromAsset (String assetName)
\r
1686 return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
\r
1688 catch (Throwable e) {}
\r
1693 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
\r
1695 public static String bytesToHex (byte[] bytes)
\r
1697 char[] hexChars = new char[bytes.length * 2];
\r
1699 for (int j = 0; j < bytes.length; ++j)
\r
1701 int v = bytes[j] & 0xff;
\r
1702 hexChars[j * 2] = hexArray[v >>> 4];
\r
1703 hexChars[j * 2 + 1] = hexArray[v & 0x0f];
\r
1706 return new String (hexChars);
\r
1709 final private java.util.Map dataCache = new java.util.HashMap();
\r
1711 synchronized private final File getDataCacheFile (byte[] data)
\r
1715 java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
\r
1716 digest.update (data);
\r
1718 String key = bytesToHex (digest.digest());
\r
1720 if (dataCache.containsKey (key))
\r
1721 return (File) dataCache.get (key);
\r
1723 File f = new File (this.getCacheDir(), "bindata_" + key);
\r
1725 FileOutputStream os = new FileOutputStream (f);
\r
1726 os.write (data, 0, data.length);
\r
1727 dataCache.put (key, f);
\r
1730 catch (Throwable e) {}
\r
1735 private final void clearDataCache()
\r
1737 java.util.Iterator it = dataCache.values().iterator();
\r
1739 while (it.hasNext())
\r
1741 File f = (File) it.next();
\r
1746 public final Typeface getTypeFaceFromByteArray (byte[] data)
\r
1750 File f = getDataCacheFile (data);
\r
1753 return Typeface.createFromFile (f);
\r
1755 catch (Exception e)
\r
1757 Log.e ("JUCE", e.toString());
\r
1763 public static final int getAndroidSDKVersion()
\r
1765 return android.os.Build.VERSION.SDK_INT;
\r
1768 public final String audioManagerGetProperty (String property)
\r
1770 Object obj = getSystemService (AUDIO_SERVICE);
\r
1774 java.lang.reflect.Method method;
\r
1778 method = obj.getClass().getMethod ("getProperty", String.class);
\r
1780 catch (SecurityException e) { return null; }
\r
1781 catch (NoSuchMethodException e) { return null; }
\r
1783 if (method == null)
\r
1788 return (String) method.invoke (obj, property);
\r
1790 catch (java.lang.IllegalArgumentException e) {}
\r
1791 catch (java.lang.IllegalAccessException e) {}
\r
1792 catch (java.lang.reflect.InvocationTargetException e) {}
\r
1797 public final boolean hasSystemFeature (String property)
\r
1799 return getPackageManager().hasSystemFeature (property);
\r