9bf2c989a1f05e6dbc0304ce990a28d35df43f73
[juce.git] /
1 /*\r
2   ==============================================================================\r
3 \r
4    This file is part of the JUCE library.\r
5    Copyright (c) 2017 - ROLI Ltd.\r
6 \r
7    JUCE is an open source library subject to commercial or open-source\r
8    licensing.\r
9 \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
15 \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
18    DISCLAIMED.\r
19 \r
20   ==============================================================================\r
21 */\r
22 \r
23 package com.juce.networkgraphicsdemo;\r
24 \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
59 import java.util.*;\r
60 import java.io.*;\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
75 \r
76 \r
77 \r
78 //==============================================================================\r
79 public class JUCENetworkGraphicsDemo   extends Activity\r
80 {\r
81     //==============================================================================\r
82     static\r
83     {\r
84         System.loadLibrary ("juce_jni");\r
85     }\r
86 \r
87     //==============================================================================\r
88     public boolean isPermissionDeclaredInManifest (int permissionID)\r
89     {\r
90         String permissionToCheck = getAndroidPermissionName(permissionID);\r
91 \r
92         try\r
93         {\r
94             PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);\r
95 \r
96             if (info.requestedPermissions != null)\r
97                 for (String permission : info.requestedPermissions)\r
98                     if (permission.equals (permissionToCheck))\r
99                         return true;\r
100         }\r
101         catch (PackageManager.NameNotFoundException e)\r
102         {\r
103             Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());\r
104         }\r
105 \r
106         Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);\r
107         return false;\r
108     }\r
109 \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
116 \r
117     private static String getAndroidPermissionName (int permissionID)\r
118     {\r
119         switch (permissionID)\r
120         {\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
126         }\r
127 \r
128         // unknown permission ID!\r
129         assert false;\r
130         return new String();\r
131     }\r
132 \r
133     public boolean isPermissionGranted (int permissionID)\r
134     {\r
135         return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;\r
136     }\r
137 \r
138     private Map<Integer, Long> permissionCallbackPtrMap;\r
139 \r
140     public void requestRuntimePermission (int permissionID, long ptrToCallback)\r
141     {\r
142         String permissionName = getAndroidPermissionName (permissionID);\r
143 \r
144         if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED)\r
145         {\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
149         }\r
150         else\r
151         {\r
152             // permissions were already granted before, we can call callback directly\r
153             androidRuntimePermissionsCallback (true, ptrToCallback);\r
154         }\r
155     }\r
156 \r
157     private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);\r
158 \r
159 \r
160     //==============================================================================\r
161     public interface JuceMidiPort\r
162     {\r
163         boolean isInputPort();\r
164 \r
165         // start, stop does nothing on an output port\r
166         void start();\r
167         void stop();\r
168 \r
169         void close();\r
170 \r
171         // send will do nothing on an input port\r
172         void sendMidi (byte[] msg, int offset, int count);\r
173     }\r
174 \r
175     //==============================================================================\r
176     //==============================================================================\r
177     public class BluetoothManager\r
178     {\r
179         BluetoothManager()\r
180         {\r
181         }\r
182 \r
183         public String[] getMidiBluetoothAddresses()\r
184         {\r
185             String[] bluetoothAddresses = new String[0];\r
186             return bluetoothAddresses;\r
187         }\r
188 \r
189         public String getHumanReadableStringForBluetoothAddress (String address)\r
190         {\r
191             return address;\r
192         }\r
193 \r
194         public int getBluetoothDeviceStatus (String address)\r
195         {\r
196             return 0;\r
197         }\r
198 \r
199         public void startStopScan (boolean shouldStart)\r
200         {\r
201         }\r
202 \r
203         public boolean pairBluetoothMidiDevice(String address)\r
204         {\r
205             return false;\r
206         }\r
207 \r
208         public void unpairBluetoothMidiDevice (String address)\r
209         {\r
210         }\r
211     }\r
212 \r
213     //==============================================================================\r
214     public class MidiDeviceManager\r
215     {\r
216         public MidiDeviceManager()\r
217         {\r
218         }\r
219 \r
220         public String[] getJuceAndroidMidiInputDevices()\r
221         {\r
222             return new String[0];\r
223         }\r
224 \r
225         public String[] getJuceAndroidMidiOutputDevices()\r
226         {\r
227             return new String[0];\r
228         }\r
229 \r
230         public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)\r
231         {\r
232             return null;\r
233         }\r
234 \r
235         public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)\r
236         {\r
237             return null;\r
238         }\r
239 \r
240         public String getInputPortNameForJuceIndex (int index)\r
241         {\r
242             return "";\r
243         }\r
244 \r
245         public String getOutputPortNameForJuceIndex (int index)\r
246         {\r
247             return "";\r
248         }\r
249     }\r
250 \r
251 \r
252     public MidiDeviceManager getAndroidMidiDeviceManager()\r
253     {\r
254         return null;\r
255     }\r
256 \r
257     public BluetoothManager getAndroidBluetoothManager()\r
258     {\r
259         return null;\r
260     }\r
261 \r
262     //==============================================================================\r
263     @Override\r
264     public void onCreate (Bundle savedInstanceState)\r
265     {\r
266         super.onCreate (savedInstanceState);\r
267 \r
268         isScreenSaverEnabled = true;\r
269         hideActionBar();\r
270         viewHolder = new ViewHolder (this);\r
271         setContentView (viewHolder);\r
272 \r
273         setVolumeControlStream (AudioManager.STREAM_MUSIC);\r
274 \r
275         permissionCallbackPtrMap = new HashMap<Integer, Long>();\r
276     }\r
277 \r
278     @Override\r
279     protected void onDestroy()\r
280     {\r
281         quitApp();\r
282         super.onDestroy();\r
283 \r
284         clearDataCache();\r
285     }\r
286 \r
287     @Override\r
288     protected void onPause()\r
289     {\r
290         suspendApp();\r
291 \r
292         try\r
293         {\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
297 \r
298         super.onPause();\r
299     }\r
300 \r
301     @Override\r
302     protected void onResume()\r
303     {\r
304         super.onResume();\r
305         resumeApp();\r
306 \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
310     }\r
311 \r
312     @Override\r
313     public void onConfigurationChanged (Configuration cfg)\r
314     {\r
315         super.onConfigurationChanged (cfg);\r
316         setContentView (viewHolder);\r
317     }\r
318 \r
319     private void callAppLauncher()\r
320     {\r
321         launchApp (getApplicationInfo().publicSourceDir,\r
322                    getApplicationInfo().dataDir);\r
323     }\r
324 \r
325     // Need to override this as the default implementation always finishes the activity.\r
326     @Override\r
327     public void onBackPressed()\r
328     {\r
329         ComponentPeerView focusedView = getViewWithFocusOrDefaultView();\r
330 \r
331         if (focusedView == null)\r
332             return;\r
333 \r
334         focusedView.backButtonPressed();\r
335     }\r
336 \r
337     private ComponentPeerView getViewWithFocusOrDefaultView()\r
338     {\r
339         for (int i = 0; i < viewHolder.getChildCount(); ++i)\r
340         {\r
341             if (viewHolder.getChildAt (i).hasFocus())\r
342                 return (ComponentPeerView) viewHolder.getChildAt (i);\r
343         }\r
344 \r
345         if (viewHolder.getChildCount() > 0)\r
346             return (ComponentPeerView) viewHolder.getChildAt (0);\r
347 \r
348         return null;\r
349     }\r
350 \r
351     //==============================================================================\r
352     private void hideActionBar()\r
353     {\r
354         // get "getActionBar" method\r
355         java.lang.reflect.Method getActionBarMethod = null;\r
356         try\r
357         {\r
358             getActionBarMethod = this.getClass().getMethod ("getActionBar");\r
359         }\r
360         catch (SecurityException e)     { return; }\r
361         catch (NoSuchMethodException e) { return; }\r
362         if (getActionBarMethod == null) return;\r
363 \r
364         // invoke "getActionBar" method\r
365         Object actionBar = null;\r
366         try\r
367         {\r
368             actionBar = getActionBarMethod.invoke (this);\r
369         }\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
374 \r
375         // get "hide" method\r
376         java.lang.reflect.Method actionBarHideMethod = null;\r
377         try\r
378         {\r
379             actionBarHideMethod = actionBar.getClass().getMethod ("hide");\r
380         }\r
381         catch (SecurityException e)     { return; }\r
382         catch (NoSuchMethodException e) { return; }\r
383         if (actionBarHideMethod == null) return;\r
384 \r
385         // invoke "hide" method\r
386         try\r
387         {\r
388             actionBarHideMethod.invoke (actionBar);\r
389         }\r
390         catch (java.lang.IllegalArgumentException e) {}\r
391         catch (java.lang.IllegalAccessException e) {}\r
392         catch (java.lang.reflect.InvocationTargetException e) {}\r
393     }\r
394 \r
395     void requestPermissionsCompat (String[] permissions, int requestCode)\r
396     {\r
397         Method requestPermissionsMethod = null;\r
398         try\r
399         {\r
400             requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",\r
401                                                                   String[].class, int.class);\r
402         }\r
403         catch (SecurityException e)     { return; }\r
404         catch (NoSuchMethodException e) { return; }\r
405         if (requestPermissionsMethod == null) return;\r
406 \r
407         try\r
408         {\r
409             requestPermissionsMethod.invoke (this, permissions, requestCode);\r
410         }\r
411         catch (java.lang.IllegalArgumentException e) {}\r
412         catch (java.lang.IllegalAccessException e) {}\r
413         catch (java.lang.reflect.InvocationTargetException e) {}\r
414     }\r
415 \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
424 \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
431 \r
432     public final ComponentPeerView createNewView (boolean opaque, long host)\r
433     {\r
434         ComponentPeerView v = new ComponentPeerView (this, opaque, host);\r
435         viewHolder.addView (v);\r
436         return v;\r
437     }\r
438 \r
439     public final void deleteView (ComponentPeerView view)\r
440     {\r
441         view.host = 0;\r
442 \r
443         ViewGroup group = (ViewGroup) (view.getParent());\r
444 \r
445         if (group != null)\r
446             group.removeView (view);\r
447     }\r
448 \r
449     public final void deleteNativeSurfaceView (NativeSurfaceView view)\r
450     {\r
451         ViewGroup group = (ViewGroup) (view.getParent());\r
452 \r
453         if (group != null)\r
454             group.removeView (view);\r
455     }\r
456 \r
457     final class ViewHolder  extends ViewGroup\r
458     {\r
459         public ViewHolder (Context context)\r
460         {\r
461             super (context);\r
462             setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);\r
463             setFocusable (false);\r
464         }\r
465 \r
466         protected final void onLayout (boolean changed, int left, int top, int right, int bottom)\r
467         {\r
468             setScreenSize (getWidth(), getHeight(), getDPI());\r
469 \r
470             if (isFirstResize)\r
471             {\r
472                 isFirstResize = false;\r
473                 callAppLauncher();\r
474             }\r
475         }\r
476 \r
477         private final int getDPI()\r
478         {\r
479             DisplayMetrics metrics = new DisplayMetrics();\r
480             getWindowManager().getDefaultDisplay().getMetrics (metrics);\r
481             return metrics.densityDpi;\r
482         }\r
483 \r
484         private boolean isFirstResize = true;\r
485     }\r
486 \r
487     public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)\r
488     {\r
489         canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);\r
490     }\r
491 \r
492     //==============================================================================\r
493     public final void setScreenSaver (boolean enabled)\r
494     {\r
495         if (isScreenSaverEnabled != enabled)\r
496         {\r
497             isScreenSaverEnabled = enabled;\r
498 \r
499             if (keepAliveTimer != null)\r
500             {\r
501                 keepAliveTimer.cancel();\r
502                 keepAliveTimer = null;\r
503             }\r
504 \r
505             if (enabled)\r
506             {\r
507                 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
508             }\r
509             else\r
510             {\r
511                 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
512 \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
516 \r
517                 keepAliveTimer.scheduleAtFixedRate (new TimerTask()\r
518                 {\r
519                     @Override\r
520                     public void run()\r
521                     {\r
522                         android.app.Instrumentation instrumentation = new android.app.Instrumentation();\r
523 \r
524                         try\r
525                         {\r
526                             instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);\r
527                         }\r
528                         catch (Exception e)\r
529                         {\r
530                         }\r
531                     }\r
532                 }, 2000, 2000);\r
533             }\r
534         }\r
535     }\r
536 \r
537     public final boolean getScreenSaver()\r
538     {\r
539         return isScreenSaverEnabled;\r
540     }\r
541 \r
542     //==============================================================================\r
543     public final String getClipboardContent()\r
544     {\r
545         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
546         return clipboard.getText().toString();\r
547     }\r
548 \r
549     public final void setClipboardContent (String newText)\r
550     {\r
551         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
552         clipboard.setText (newText);\r
553     }\r
554 \r
555     //==============================================================================\r
556     public final void showMessageBox (String title, String message, final long callback)\r
557     {\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
563                     {\r
564                         public void onCancel (DialogInterface dialog)\r
565                         {\r
566                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
567                         }\r
568                     })\r
569                .setPositiveButton ("OK", new DialogInterface.OnClickListener()\r
570                     {\r
571                         public void onClick (DialogInterface dialog, int id)\r
572                         {\r
573                             dialog.dismiss();\r
574                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
575                         }\r
576                     });\r
577 \r
578         builder.create().show();\r
579     }\r
580 \r
581     public final void showOkCancelBox (String title, String message, final long callback,\r
582                                        String okButtonText, String cancelButtonText)\r
583     {\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
589                     {\r
590                         public void onCancel (DialogInterface dialog)\r
591                         {\r
592                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
593                         }\r
594                     })\r
595                .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()\r
596                     {\r
597                         public void onClick (DialogInterface dialog, int id)\r
598                         {\r
599                             dialog.dismiss();\r
600                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);\r
601                         }\r
602                     })\r
603                .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()\r
604                     {\r
605                         public void onClick (DialogInterface dialog, int id)\r
606                         {\r
607                             dialog.dismiss();\r
608                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
609                         }\r
610                     });\r
611 \r
612         builder.create().show();\r
613     }\r
614 \r
615     public final void showYesNoCancelBox (String title, String message, final long callback)\r
616     {\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
622                     {\r
623                         public void onCancel (DialogInterface dialog)\r
624                         {\r
625                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
626                         }\r
627                     })\r
628                .setPositiveButton ("Yes", new DialogInterface.OnClickListener()\r
629                     {\r
630                         public void onClick (DialogInterface dialog, int id)\r
631                         {\r
632                             dialog.dismiss();\r
633                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);\r
634                         }\r
635                     })\r
636                .setNegativeButton ("No", new DialogInterface.OnClickListener()\r
637                     {\r
638                         public void onClick (DialogInterface dialog, int id)\r
639                         {\r
640                             dialog.dismiss();\r
641                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 2);\r
642                         }\r
643                     })\r
644                .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()\r
645                     {\r
646                         public void onClick (DialogInterface dialog, int id)\r
647                         {\r
648                             dialog.dismiss();\r
649                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
650                         }\r
651                     });\r
652 \r
653         builder.create().show();\r
654     }\r
655 \r
656     public native void alertDismissed (long callback, int id);\r
657 \r
658     //==============================================================================\r
659     public final class ComponentPeerView extends ViewGroup\r
660                                          implements View.OnFocusChangeListener\r
661     {\r
662         public ComponentPeerView (Context context, boolean opaque_, long host)\r
663         {\r
664             super (context);\r
665             this.host = host;\r
666             setWillNotDraw (false);\r
667             opaque = opaque_;\r
668 \r
669             setFocusable (true);\r
670             setFocusableInTouchMode (true);\r
671             setOnFocusChangeListener (this);\r
672 \r
673             // swap red and blue colours to match internal opengl texture format\r
674             ColorMatrix colorMatrix = new ColorMatrix();\r
675 \r
676             float[] colorTransform = { 0,    0,    1.0f, 0,    0,\r
677                                        0,    1.0f, 0,    0,    0,\r
678                                        1.0f, 0,    0,    0,    0,\r
679                                        0,    0,    0,    1.0f, 0 };\r
680 \r
681             colorMatrix.set (colorTransform);\r
682             paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));\r
683 \r
684             java.lang.reflect.Method method = null;\r
685 \r
686             try\r
687             {\r
688                 method = getClass().getMethod ("setLayerType", int.class, Paint.class);\r
689             }\r
690             catch (SecurityException e)     {}\r
691             catch (NoSuchMethodException e) {}\r
692 \r
693             if (method != null)\r
694             {\r
695                 try\r
696                 {\r
697                     int layerTypeNone = 0;\r
698                     method.invoke (this, layerTypeNone, null);\r
699                 }\r
700                 catch (java.lang.IllegalArgumentException e) {}\r
701                 catch (java.lang.IllegalAccessException e) {}\r
702                 catch (java.lang.reflect.InvocationTargetException e) {}\r
703             }\r
704         }\r
705 \r
706         //==============================================================================\r
707         private native void handlePaint (long host, Canvas canvas, Paint paint);\r
708 \r
709         @Override\r
710         public void onDraw (Canvas canvas)\r
711         {\r
712             if (host == 0)\r
713                 return;\r
714 \r
715             handlePaint (host, canvas, paint);\r
716         }\r
717 \r
718         @Override\r
719         public boolean isOpaque()\r
720         {\r
721             return opaque;\r
722         }\r
723 \r
724         private boolean opaque;\r
725         private long host;\r
726         private Paint paint = new Paint();\r
727 \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
732 \r
733         @Override\r
734         public boolean onTouchEvent (MotionEvent event)\r
735         {\r
736             if (host == 0)\r
737                 return false;\r
738 \r
739             int action = event.getAction();\r
740             long time = event.getEventTime();\r
741 \r
742             switch (action & MotionEvent.ACTION_MASK)\r
743             {\r
744                 case MotionEvent.ACTION_DOWN:\r
745                     handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
746                     return true;\r
747 \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
751                     return true;\r
752 \r
753                 case MotionEvent.ACTION_MOVE:\r
754                 {\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
758 \r
759                     return true;\r
760                 }\r
761 \r
762                 case MotionEvent.ACTION_POINTER_UP:\r
763                 {\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
766                     return true;\r
767                 }\r
768 \r
769                 case MotionEvent.ACTION_POINTER_DOWN:\r
770                 {\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
773                     return true;\r
774                 }\r
775 \r
776                 default:\r
777                     break;\r
778             }\r
779 \r
780             return false;\r
781         }\r
782 \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
788 \r
789         public void showKeyboard (String type)\r
790         {\r
791             InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);\r
792 \r
793             if (imm != null)\r
794             {\r
795                 if (type.length() > 0)\r
796                 {\r
797                     imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);\r
798                     imm.setInputMethod (getWindowToken(), type);\r
799                     keyboardDismissListener.startListening();\r
800                 }\r
801                 else\r
802                 {\r
803                     imm.hideSoftInputFromWindow (getWindowToken(), 0);\r
804                     keyboardDismissListener.stopListening();\r
805                 }\r
806             }\r
807         }\r
808 \r
809         public void backButtonPressed()\r
810         {\r
811             if (host == 0)\r
812                 return;\r
813 \r
814             handleBackButton (host);\r
815         }\r
816 \r
817         @Override\r
818         public boolean onKeyDown (int keyCode, KeyEvent event)\r
819         {\r
820             if (host == 0)\r
821                 return false;\r
822 \r
823             switch (keyCode)\r
824             {\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
829                 {\r
830                     ((Activity) getContext()).onBackPressed();\r
831                     return true;\r
832                 }\r
833 \r
834                 default:\r
835                     break;\r
836             }\r
837 \r
838             handleKeyDown (host, keyCode, event.getUnicodeChar());\r
839             return true;\r
840         }\r
841 \r
842         @Override\r
843         public boolean onKeyUp (int keyCode, KeyEvent event)\r
844         {\r
845             if (host == 0)\r
846                 return false;\r
847 \r
848             handleKeyUp (host, keyCode, event.getUnicodeChar());\r
849             return true;\r
850         }\r
851 \r
852         @Override\r
853         public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)\r
854         {\r
855             if (host == 0)\r
856                 return false;\r
857 \r
858             if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)\r
859                 return super.onKeyMultiple (keyCode, count, event);\r
860 \r
861             if (event.getCharacters() != null)\r
862             {\r
863                 int utf8Char = event.getCharacters().codePointAt (0);\r
864                 handleKeyDown (host, utf8Char, utf8Char);\r
865                 return true;\r
866             }\r
867 \r
868             return false;\r
869         }\r
870 \r
871         //==============================================================================\r
872         private final class KeyboardDismissListener\r
873         {\r
874             public KeyboardDismissListener (ComponentPeerView viewToUse)\r
875             {\r
876                 view = viewToUse;\r
877             }\r
878 \r
879             private void startListening()\r
880             {\r
881                 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);\r
882             }\r
883 \r
884             private void stopListening()\r
885             {\r
886                 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);\r
887             }\r
888 \r
889             private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener\r
890             {\r
891                 @Override\r
892                 public void onGlobalLayout()\r
893                 {\r
894                     Rect r = new Rect();\r
895 \r
896                     view.getWindowVisibleDisplayFrame(r);\r
897 \r
898                     int diff = view.getHeight() - (r.bottom - r.top);\r
899 \r
900                     // Arbitrary threshold, surely keyboard would take more than 20 pix.\r
901                     if (diff < 20)\r
902                         handleKeyboardHidden (view.host);\r
903                 };\r
904             };\r
905 \r
906             private ComponentPeerView view;\r
907             private TreeObserver viewTreeObserver = new TreeObserver();\r
908         }\r
909 \r
910         private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);\r
911 \r
912         // this is here to make keyboard entry work on a Galaxy Tab2 10.1\r
913         @Override\r
914         public InputConnection onCreateInputConnection (EditorInfo outAttrs)\r
915         {\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
923 \r
924             return new BaseInputConnection (this, false);\r
925         }\r
926 \r
927         //==============================================================================\r
928         @Override\r
929         protected void onSizeChanged (int w, int h, int oldw, int oldh)\r
930         {\r
931             if (host == 0)\r
932                 return;\r
933 \r
934             super.onSizeChanged (w, h, oldw, oldh);\r
935             viewSizeChanged (host);\r
936         }\r
937 \r
938         @Override\r
939         protected void onLayout (boolean changed, int left, int top, int right, int bottom)\r
940         {\r
941             for (int i = getChildCount(); --i >= 0;)\r
942                 requestTransparentRegion (getChildAt (i));\r
943         }\r
944 \r
945         private native void viewSizeChanged (long host);\r
946 \r
947         @Override\r
948         public void onFocusChange (View v, boolean hasFocus)\r
949         {\r
950             if (host == 0)\r
951                 return;\r
952 \r
953             if (v == this)\r
954                 focusChanged (host, hasFocus);\r
955         }\r
956 \r
957         private native void focusChanged (long host, boolean hasFocus);\r
958 \r
959         public void setViewName (String newName)    {}\r
960 \r
961         public void setSystemUiVisibilityCompat (int visibility)\r
962         {\r
963             Method systemUIVisibilityMethod = null;\r
964             try\r
965             {\r
966                 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);\r
967             }\r
968             catch (SecurityException e)     { return; }\r
969             catch (NoSuchMethodException e) { return; }\r
970             if (systemUIVisibilityMethod == null) return;\r
971 \r
972             try\r
973             {\r
974                 systemUIVisibilityMethod.invoke (this, visibility);\r
975             }\r
976             catch (java.lang.IllegalArgumentException e) {}\r
977             catch (java.lang.IllegalAccessException e) {}\r
978             catch (java.lang.reflect.InvocationTargetException e) {}\r
979         }\r
980 \r
981         public boolean isVisible()                  { return getVisibility() == VISIBLE; }\r
982         public void setVisible (boolean b)          { setVisibility (b ? VISIBLE : INVISIBLE); }\r
983 \r
984         public boolean containsPoint (int x, int y)\r
985         {\r
986             return true; //xxx needs to check overlapping views\r
987         }\r
988 \r
989         //==============================================================================\r
990         private native void handleAppResumed (long host);\r
991 \r
992         public void appResumed()\r
993         {\r
994             if (host == 0)\r
995                 return;\r
996 \r
997             handleAppResumed (host);\r
998         }\r
999     }\r
1000 \r
1001     //==============================================================================\r
1002     public static class NativeSurfaceView    extends SurfaceView\r
1003                                           implements SurfaceHolder.Callback\r
1004     {\r
1005         private long nativeContext = 0;\r
1006 \r
1007         NativeSurfaceView (Context context, long nativeContextPtr)\r
1008         {\r
1009             super (context);\r
1010             nativeContext = nativeContextPtr;\r
1011         }\r
1012 \r
1013         public Surface getNativeSurface()\r
1014         {\r
1015             Surface retval = null;\r
1016 \r
1017             SurfaceHolder holder = getHolder();\r
1018             if (holder != null)\r
1019                 retval = holder.getSurface();\r
1020 \r
1021             return retval;\r
1022         }\r
1023 \r
1024         //==============================================================================\r
1025         @Override\r
1026         public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)\r
1027         {\r
1028             surfaceChangedNative (nativeContext, holder, format, width, height);\r
1029         }\r
1030 \r
1031         @Override\r
1032         public void surfaceCreated (SurfaceHolder holder)\r
1033         {\r
1034             surfaceCreatedNative (nativeContext, holder);\r
1035         }\r
1036 \r
1037         @Override\r
1038         public void surfaceDestroyed (SurfaceHolder holder)\r
1039         {\r
1040             surfaceDestroyedNative (nativeContext, holder);\r
1041         }\r
1042 \r
1043         @Override\r
1044         protected void dispatchDraw (Canvas canvas)\r
1045         {\r
1046             super.dispatchDraw (canvas);\r
1047             dispatchDrawNative (nativeContext, canvas);\r
1048         }\r
1049 \r
1050         //==============================================================================\r
1051         @Override\r
1052         protected void onAttachedToWindow ()\r
1053         {\r
1054             super.onAttachedToWindow();\r
1055             getHolder().addCallback (this);\r
1056         }\r
1057 \r
1058         @Override\r
1059         protected void onDetachedFromWindow ()\r
1060         {\r
1061             super.onDetachedFromWindow();\r
1062             getHolder().removeCallback (this);\r
1063         }\r
1064 \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
1071     }\r
1072 \r
1073     public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)\r
1074     {\r
1075         return new NativeSurfaceView (this, nativeSurfacePtr);\r
1076     }\r
1077 \r
1078     //==============================================================================\r
1079     public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)\r
1080     {\r
1081         Path p = new Path();\r
1082 \r
1083         char[] str = { glyph1, glyph2 };\r
1084         paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);\r
1085 \r
1086         RectF boundsF = new RectF();\r
1087         p.computeBounds (boundsF, true);\r
1088         matrix.mapRect (boundsF);\r
1089 \r
1090         boundsF.roundOut (bounds);\r
1091         bounds.left--;\r
1092         bounds.right++;\r
1093 \r
1094         final int w = bounds.width();\r
1095         final int h = Math.max (1, bounds.height());\r
1096 \r
1097         Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);\r
1098 \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
1103 \r
1104         final int sizeNeeded = w * h;\r
1105         if (cachedRenderArray.length < sizeNeeded)\r
1106             cachedRenderArray = new int [sizeNeeded];\r
1107 \r
1108         bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);\r
1109         bm.recycle();\r
1110         return cachedRenderArray;\r
1111     }\r
1112 \r
1113     private int[] cachedRenderArray = new int [256];\r
1114 \r
1115     //==============================================================================\r
1116     public static class NativeInvocationHandler implements InvocationHandler\r
1117     {\r
1118         public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)\r
1119         {\r
1120             activity = activityToUse;\r
1121             nativeContext = nativeContextRef;\r
1122         }\r
1123 \r
1124         public void nativeContextDeleted()\r
1125         {\r
1126             nativeContext = 0;\r
1127         }\r
1128 \r
1129         @Override\r
1130         public void finalize()\r
1131         {\r
1132             activity.runOnUiThread (new Runnable()\r
1133                                     {\r
1134                                         @Override\r
1135                                         public void run()\r
1136                                         {\r
1137                                             if (nativeContext != 0)\r
1138                                                 dispatchFinalize (nativeContext);\r
1139                                         }\r
1140                                     });\r
1141         }\r
1142 \r
1143         @Override\r
1144         public Object invoke (Object proxy, Method method, Object[] args) throws Throwable\r
1145         {\r
1146             return dispatchInvoke (nativeContext, proxy, method, args);\r
1147         }\r
1148 \r
1149         //==============================================================================\r
1150         Activity activity;\r
1151         private long nativeContext = 0;\r
1152 \r
1153         private native void dispatchFinalize (long nativeContextRef);\r
1154         private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);\r
1155     }\r
1156 \r
1157     public InvocationHandler createInvocationHandler (long nativeContextRef)\r
1158     {\r
1159         return new NativeInvocationHandler (this, nativeContextRef);\r
1160     }\r
1161 \r
1162     public void invocationHandlerContextDeleted (InvocationHandler handler)\r
1163     {\r
1164         ((NativeInvocationHandler) handler).nativeContextDeleted();\r
1165     }\r
1166 \r
1167     //==============================================================================\r
1168     public static class HTTPStream\r
1169     {\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
1174         {\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
1181             totalLength = -1;\r
1182             numRedirectsToFollow = numRedirectsToFollowToUse;\r
1183             httpRequestCmd = httpRequestCmdToUse;\r
1184 \r
1185             connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);\r
1186         }\r
1187 \r
1188         private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,\r
1189                                                           String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException\r
1190         {\r
1191             HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());\r
1192 \r
1193             try\r
1194             {\r
1195                 newConnection.setInstanceFollowRedirects (false);\r
1196                 newConnection.setConnectTimeout (timeOutMs);\r
1197                 newConnection.setReadTimeout (timeOutMs);\r
1198 \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
1202 \r
1203                 // Set request headers\r
1204                 for (int i = 0; i < headerLines.length; ++i)\r
1205                 {\r
1206                     int pos = headerLines[i].indexOf (":");\r
1207 \r
1208                     if (pos > 0 && pos < headerLines[i].length())\r
1209                     {\r
1210                         String field = headerLines[i].substring (0, pos);\r
1211                         String value = headerLines[i].substring (pos + 1);\r
1212 \r
1213                         if (value.length() > 0)\r
1214                             newConnection.setRequestProperty (field, value);\r
1215                     }\r
1216                 }\r
1217 \r
1218                 newConnection.setRequestMethod (httpRequestCmd);\r
1219 \r
1220                 if (isPost)\r
1221                 {\r
1222                     newConnection.setDoOutput (true);\r
1223 \r
1224                     if (postData != null)\r
1225                     {\r
1226                         OutputStream out = newConnection.getOutputStream();\r
1227                         out.write(postData);\r
1228                         out.flush();\r
1229                     }\r
1230                 }\r
1231 \r
1232                 return newConnection;\r
1233             }\r
1234             catch (Throwable e)\r
1235             {\r
1236                 newConnection.disconnect();\r
1237                 throw new IOException ("Connection error");\r
1238             }\r
1239         }\r
1240 \r
1241         private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException\r
1242         {\r
1243             synchronized (createFutureLock)\r
1244             {\r
1245                 if (hasBeenCancelled.get())\r
1246                     return null;\r
1247 \r
1248                 streamFuture = executor.submit (new Callable<BufferedInputStream>()\r
1249                 {\r
1250                     @Override\r
1251                     public BufferedInputStream call() throws IOException\r
1252                     {\r
1253                         return new BufferedInputStream (isInput ? connection.getInputStream()\r
1254                                                                 : connection.getErrorStream());\r
1255                     }\r
1256                 });\r
1257             }\r
1258 \r
1259             try\r
1260             {\r
1261                 return streamFuture.get();\r
1262             }\r
1263             catch (InterruptedException e)\r
1264             {\r
1265                 return null;\r
1266             }\r
1267             catch (CancellationException e)\r
1268             {\r
1269                 return null;\r
1270             }\r
1271         }\r
1272 \r
1273         public final boolean connect()\r
1274         {\r
1275             boolean result = false;\r
1276             int numFollowedRedirects = 0;\r
1277 \r
1278             while (true)\r
1279             {\r
1280                 result = doConnect();\r
1281 \r
1282                 if (! result)\r
1283                     return false;\r
1284 \r
1285                 if (++numFollowedRedirects > numRedirectsToFollow)\r
1286                     break;\r
1287 \r
1288                 int status = statusCode[0];\r
1289 \r
1290                 if (status == 301 || status == 302 || status == 303 || status == 307)\r
1291                 {\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
1295 \r
1296                     if (pos2 > pos1)\r
1297                     {\r
1298                         String currentLocation = connection.getURL().toString();\r
1299                         String newLocation = responseHeaders.substring (pos1, pos2);\r
1300 \r
1301                         try\r
1302                         {\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
1307 \r
1308                             if (transformedNewLocation != currentLocation)\r
1309                             {\r
1310                                 // Clear responseHeaders before next iteration\r
1311                                 responseHeaders.delete (0, responseHeaders.length());\r
1312 \r
1313                                 synchronized (createStreamLock)\r
1314                                 {\r
1315                                     if (hasBeenCancelled.get())\r
1316                                         return false;\r
1317 \r
1318                                     connection.disconnect();\r
1319 \r
1320                                     try\r
1321                                     {\r
1322                                         connection = createConnection (transformedNewLocation, isPost,\r
1323                                                                        postData, headers, timeOutMs,\r
1324                                                                        httpRequestCmd);\r
1325                                     }\r
1326                                     catch (Throwable e)\r
1327                                     {\r
1328                                         return false;\r
1329                                     }\r
1330                                 }\r
1331                             }\r
1332                             else\r
1333                             {\r
1334                                 break;\r
1335                             }\r
1336                         }\r
1337                         catch (Throwable e)\r
1338                         {\r
1339                             return false;\r
1340                         }\r
1341                     }\r
1342                     else\r
1343                     {\r
1344                         break;\r
1345                     }\r
1346                 }\r
1347                 else\r
1348                 {\r
1349                     break;\r
1350                 }\r
1351             }\r
1352 \r
1353             return result;\r
1354         }\r
1355 \r
1356         private final boolean doConnect()\r
1357         {\r
1358             synchronized (createStreamLock)\r
1359             {\r
1360                 if (hasBeenCancelled.get())\r
1361                     return false;\r
1362 \r
1363                 try\r
1364                 {\r
1365                     try\r
1366                     {\r
1367                         inputStream = getCancellableStream (true);\r
1368                     }\r
1369                     catch (ExecutionException e)\r
1370                     {\r
1371                         if (connection.getResponseCode() < 400)\r
1372                         {\r
1373                             statusCode[0] = connection.getResponseCode();\r
1374                             connection.disconnect();\r
1375                             return false;\r
1376                         }\r
1377                     }\r
1378                     finally\r
1379                     {\r
1380                         statusCode[0] = connection.getResponseCode();\r
1381                     }\r
1382 \r
1383                     try\r
1384                     {\r
1385                         if (statusCode[0] >= 400)\r
1386                             inputStream = getCancellableStream (false);\r
1387                         else\r
1388                             inputStream = getCancellableStream (true);\r
1389                     }\r
1390                     catch (ExecutionException e)\r
1391                     {}\r
1392 \r
1393                     for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())\r
1394                     {\r
1395                         if (entry.getKey() != null && entry.getValue() != null)\r
1396                         {\r
1397                             responseHeaders.append(entry.getKey() + ": "\r
1398                                                    + android.text.TextUtils.join(",", entry.getValue()) + "\n");\r
1399 \r
1400                             if (entry.getKey().compareTo ("Content-Length") == 0)\r
1401                                 totalLength = Integer.decode (entry.getValue().get (0));\r
1402                         }\r
1403                     }\r
1404 \r
1405                     return true;\r
1406                 }\r
1407                 catch (IOException e)\r
1408                 {\r
1409                     return false;\r
1410                 }\r
1411             }\r
1412         }\r
1413 \r
1414         static class DisconnectionRunnable implements Runnable\r
1415         {\r
1416             public DisconnectionRunnable (HttpURLConnection theConnection,\r
1417                                           InputStream theInputStream,\r
1418                                           ReentrantLock theCreateStreamLock,\r
1419                                           Object theCreateFutureLock,\r
1420                                           Future<BufferedInputStream> theStreamFuture)\r
1421             {\r
1422                 connectionToDisconnect = theConnection;\r
1423                 inputStream = theInputStream;\r
1424                 createStreamLock = theCreateStreamLock;\r
1425                 createFutureLock = theCreateFutureLock;\r
1426                 streamFuture = theStreamFuture;\r
1427             }\r
1428 \r
1429             public void run()\r
1430             {\r
1431                 try\r
1432                 {\r
1433                     if (! createStreamLock.tryLock())\r
1434                     {\r
1435                         synchronized (createFutureLock)\r
1436                         {\r
1437                             if (streamFuture != null)\r
1438                                 streamFuture.cancel (true);\r
1439                         }\r
1440 \r
1441                         createStreamLock.lock();\r
1442                     }\r
1443 \r
1444                     if (connectionToDisconnect != null)\r
1445                         connectionToDisconnect.disconnect();\r
1446 \r
1447                     if (inputStream != null)\r
1448                         inputStream.close();\r
1449                 }\r
1450                 catch (IOException e)\r
1451                 {}\r
1452                 finally\r
1453                 {\r
1454                     createStreamLock.unlock();\r
1455                 }\r
1456             }\r
1457 \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
1463         }\r
1464 \r
1465         public final void release()\r
1466         {\r
1467             DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,\r
1468                                                                                      inputStream,\r
1469                                                                                      createStreamLock,\r
1470                                                                                      createFutureLock,\r
1471                                                                                      streamFuture);\r
1472 \r
1473             synchronized (createStreamLock)\r
1474             {\r
1475                 hasBeenCancelled.set (true);\r
1476 \r
1477                 connection = null;\r
1478             }\r
1479 \r
1480             Thread disconnectionThread = new Thread(disconnectionRunnable);\r
1481             disconnectionThread.start();\r
1482         }\r
1483 \r
1484         public final int read (byte[] buffer, int numBytes)\r
1485         {\r
1486             int num = 0;\r
1487 \r
1488             try\r
1489             {\r
1490                 synchronized (createStreamLock)\r
1491                 {\r
1492                     if (inputStream != null)\r
1493                         num = inputStream.read (buffer, 0, numBytes);\r
1494                 }\r
1495             }\r
1496             catch (IOException e)\r
1497             {}\r
1498 \r
1499             if (num > 0)\r
1500                 position += num;\r
1501 \r
1502             return num;\r
1503         }\r
1504 \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
1509 \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
1525 \r
1526         private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());\r
1527         Future<BufferedInputStream> streamFuture;\r
1528     }\r
1529 \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
1534     {\r
1535         // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)\r
1536         if (timeOutMs < 0)\r
1537             timeOutMs = 0;\r
1538         else if (timeOutMs == 0)\r
1539             timeOutMs = 30000;\r
1540 \r
1541         for (;;)\r
1542         {\r
1543             try\r
1544             {\r
1545                 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,\r
1546                                                         timeOutMs, statusCode, responseHeaders,\r
1547                                                         numRedirectsToFollow, httpRequestCmd);\r
1548 \r
1549                 return httpStream;\r
1550             }\r
1551             catch (Throwable e) {}\r
1552 \r
1553             return null;\r
1554         }\r
1555     }\r
1556 \r
1557     public final void launchURL (String url)\r
1558     {\r
1559         startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));\r
1560     }\r
1561 \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
1567 \r
1568     //==============================================================================\r
1569     public class JuceWebViewClient   extends WebViewClient\r
1570     {\r
1571         public JuceWebViewClient (long hostToUse)\r
1572         {\r
1573             host = hostToUse;\r
1574         }\r
1575 \r
1576         public void hostDeleted()\r
1577         {\r
1578             synchronized (hostLock)\r
1579             {\r
1580                 host = 0;\r
1581             }\r
1582         }\r
1583 \r
1584         @Override\r
1585         public void onPageFinished (WebView view, String url)\r
1586         {\r
1587             if (host == 0)\r
1588                 return;\r
1589 \r
1590             webViewPageLoadFinished (host, view, url);\r
1591         }\r
1592 \r
1593         @Override\r
1594         public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)\r
1595         {\r
1596             if (host == 0)\r
1597                 return;\r
1598 \r
1599             webViewReceivedSslError (host, view, handler, error);\r
1600         }\r
1601 \r
1602         @Override\r
1603         public void onPageStarted (WebView view, String url, Bitmap favicon)\r
1604         {\r
1605             if (host != 0)\r
1606                 webViewPageLoadStarted (host, view, url);\r
1607         }\r
1608 \r
1609         private long host;\r
1610         private final Object hostLock = new Object();\r
1611     }\r
1612 \r
1613     public class JuceWebChromeClient    extends WebChromeClient\r
1614     {\r
1615         public JuceWebChromeClient (long hostToUse)\r
1616         {\r
1617             host = hostToUse;\r
1618         }\r
1619 \r
1620         @Override\r
1621         public void onCloseWindow (WebView window)\r
1622         {\r
1623             webViewCloseWindowRequest (host, window);\r
1624         }\r
1625 \r
1626         @Override\r
1627         public boolean onCreateWindow (WebView view, boolean isDialog,\r
1628                                        boolean isUserGesture, Message resultMsg)\r
1629         {\r
1630             webViewCreateWindowRequest (host, view);\r
1631             return false;\r
1632         }\r
1633 \r
1634         private long host;\r
1635         private final Object hostLock = new Object();\r
1636     }\r
1637 \r
1638     //==============================================================================\r
1639     public static final String getLocaleValue (boolean isRegion)\r
1640     {\r
1641         java.util.Locale locale = java.util.Locale.getDefault();\r
1642 \r
1643         return isRegion ? locale.getCountry()\r
1644                         : locale.getLanguage();\r
1645     }\r
1646 \r
1647     private static final String getFileLocation (String type)\r
1648     {\r
1649         return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();\r
1650     }\r
1651 \r
1652     public static final String getDocumentsFolder()\r
1653     {\r
1654         if (getAndroidSDKVersion() >= 19)\r
1655             return getFileLocation ("Documents");\r
1656 \r
1657         return Environment.getDataDirectory().getAbsolutePath();\r
1658     }\r
1659 \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
1664 \r
1665     //==============================================================================\r
1666     @Override\r
1667     protected void onActivityResult (int requestCode, int resultCode, Intent data)\r
1668     {\r
1669         appActivityResult (requestCode, resultCode, data);\r
1670     }\r
1671 \r
1672     @Override\r
1673     protected void onNewIntent (Intent intent)\r
1674     {\r
1675         super.onNewIntent(intent);\r
1676         setIntent(intent);\r
1677 \r
1678         appNewIntent (intent);\r
1679     }\r
1680 \r
1681     //==============================================================================\r
1682     public final Typeface getTypeFaceFromAsset (String assetName)\r
1683     {\r
1684         try\r
1685         {\r
1686             return Typeface.createFromAsset (this.getResources().getAssets(), assetName);\r
1687         }\r
1688         catch (Throwable e) {}\r
1689 \r
1690         return null;\r
1691     }\r
1692 \r
1693     final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();\r
1694 \r
1695     public static String bytesToHex (byte[] bytes)\r
1696     {\r
1697         char[] hexChars = new char[bytes.length * 2];\r
1698 \r
1699         for (int j = 0; j < bytes.length; ++j)\r
1700         {\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
1704         }\r
1705 \r
1706         return new String (hexChars);\r
1707     }\r
1708 \r
1709     final private java.util.Map dataCache = new java.util.HashMap();\r
1710 \r
1711     synchronized private final File getDataCacheFile (byte[] data)\r
1712     {\r
1713         try\r
1714         {\r
1715             java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");\r
1716             digest.update (data);\r
1717 \r
1718             String key = bytesToHex (digest.digest());\r
1719 \r
1720             if (dataCache.containsKey (key))\r
1721                 return (File) dataCache.get (key);\r
1722 \r
1723             File f = new File (this.getCacheDir(), "bindata_" + key);\r
1724             f.delete();\r
1725             FileOutputStream os = new FileOutputStream (f);\r
1726             os.write (data, 0, data.length);\r
1727             dataCache.put (key, f);\r
1728             return f;\r
1729         }\r
1730         catch (Throwable e) {}\r
1731 \r
1732         return null;\r
1733     }\r
1734 \r
1735     private final void clearDataCache()\r
1736     {\r
1737         java.util.Iterator it = dataCache.values().iterator();\r
1738 \r
1739         while (it.hasNext())\r
1740         {\r
1741             File f = (File) it.next();\r
1742             f.delete();\r
1743         }\r
1744     }\r
1745 \r
1746     public final Typeface getTypeFaceFromByteArray (byte[] data)\r
1747     {\r
1748         try\r
1749         {\r
1750             File f = getDataCacheFile (data);\r
1751 \r
1752             if (f != null)\r
1753                 return Typeface.createFromFile (f);\r
1754         }\r
1755         catch (Exception e)\r
1756         {\r
1757             Log.e ("JUCE", e.toString());\r
1758         }\r
1759 \r
1760         return null;\r
1761     }\r
1762 \r
1763     public static final int getAndroidSDKVersion()\r
1764     {\r
1765         return android.os.Build.VERSION.SDK_INT;\r
1766     }\r
1767 \r
1768     public final String audioManagerGetProperty (String property)\r
1769     {\r
1770         Object obj = getSystemService (AUDIO_SERVICE);\r
1771         if (obj == null)\r
1772             return null;\r
1773 \r
1774         java.lang.reflect.Method method;\r
1775 \r
1776         try\r
1777         {\r
1778             method = obj.getClass().getMethod ("getProperty", String.class);\r
1779         }\r
1780         catch (SecurityException e)     { return null; }\r
1781         catch (NoSuchMethodException e) { return null; }\r
1782 \r
1783         if (method == null)\r
1784             return null;\r
1785 \r
1786         try\r
1787         {\r
1788             return (String) method.invoke (obj, property);\r
1789         }\r
1790         catch (java.lang.IllegalArgumentException e) {}\r
1791         catch (java.lang.IllegalAccessException e) {}\r
1792         catch (java.lang.reflect.InvocationTargetException e) {}\r
1793 \r
1794         return null;\r
1795     }\r
1796 \r
1797     public final boolean hasSystemFeature (String property)\r
1798     {\r
1799         return getPackageManager().hasSystemFeature (property);\r
1800     }\r
1801 }\r