e37f33ec908692cb2be2eae7d2eb99abea688c8f
[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         {\r
310             if (viewHolder.getChildAt (i) instanceof ComponentPeerView)\r
311                 ((ComponentPeerView) viewHolder.getChildAt (i)).appResumed();\r
312         }\r
313     }\r
314 \r
315     @Override\r
316     public void onConfigurationChanged (Configuration cfg)\r
317     {\r
318         super.onConfigurationChanged (cfg);\r
319         setContentView (viewHolder);\r
320     }\r
321 \r
322     private void callAppLauncher()\r
323     {\r
324         launchApp (getApplicationInfo().publicSourceDir,\r
325                    getApplicationInfo().dataDir);\r
326     }\r
327 \r
328     // Need to override this as the default implementation always finishes the activity.\r
329     @Override\r
330     public void onBackPressed()\r
331     {\r
332         ComponentPeerView focusedView = getViewWithFocusOrDefaultView();\r
333 \r
334         if (focusedView == null)\r
335             return;\r
336 \r
337         focusedView.backButtonPressed();\r
338     }\r
339 \r
340     private ComponentPeerView getViewWithFocusOrDefaultView()\r
341     {\r
342         for (int i = 0; i < viewHolder.getChildCount(); ++i)\r
343         {\r
344             if (viewHolder.getChildAt (i).hasFocus())\r
345                 return (ComponentPeerView) viewHolder.getChildAt (i);\r
346         }\r
347 \r
348         if (viewHolder.getChildCount() > 0)\r
349             return (ComponentPeerView) viewHolder.getChildAt (0);\r
350 \r
351         return null;\r
352     }\r
353 \r
354     //==============================================================================\r
355     private void hideActionBar()\r
356     {\r
357         // get "getActionBar" method\r
358         java.lang.reflect.Method getActionBarMethod = null;\r
359         try\r
360         {\r
361             getActionBarMethod = this.getClass().getMethod ("getActionBar");\r
362         }\r
363         catch (SecurityException e)     { return; }\r
364         catch (NoSuchMethodException e) { return; }\r
365         if (getActionBarMethod == null) return;\r
366 \r
367         // invoke "getActionBar" method\r
368         Object actionBar = null;\r
369         try\r
370         {\r
371             actionBar = getActionBarMethod.invoke (this);\r
372         }\r
373         catch (java.lang.IllegalArgumentException e) { return; }\r
374         catch (java.lang.IllegalAccessException e) { return; }\r
375         catch (java.lang.reflect.InvocationTargetException e) { return; }\r
376         if (actionBar == null) return;\r
377 \r
378         // get "hide" method\r
379         java.lang.reflect.Method actionBarHideMethod = null;\r
380         try\r
381         {\r
382             actionBarHideMethod = actionBar.getClass().getMethod ("hide");\r
383         }\r
384         catch (SecurityException e)     { return; }\r
385         catch (NoSuchMethodException e) { return; }\r
386         if (actionBarHideMethod == null) return;\r
387 \r
388         // invoke "hide" method\r
389         try\r
390         {\r
391             actionBarHideMethod.invoke (actionBar);\r
392         }\r
393         catch (java.lang.IllegalArgumentException e) {}\r
394         catch (java.lang.IllegalAccessException e) {}\r
395         catch (java.lang.reflect.InvocationTargetException e) {}\r
396     }\r
397 \r
398     void requestPermissionsCompat (String[] permissions, int requestCode)\r
399     {\r
400         Method requestPermissionsMethod = null;\r
401         try\r
402         {\r
403             requestPermissionsMethod = this.getClass().getMethod ("requestPermissions",\r
404                                                                   String[].class, int.class);\r
405         }\r
406         catch (SecurityException e)     { return; }\r
407         catch (NoSuchMethodException e) { return; }\r
408         if (requestPermissionsMethod == null) return;\r
409 \r
410         try\r
411         {\r
412             requestPermissionsMethod.invoke (this, permissions, requestCode);\r
413         }\r
414         catch (java.lang.IllegalArgumentException e) {}\r
415         catch (java.lang.IllegalAccessException e) {}\r
416         catch (java.lang.reflect.InvocationTargetException e) {}\r
417     }\r
418 \r
419     //==============================================================================\r
420     private native void launchApp (String appFile, String appDataDir);\r
421     private native void quitApp();\r
422     private native void suspendApp();\r
423     private native void resumeApp();\r
424     private native void setScreenSize (int screenWidth, int screenHeight, int dpi);\r
425     private native void appActivityResult (int requestCode, int resultCode, Intent data);\r
426     private native void appNewIntent (Intent intent);\r
427 \r
428     //==============================================================================\r
429     private ViewHolder viewHolder;\r
430     private MidiDeviceManager midiDeviceManager = null;\r
431     private BluetoothManager bluetoothManager = null;\r
432     private boolean isScreenSaverEnabled;\r
433     private java.util.Timer keepAliveTimer;\r
434 \r
435     public final ComponentPeerView createNewView (boolean opaque, long host)\r
436     {\r
437         ComponentPeerView v = new ComponentPeerView (this, opaque, host);\r
438         viewHolder.addView (v);\r
439         return v;\r
440     }\r
441 \r
442     public final void deleteView (ComponentPeerView view)\r
443     {\r
444         view.host = 0;\r
445 \r
446         ViewGroup group = (ViewGroup) (view.getParent());\r
447 \r
448         if (group != null)\r
449             group.removeView (view);\r
450     }\r
451 \r
452     public final void deleteNativeSurfaceView (NativeSurfaceView view)\r
453     {\r
454         ViewGroup group = (ViewGroup) (view.getParent());\r
455 \r
456         if (group != null)\r
457             group.removeView (view);\r
458     }\r
459 \r
460     final class ViewHolder  extends ViewGroup\r
461     {\r
462         public ViewHolder (Context context)\r
463         {\r
464             super (context);\r
465             setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);\r
466             setFocusable (false);\r
467         }\r
468 \r
469         protected final void onLayout (boolean changed, int left, int top, int right, int bottom)\r
470         {\r
471             setScreenSize (getWidth(), getHeight(), getDPI());\r
472 \r
473             if (isFirstResize)\r
474             {\r
475                 isFirstResize = false;\r
476                 callAppLauncher();\r
477             }\r
478         }\r
479 \r
480         private final int getDPI()\r
481         {\r
482             DisplayMetrics metrics = new DisplayMetrics();\r
483             getWindowManager().getDefaultDisplay().getMetrics (metrics);\r
484             return metrics.densityDpi;\r
485         }\r
486 \r
487         private boolean isFirstResize = true;\r
488     }\r
489 \r
490     public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)\r
491     {\r
492         canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);\r
493     }\r
494 \r
495     //==============================================================================\r
496     public final void setScreenSaver (boolean enabled)\r
497     {\r
498         if (isScreenSaverEnabled != enabled)\r
499         {\r
500             isScreenSaverEnabled = enabled;\r
501 \r
502             if (keepAliveTimer != null)\r
503             {\r
504                 keepAliveTimer.cancel();\r
505                 keepAliveTimer = null;\r
506             }\r
507 \r
508             if (enabled)\r
509             {\r
510                 getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
511             }\r
512             else\r
513             {\r
514                 getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\r
515 \r
516                 // If no user input is received after about 3 seconds, the OS will lower the\r
517                 // task's priority, so this timer forces it to be kept active.\r
518                 keepAliveTimer = new java.util.Timer();\r
519 \r
520                 keepAliveTimer.scheduleAtFixedRate (new TimerTask()\r
521                 {\r
522                     @Override\r
523                     public void run()\r
524                     {\r
525                         android.app.Instrumentation instrumentation = new android.app.Instrumentation();\r
526 \r
527                         try\r
528                         {\r
529                             instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);\r
530                         }\r
531                         catch (Exception e)\r
532                         {\r
533                         }\r
534                     }\r
535                 }, 2000, 2000);\r
536             }\r
537         }\r
538     }\r
539 \r
540     public final boolean getScreenSaver()\r
541     {\r
542         return isScreenSaverEnabled;\r
543     }\r
544 \r
545     //==============================================================================\r
546     public final String getClipboardContent()\r
547     {\r
548         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
549         return clipboard.getText().toString();\r
550     }\r
551 \r
552     public final void setClipboardContent (String newText)\r
553     {\r
554         ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);\r
555         clipboard.setText (newText);\r
556     }\r
557 \r
558     //==============================================================================\r
559     public final void showMessageBox (String title, String message, final long callback)\r
560     {\r
561         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
562         builder.setTitle (title)\r
563                .setMessage (message)\r
564                .setCancelable (true)\r
565                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
566                     {\r
567                         public void onCancel (DialogInterface dialog)\r
568                         {\r
569                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
570                         }\r
571                     })\r
572                .setPositiveButton ("OK", new DialogInterface.OnClickListener()\r
573                     {\r
574                         public void onClick (DialogInterface dialog, int id)\r
575                         {\r
576                             dialog.dismiss();\r
577                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
578                         }\r
579                     });\r
580 \r
581         builder.create().show();\r
582     }\r
583 \r
584     public final void showOkCancelBox (String title, String message, final long callback,\r
585                                        String okButtonText, String cancelButtonText)\r
586     {\r
587         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
588         builder.setTitle (title)\r
589                .setMessage (message)\r
590                .setCancelable (true)\r
591                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
592                     {\r
593                         public void onCancel (DialogInterface dialog)\r
594                         {\r
595                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
596                         }\r
597                     })\r
598                .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener()\r
599                     {\r
600                         public void onClick (DialogInterface dialog, int id)\r
601                         {\r
602                             dialog.dismiss();\r
603                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);\r
604                         }\r
605                     })\r
606                .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener()\r
607                     {\r
608                         public void onClick (DialogInterface dialog, int id)\r
609                         {\r
610                             dialog.dismiss();\r
611                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
612                         }\r
613                     });\r
614 \r
615         builder.create().show();\r
616     }\r
617 \r
618     public final void showYesNoCancelBox (String title, String message, final long callback)\r
619     {\r
620         AlertDialog.Builder builder = new AlertDialog.Builder (this);\r
621         builder.setTitle (title)\r
622                .setMessage (message)\r
623                .setCancelable (true)\r
624                .setOnCancelListener (new DialogInterface.OnCancelListener()\r
625                     {\r
626                         public void onCancel (DialogInterface dialog)\r
627                         {\r
628                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
629                         }\r
630                     })\r
631                .setPositiveButton ("Yes", new DialogInterface.OnClickListener()\r
632                     {\r
633                         public void onClick (DialogInterface dialog, int id)\r
634                         {\r
635                             dialog.dismiss();\r
636                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 1);\r
637                         }\r
638                     })\r
639                .setNegativeButton ("No", new DialogInterface.OnClickListener()\r
640                     {\r
641                         public void onClick (DialogInterface dialog, int id)\r
642                         {\r
643                             dialog.dismiss();\r
644                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 2);\r
645                         }\r
646                     })\r
647                .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()\r
648                     {\r
649                         public void onClick (DialogInterface dialog, int id)\r
650                         {\r
651                             dialog.dismiss();\r
652                             JUCENetworkGraphicsDemo.this.alertDismissed (callback, 0);\r
653                         }\r
654                     });\r
655 \r
656         builder.create().show();\r
657     }\r
658 \r
659     public native void alertDismissed (long callback, int id);\r
660 \r
661     //==============================================================================\r
662     public final class ComponentPeerView extends ViewGroup\r
663                                          implements View.OnFocusChangeListener\r
664     {\r
665         public ComponentPeerView (Context context, boolean opaque_, long host)\r
666         {\r
667             super (context);\r
668             this.host = host;\r
669             setWillNotDraw (false);\r
670             opaque = opaque_;\r
671 \r
672             setFocusable (true);\r
673             setFocusableInTouchMode (true);\r
674             setOnFocusChangeListener (this);\r
675 \r
676             // swap red and blue colours to match internal opengl texture format\r
677             ColorMatrix colorMatrix = new ColorMatrix();\r
678 \r
679             float[] colorTransform = { 0,    0,    1.0f, 0,    0,\r
680                                        0,    1.0f, 0,    0,    0,\r
681                                        1.0f, 0,    0,    0,    0,\r
682                                        0,    0,    0,    1.0f, 0 };\r
683 \r
684             colorMatrix.set (colorTransform);\r
685             paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));\r
686 \r
687             java.lang.reflect.Method method = null;\r
688 \r
689             try\r
690             {\r
691                 method = getClass().getMethod ("setLayerType", int.class, Paint.class);\r
692             }\r
693             catch (SecurityException e)     {}\r
694             catch (NoSuchMethodException e) {}\r
695 \r
696             if (method != null)\r
697             {\r
698                 try\r
699                 {\r
700                     int layerTypeNone = 0;\r
701                     method.invoke (this, layerTypeNone, null);\r
702                 }\r
703                 catch (java.lang.IllegalArgumentException e) {}\r
704                 catch (java.lang.IllegalAccessException e) {}\r
705                 catch (java.lang.reflect.InvocationTargetException e) {}\r
706             }\r
707         }\r
708 \r
709         //==============================================================================\r
710         private native void handlePaint (long host, Canvas canvas, Paint paint);\r
711 \r
712         @Override\r
713         public void onDraw (Canvas canvas)\r
714         {\r
715             if (host == 0)\r
716                 return;\r
717 \r
718             handlePaint (host, canvas, paint);\r
719         }\r
720 \r
721         @Override\r
722         public boolean isOpaque()\r
723         {\r
724             return opaque;\r
725         }\r
726 \r
727         private boolean opaque;\r
728         private long host;\r
729         private Paint paint = new Paint();\r
730 \r
731         //==============================================================================\r
732         private native void handleMouseDown (long host, int index, float x, float y, long time);\r
733         private native void handleMouseDrag (long host, int index, float x, float y, long time);\r
734         private native void handleMouseUp   (long host, int index, float x, float y, long time);\r
735 \r
736         @Override\r
737         public boolean onTouchEvent (MotionEvent event)\r
738         {\r
739             if (host == 0)\r
740                 return false;\r
741 \r
742             int action = event.getAction();\r
743             long time = event.getEventTime();\r
744 \r
745             switch (action & MotionEvent.ACTION_MASK)\r
746             {\r
747                 case MotionEvent.ACTION_DOWN:\r
748                     handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
749                     return true;\r
750 \r
751                 case MotionEvent.ACTION_CANCEL:\r
752                 case MotionEvent.ACTION_UP:\r
753                     handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);\r
754                     return true;\r
755 \r
756                 case MotionEvent.ACTION_MOVE:\r
757                 {\r
758                     int n = event.getPointerCount();\r
759                     for (int i = 0; i < n; ++i)\r
760                         handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
761 \r
762                     return true;\r
763                 }\r
764 \r
765                 case MotionEvent.ACTION_POINTER_UP:\r
766                 {\r
767                     int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;\r
768                     handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
769                     return true;\r
770                 }\r
771 \r
772                 case MotionEvent.ACTION_POINTER_DOWN:\r
773                 {\r
774                     int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;\r
775                     handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);\r
776                     return true;\r
777                 }\r
778 \r
779                 default:\r
780                     break;\r
781             }\r
782 \r
783             return false;\r
784         }\r
785 \r
786         //==============================================================================\r
787         private native void handleKeyDown (long host, int keycode, int textchar);\r
788         private native void handleKeyUp (long host, int keycode, int textchar);\r
789         private native void handleBackButton (long host);\r
790         private native void handleKeyboardHidden (long host);\r
791 \r
792         public void showKeyboard (String type)\r
793         {\r
794             InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);\r
795 \r
796             if (imm != null)\r
797             {\r
798                 if (type.length() > 0)\r
799                 {\r
800                     imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);\r
801                     imm.setInputMethod (getWindowToken(), type);\r
802                     keyboardDismissListener.startListening();\r
803                 }\r
804                 else\r
805                 {\r
806                     imm.hideSoftInputFromWindow (getWindowToken(), 0);\r
807                     keyboardDismissListener.stopListening();\r
808                 }\r
809             }\r
810         }\r
811 \r
812         public void backButtonPressed()\r
813         {\r
814             if (host == 0)\r
815                 return;\r
816 \r
817             handleBackButton (host);\r
818         }\r
819 \r
820         @Override\r
821         public boolean onKeyDown (int keyCode, KeyEvent event)\r
822         {\r
823             if (host == 0)\r
824                 return false;\r
825 \r
826             switch (keyCode)\r
827             {\r
828                 case KeyEvent.KEYCODE_VOLUME_UP:\r
829                 case KeyEvent.KEYCODE_VOLUME_DOWN:\r
830                     return super.onKeyDown (keyCode, event);\r
831                 case KeyEvent.KEYCODE_BACK:\r
832                 {\r
833                     ((Activity) getContext()).onBackPressed();\r
834                     return true;\r
835                 }\r
836 \r
837                 default:\r
838                     break;\r
839             }\r
840 \r
841             handleKeyDown (host, keyCode, event.getUnicodeChar());\r
842             return true;\r
843         }\r
844 \r
845         @Override\r
846         public boolean onKeyUp (int keyCode, KeyEvent event)\r
847         {\r
848             if (host == 0)\r
849                 return false;\r
850 \r
851             handleKeyUp (host, keyCode, event.getUnicodeChar());\r
852             return true;\r
853         }\r
854 \r
855         @Override\r
856         public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)\r
857         {\r
858             if (host == 0)\r
859                 return false;\r
860 \r
861             if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)\r
862                 return super.onKeyMultiple (keyCode, count, event);\r
863 \r
864             if (event.getCharacters() != null)\r
865             {\r
866                 int utf8Char = event.getCharacters().codePointAt (0);\r
867                 handleKeyDown (host, utf8Char, utf8Char);\r
868                 return true;\r
869             }\r
870 \r
871             return false;\r
872         }\r
873 \r
874         //==============================================================================\r
875         private final class KeyboardDismissListener\r
876         {\r
877             public KeyboardDismissListener (ComponentPeerView viewToUse)\r
878             {\r
879                 view = viewToUse;\r
880             }\r
881 \r
882             private void startListening()\r
883             {\r
884                 view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver);\r
885             }\r
886 \r
887             private void stopListening()\r
888             {\r
889                 view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver);\r
890             }\r
891 \r
892             private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener\r
893             {\r
894                 TreeObserver()\r
895                 {\r
896                     keyboardShown = false;\r
897                 }\r
898 \r
899                 @Override\r
900                 public void onGlobalLayout()\r
901                 {\r
902                     Rect r = new Rect();\r
903 \r
904                     ViewGroup parentView = (ViewGroup) getParent();\r
905 \r
906                     if (parentView == null)\r
907                         return;\r
908 \r
909                     parentView.getWindowVisibleDisplayFrame (r);\r
910 \r
911                     int diff = parentView.getHeight() - (r.bottom - r.top);\r
912 \r
913                     // Arbitrary threshold, surely keyboard would take more than 20 pix.\r
914                     if (diff < 20 && keyboardShown)\r
915                     {\r
916                         keyboardShown = false;\r
917                         handleKeyboardHidden (view.host);\r
918                     }\r
919 \r
920                     if (! keyboardShown && diff > 20)\r
921                         keyboardShown = true;\r
922                 };\r
923 \r
924                 private boolean keyboardShown;\r
925             };\r
926 \r
927             private ComponentPeerView view;\r
928             private TreeObserver viewTreeObserver = new TreeObserver();\r
929         }\r
930 \r
931         private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this);\r
932 \r
933         // this is here to make keyboard entry work on a Galaxy Tab2 10.1\r
934         @Override\r
935         public InputConnection onCreateInputConnection (EditorInfo outAttrs)\r
936         {\r
937             outAttrs.actionLabel = "";\r
938             outAttrs.hintText = "";\r
939             outAttrs.initialCapsMode = 0;\r
940             outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;\r
941             outAttrs.label = "";\r
942             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;\r
943             outAttrs.inputType = InputType.TYPE_NULL;\r
944 \r
945             return new BaseInputConnection (this, false);\r
946         }\r
947 \r
948         //==============================================================================\r
949         @Override\r
950         protected void onSizeChanged (int w, int h, int oldw, int oldh)\r
951         {\r
952             if (host == 0)\r
953                 return;\r
954 \r
955             super.onSizeChanged (w, h, oldw, oldh);\r
956             viewSizeChanged (host);\r
957         }\r
958 \r
959         @Override\r
960         protected void onLayout (boolean changed, int left, int top, int right, int bottom)\r
961         {\r
962             for (int i = getChildCount(); --i >= 0;)\r
963                 requestTransparentRegion (getChildAt (i));\r
964         }\r
965 \r
966         private native void viewSizeChanged (long host);\r
967 \r
968         @Override\r
969         public void onFocusChange (View v, boolean hasFocus)\r
970         {\r
971             if (host == 0)\r
972                 return;\r
973 \r
974             if (v == this)\r
975                 focusChanged (host, hasFocus);\r
976         }\r
977 \r
978         private native void focusChanged (long host, boolean hasFocus);\r
979 \r
980         public void setViewName (String newName)    {}\r
981 \r
982         public void setSystemUiVisibilityCompat (int visibility)\r
983         {\r
984             Method systemUIVisibilityMethod = null;\r
985             try\r
986             {\r
987                 systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);\r
988             }\r
989             catch (SecurityException e)     { return; }\r
990             catch (NoSuchMethodException e) { return; }\r
991             if (systemUIVisibilityMethod == null) return;\r
992 \r
993             try\r
994             {\r
995                 systemUIVisibilityMethod.invoke (this, visibility);\r
996             }\r
997             catch (java.lang.IllegalArgumentException e) {}\r
998             catch (java.lang.IllegalAccessException e) {}\r
999             catch (java.lang.reflect.InvocationTargetException e) {}\r
1000         }\r
1001 \r
1002         public boolean isVisible()                  { return getVisibility() == VISIBLE; }\r
1003         public void setVisible (boolean b)          { setVisibility (b ? VISIBLE : INVISIBLE); }\r
1004 \r
1005         public boolean containsPoint (int x, int y)\r
1006         {\r
1007             return true; //xxx needs to check overlapping views\r
1008         }\r
1009 \r
1010         //==============================================================================\r
1011         private native void handleAppResumed (long host);\r
1012 \r
1013         public void appResumed()\r
1014         {\r
1015             if (host == 0)\r
1016                 return;\r
1017 \r
1018             handleAppResumed (host);\r
1019         }\r
1020     }\r
1021 \r
1022     //==============================================================================\r
1023     public static class NativeSurfaceView    extends SurfaceView\r
1024                                           implements SurfaceHolder.Callback\r
1025     {\r
1026         private long nativeContext = 0;\r
1027 \r
1028         NativeSurfaceView (Context context, long nativeContextPtr)\r
1029         {\r
1030             super (context);\r
1031             nativeContext = nativeContextPtr;\r
1032         }\r
1033 \r
1034         public Surface getNativeSurface()\r
1035         {\r
1036             Surface retval = null;\r
1037 \r
1038             SurfaceHolder holder = getHolder();\r
1039             if (holder != null)\r
1040                 retval = holder.getSurface();\r
1041 \r
1042             return retval;\r
1043         }\r
1044 \r
1045         //==============================================================================\r
1046         @Override\r
1047         public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)\r
1048         {\r
1049             surfaceChangedNative (nativeContext, holder, format, width, height);\r
1050         }\r
1051 \r
1052         @Override\r
1053         public void surfaceCreated (SurfaceHolder holder)\r
1054         {\r
1055             surfaceCreatedNative (nativeContext, holder);\r
1056         }\r
1057 \r
1058         @Override\r
1059         public void surfaceDestroyed (SurfaceHolder holder)\r
1060         {\r
1061             surfaceDestroyedNative (nativeContext, holder);\r
1062         }\r
1063 \r
1064         @Override\r
1065         protected void dispatchDraw (Canvas canvas)\r
1066         {\r
1067             super.dispatchDraw (canvas);\r
1068             dispatchDrawNative (nativeContext, canvas);\r
1069         }\r
1070 \r
1071         //==============================================================================\r
1072         @Override\r
1073         protected void onAttachedToWindow ()\r
1074         {\r
1075             super.onAttachedToWindow();\r
1076             getHolder().addCallback (this);\r
1077         }\r
1078 \r
1079         @Override\r
1080         protected void onDetachedFromWindow ()\r
1081         {\r
1082             super.onDetachedFromWindow();\r
1083             getHolder().removeCallback (this);\r
1084         }\r
1085 \r
1086         //==============================================================================\r
1087         private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);\r
1088         private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);\r
1089         private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);\r
1090         private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,\r
1091                                                   int format, int width, int height);\r
1092     }\r
1093 \r
1094     public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)\r
1095     {\r
1096         return new NativeSurfaceView (this, nativeSurfacePtr);\r
1097     }\r
1098 \r
1099     //==============================================================================\r
1100     public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds)\r
1101     {\r
1102         Path p = new Path();\r
1103 \r
1104         char[] str = { glyph1, glyph2 };\r
1105         paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p);\r
1106 \r
1107         RectF boundsF = new RectF();\r
1108         p.computeBounds (boundsF, true);\r
1109         matrix.mapRect (boundsF);\r
1110 \r
1111         boundsF.roundOut (bounds);\r
1112         bounds.left--;\r
1113         bounds.right++;\r
1114 \r
1115         final int w = bounds.width();\r
1116         final int h = Math.max (1, bounds.height());\r
1117 \r
1118         Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);\r
1119 \r
1120         Canvas c = new Canvas (bm);\r
1121         matrix.postTranslate (-bounds.left, -bounds.top);\r
1122         c.setMatrix (matrix);\r
1123         c.drawPath (p, paint);\r
1124 \r
1125         final int sizeNeeded = w * h;\r
1126         if (cachedRenderArray.length < sizeNeeded)\r
1127             cachedRenderArray = new int [sizeNeeded];\r
1128 \r
1129         bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);\r
1130         bm.recycle();\r
1131         return cachedRenderArray;\r
1132     }\r
1133 \r
1134     private int[] cachedRenderArray = new int [256];\r
1135 \r
1136     //==============================================================================\r
1137     public static class NativeInvocationHandler implements InvocationHandler\r
1138     {\r
1139         public NativeInvocationHandler (Activity activityToUse, long nativeContextRef)\r
1140         {\r
1141             activity = activityToUse;\r
1142             nativeContext = nativeContextRef;\r
1143         }\r
1144 \r
1145         public void nativeContextDeleted()\r
1146         {\r
1147             nativeContext = 0;\r
1148         }\r
1149 \r
1150         @Override\r
1151         public void finalize()\r
1152         {\r
1153             activity.runOnUiThread (new Runnable()\r
1154                                     {\r
1155                                         @Override\r
1156                                         public void run()\r
1157                                         {\r
1158                                             if (nativeContext != 0)\r
1159                                                 dispatchFinalize (nativeContext);\r
1160                                         }\r
1161                                     });\r
1162         }\r
1163 \r
1164         @Override\r
1165         public Object invoke (Object proxy, Method method, Object[] args) throws Throwable\r
1166         {\r
1167             return dispatchInvoke (nativeContext, proxy, method, args);\r
1168         }\r
1169 \r
1170         //==============================================================================\r
1171         Activity activity;\r
1172         private long nativeContext = 0;\r
1173 \r
1174         private native void dispatchFinalize (long nativeContextRef);\r
1175         private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args);\r
1176     }\r
1177 \r
1178     public InvocationHandler createInvocationHandler (long nativeContextRef)\r
1179     {\r
1180         return new NativeInvocationHandler (this, nativeContextRef);\r
1181     }\r
1182 \r
1183     public void invocationHandlerContextDeleted (InvocationHandler handler)\r
1184     {\r
1185         ((NativeInvocationHandler) handler).nativeContextDeleted();\r
1186     }\r
1187 \r
1188     //==============================================================================\r
1189     public static class HTTPStream\r
1190     {\r
1191         public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse,\r
1192                            String headersToUse, int timeOutMsToUse,\r
1193                            int[] statusCodeToUse, StringBuffer responseHeadersToUse,\r
1194                            int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException\r
1195         {\r
1196             isPost = isPostToUse;\r
1197             postData = postDataToUse;\r
1198             headers = headersToUse;\r
1199             timeOutMs = timeOutMsToUse;\r
1200             statusCode = statusCodeToUse;\r
1201             responseHeaders = responseHeadersToUse;\r
1202             totalLength = -1;\r
1203             numRedirectsToFollow = numRedirectsToFollowToUse;\r
1204             httpRequestCmd = httpRequestCmdToUse;\r
1205 \r
1206             connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd);\r
1207         }\r
1208 \r
1209         private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData,\r
1210                                                           String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException\r
1211         {\r
1212             HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection());\r
1213 \r
1214             try\r
1215             {\r
1216                 newConnection.setInstanceFollowRedirects (false);\r
1217                 newConnection.setConnectTimeout (timeOutMs);\r
1218                 newConnection.setReadTimeout (timeOutMs);\r
1219 \r
1220                 // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.\r
1221                 // So convert headers string to an array, with an element for each line\r
1222                 String headerLines[] = headers.split("\\n");\r
1223 \r
1224                 // Set request headers\r
1225                 for (int i = 0; i < headerLines.length; ++i)\r
1226                 {\r
1227                     int pos = headerLines[i].indexOf (":");\r
1228 \r
1229                     if (pos > 0 && pos < headerLines[i].length())\r
1230                     {\r
1231                         String field = headerLines[i].substring (0, pos);\r
1232                         String value = headerLines[i].substring (pos + 1);\r
1233 \r
1234                         if (value.length() > 0)\r
1235                             newConnection.setRequestProperty (field, value);\r
1236                     }\r
1237                 }\r
1238 \r
1239                 newConnection.setRequestMethod (httpRequestCmd);\r
1240 \r
1241                 if (isPost)\r
1242                 {\r
1243                     newConnection.setDoOutput (true);\r
1244 \r
1245                     if (postData != null)\r
1246                     {\r
1247                         OutputStream out = newConnection.getOutputStream();\r
1248                         out.write(postData);\r
1249                         out.flush();\r
1250                     }\r
1251                 }\r
1252 \r
1253                 return newConnection;\r
1254             }\r
1255             catch (Throwable e)\r
1256             {\r
1257                 newConnection.disconnect();\r
1258                 throw new IOException ("Connection error");\r
1259             }\r
1260         }\r
1261 \r
1262         private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException\r
1263         {\r
1264             synchronized (createFutureLock)\r
1265             {\r
1266                 if (hasBeenCancelled.get())\r
1267                     return null;\r
1268 \r
1269                 streamFuture = executor.submit (new Callable<BufferedInputStream>()\r
1270                 {\r
1271                     @Override\r
1272                     public BufferedInputStream call() throws IOException\r
1273                     {\r
1274                         return new BufferedInputStream (isInput ? connection.getInputStream()\r
1275                                                                 : connection.getErrorStream());\r
1276                     }\r
1277                 });\r
1278             }\r
1279 \r
1280             try\r
1281             {\r
1282                 return streamFuture.get();\r
1283             }\r
1284             catch (InterruptedException e)\r
1285             {\r
1286                 return null;\r
1287             }\r
1288             catch (CancellationException e)\r
1289             {\r
1290                 return null;\r
1291             }\r
1292         }\r
1293 \r
1294         public final boolean connect()\r
1295         {\r
1296             boolean result = false;\r
1297             int numFollowedRedirects = 0;\r
1298 \r
1299             while (true)\r
1300             {\r
1301                 result = doConnect();\r
1302 \r
1303                 if (! result)\r
1304                     return false;\r
1305 \r
1306                 if (++numFollowedRedirects > numRedirectsToFollow)\r
1307                     break;\r
1308 \r
1309                 int status = statusCode[0];\r
1310 \r
1311                 if (status == 301 || status == 302 || status == 303 || status == 307)\r
1312                 {\r
1313                     // Assumes only one occurrence of "Location"\r
1314                     int pos1 = responseHeaders.indexOf ("Location:") + 10;\r
1315                     int pos2 = responseHeaders.indexOf ("\n", pos1);\r
1316 \r
1317                     if (pos2 > pos1)\r
1318                     {\r
1319                         String currentLocation = connection.getURL().toString();\r
1320                         String newLocation = responseHeaders.substring (pos1, pos2);\r
1321 \r
1322                         try\r
1323                         {\r
1324                             // Handle newLocation whether it's absolute or relative\r
1325                             URL baseUrl = new URL (currentLocation);\r
1326                             URL newUrl  = new URL (baseUrl, newLocation);\r
1327                             String transformedNewLocation = newUrl.toString();\r
1328 \r
1329                             if (transformedNewLocation != currentLocation)\r
1330                             {\r
1331                                 // Clear responseHeaders before next iteration\r
1332                                 responseHeaders.delete (0, responseHeaders.length());\r
1333 \r
1334                                 synchronized (createStreamLock)\r
1335                                 {\r
1336                                     if (hasBeenCancelled.get())\r
1337                                         return false;\r
1338 \r
1339                                     connection.disconnect();\r
1340 \r
1341                                     try\r
1342                                     {\r
1343                                         connection = createConnection (transformedNewLocation, isPost,\r
1344                                                                        postData, headers, timeOutMs,\r
1345                                                                        httpRequestCmd);\r
1346                                     }\r
1347                                     catch (Throwable e)\r
1348                                     {\r
1349                                         return false;\r
1350                                     }\r
1351                                 }\r
1352                             }\r
1353                             else\r
1354                             {\r
1355                                 break;\r
1356                             }\r
1357                         }\r
1358                         catch (Throwable e)\r
1359                         {\r
1360                             return false;\r
1361                         }\r
1362                     }\r
1363                     else\r
1364                     {\r
1365                         break;\r
1366                     }\r
1367                 }\r
1368                 else\r
1369                 {\r
1370                     break;\r
1371                 }\r
1372             }\r
1373 \r
1374             return result;\r
1375         }\r
1376 \r
1377         private final boolean doConnect()\r
1378         {\r
1379             synchronized (createStreamLock)\r
1380             {\r
1381                 if (hasBeenCancelled.get())\r
1382                     return false;\r
1383 \r
1384                 try\r
1385                 {\r
1386                     try\r
1387                     {\r
1388                         inputStream = getCancellableStream (true);\r
1389                     }\r
1390                     catch (ExecutionException e)\r
1391                     {\r
1392                         if (connection.getResponseCode() < 400)\r
1393                         {\r
1394                             statusCode[0] = connection.getResponseCode();\r
1395                             connection.disconnect();\r
1396                             return false;\r
1397                         }\r
1398                     }\r
1399                     finally\r
1400                     {\r
1401                         statusCode[0] = connection.getResponseCode();\r
1402                     }\r
1403 \r
1404                     try\r
1405                     {\r
1406                         if (statusCode[0] >= 400)\r
1407                             inputStream = getCancellableStream (false);\r
1408                         else\r
1409                             inputStream = getCancellableStream (true);\r
1410                     }\r
1411                     catch (ExecutionException e)\r
1412                     {}\r
1413 \r
1414                     for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())\r
1415                     {\r
1416                         if (entry.getKey() != null && entry.getValue() != null)\r
1417                         {\r
1418                             responseHeaders.append(entry.getKey() + ": "\r
1419                                                    + android.text.TextUtils.join(",", entry.getValue()) + "\n");\r
1420 \r
1421                             if (entry.getKey().compareTo ("Content-Length") == 0)\r
1422                                 totalLength = Integer.decode (entry.getValue().get (0));\r
1423                         }\r
1424                     }\r
1425 \r
1426                     return true;\r
1427                 }\r
1428                 catch (IOException e)\r
1429                 {\r
1430                     return false;\r
1431                 }\r
1432             }\r
1433         }\r
1434 \r
1435         static class DisconnectionRunnable implements Runnable\r
1436         {\r
1437             public DisconnectionRunnable (HttpURLConnection theConnection,\r
1438                                           InputStream theInputStream,\r
1439                                           ReentrantLock theCreateStreamLock,\r
1440                                           Object theCreateFutureLock,\r
1441                                           Future<BufferedInputStream> theStreamFuture)\r
1442             {\r
1443                 connectionToDisconnect = theConnection;\r
1444                 inputStream = theInputStream;\r
1445                 createStreamLock = theCreateStreamLock;\r
1446                 createFutureLock = theCreateFutureLock;\r
1447                 streamFuture = theStreamFuture;\r
1448             }\r
1449 \r
1450             public void run()\r
1451             {\r
1452                 try\r
1453                 {\r
1454                     if (! createStreamLock.tryLock())\r
1455                     {\r
1456                         synchronized (createFutureLock)\r
1457                         {\r
1458                             if (streamFuture != null)\r
1459                                 streamFuture.cancel (true);\r
1460                         }\r
1461 \r
1462                         createStreamLock.lock();\r
1463                     }\r
1464 \r
1465                     if (connectionToDisconnect != null)\r
1466                         connectionToDisconnect.disconnect();\r
1467 \r
1468                     if (inputStream != null)\r
1469                         inputStream.close();\r
1470                 }\r
1471                 catch (IOException e)\r
1472                 {}\r
1473                 finally\r
1474                 {\r
1475                     createStreamLock.unlock();\r
1476                 }\r
1477             }\r
1478 \r
1479             private HttpURLConnection connectionToDisconnect;\r
1480             private InputStream inputStream;\r
1481             private ReentrantLock createStreamLock;\r
1482             private Object createFutureLock;\r
1483             Future<BufferedInputStream> streamFuture;\r
1484         }\r
1485 \r
1486         public final void release()\r
1487         {\r
1488             DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection,\r
1489                                                                                      inputStream,\r
1490                                                                                      createStreamLock,\r
1491                                                                                      createFutureLock,\r
1492                                                                                      streamFuture);\r
1493 \r
1494             synchronized (createStreamLock)\r
1495             {\r
1496                 hasBeenCancelled.set (true);\r
1497 \r
1498                 connection = null;\r
1499             }\r
1500 \r
1501             Thread disconnectionThread = new Thread(disconnectionRunnable);\r
1502             disconnectionThread.start();\r
1503         }\r
1504 \r
1505         public final int read (byte[] buffer, int numBytes)\r
1506         {\r
1507             int num = 0;\r
1508 \r
1509             try\r
1510             {\r
1511                 synchronized (createStreamLock)\r
1512                 {\r
1513                     if (inputStream != null)\r
1514                         num = inputStream.read (buffer, 0, numBytes);\r
1515                 }\r
1516             }\r
1517             catch (IOException e)\r
1518             {}\r
1519 \r
1520             if (num > 0)\r
1521                 position += num;\r
1522 \r
1523             return num;\r
1524         }\r
1525 \r
1526         public final long getPosition()                 { return position; }\r
1527         public final long getTotalLength()              { return totalLength; }\r
1528         public final boolean isExhausted()              { return false; }\r
1529         public final boolean setPosition (long newPos)  { return false; }\r
1530 \r
1531         private boolean isPost;\r
1532         private byte[] postData;\r
1533         private String headers;\r
1534         private int timeOutMs;\r
1535         String httpRequestCmd;\r
1536         private HttpURLConnection connection;\r
1537         private int[] statusCode;\r
1538         private StringBuffer responseHeaders;\r
1539         private int totalLength;\r
1540         private int numRedirectsToFollow;\r
1541         private InputStream inputStream;\r
1542         private long position;\r
1543         private final ReentrantLock createStreamLock = new ReentrantLock();\r
1544         private final Object createFutureLock = new Object();\r
1545         private AtomicBoolean hasBeenCancelled = new AtomicBoolean();\r
1546 \r
1547         private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory());\r
1548         Future<BufferedInputStream> streamFuture;\r
1549     }\r
1550 \r
1551     public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,\r
1552                                                      String headers, int timeOutMs, int[] statusCode,\r
1553                                                      StringBuffer responseHeaders, int numRedirectsToFollow,\r
1554                                                      String httpRequestCmd)\r
1555     {\r
1556         // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)\r
1557         if (timeOutMs < 0)\r
1558             timeOutMs = 0;\r
1559         else if (timeOutMs == 0)\r
1560             timeOutMs = 30000;\r
1561 \r
1562         for (;;)\r
1563         {\r
1564             try\r
1565             {\r
1566                 HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers,\r
1567                                                         timeOutMs, statusCode, responseHeaders,\r
1568                                                         numRedirectsToFollow, httpRequestCmd);\r
1569 \r
1570                 return httpStream;\r
1571             }\r
1572             catch (Throwable e) {}\r
1573 \r
1574             return null;\r
1575         }\r
1576     }\r
1577 \r
1578     public final void launchURL (String url)\r
1579     {\r
1580         startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));\r
1581     }\r
1582 \r
1583     private native boolean webViewPageLoadStarted (long host, WebView view, String url);\r
1584     private native void webViewPageLoadFinished (long host, WebView view, String url);\r
1585     private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error);\r
1586     private native void webViewCloseWindowRequest (long host, WebView view);\r
1587     private native void webViewCreateWindowRequest (long host, WebView view);\r
1588 \r
1589     //==============================================================================\r
1590     public class JuceWebViewClient   extends WebViewClient\r
1591     {\r
1592         public JuceWebViewClient (long hostToUse)\r
1593         {\r
1594             host = hostToUse;\r
1595         }\r
1596 \r
1597         public void hostDeleted()\r
1598         {\r
1599             synchronized (hostLock)\r
1600             {\r
1601                 host = 0;\r
1602             }\r
1603         }\r
1604 \r
1605         @Override\r
1606         public void onPageFinished (WebView view, String url)\r
1607         {\r
1608             if (host == 0)\r
1609                 return;\r
1610 \r
1611             webViewPageLoadFinished (host, view, url);\r
1612         }\r
1613 \r
1614         @Override\r
1615         public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error)\r
1616         {\r
1617             if (host == 0)\r
1618                 return;\r
1619 \r
1620             webViewReceivedSslError (host, view, handler, error);\r
1621         }\r
1622 \r
1623         @Override\r
1624         public void onPageStarted (WebView view, String url, Bitmap favicon)\r
1625         {\r
1626             if (host != 0)\r
1627                 webViewPageLoadStarted (host, view, url);\r
1628         }\r
1629 \r
1630         private long host;\r
1631         private final Object hostLock = new Object();\r
1632     }\r
1633 \r
1634     public class JuceWebChromeClient    extends WebChromeClient\r
1635     {\r
1636         public JuceWebChromeClient (long hostToUse)\r
1637         {\r
1638             host = hostToUse;\r
1639         }\r
1640 \r
1641         @Override\r
1642         public void onCloseWindow (WebView window)\r
1643         {\r
1644             webViewCloseWindowRequest (host, window);\r
1645         }\r
1646 \r
1647         @Override\r
1648         public boolean onCreateWindow (WebView view, boolean isDialog,\r
1649                                        boolean isUserGesture, Message resultMsg)\r
1650         {\r
1651             webViewCreateWindowRequest (host, view);\r
1652             return false;\r
1653         }\r
1654 \r
1655         private long host;\r
1656         private final Object hostLock = new Object();\r
1657     }\r
1658 \r
1659     //==============================================================================\r
1660     public static final String getLocaleValue (boolean isRegion)\r
1661     {\r
1662         java.util.Locale locale = java.util.Locale.getDefault();\r
1663 \r
1664         return isRegion ? locale.getCountry()\r
1665                         : locale.getLanguage();\r
1666     }\r
1667 \r
1668     private static final String getFileLocation (String type)\r
1669     {\r
1670         return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();\r
1671     }\r
1672 \r
1673     public static final String getDocumentsFolder()\r
1674     {\r
1675         if (getAndroidSDKVersion() >= 19)\r
1676             return getFileLocation ("Documents");\r
1677 \r
1678         return Environment.getDataDirectory().getAbsolutePath();\r
1679     }\r
1680 \r
1681     public static final String getPicturesFolder()   { return getFileLocation (Environment.DIRECTORY_PICTURES); }\r
1682     public static final String getMusicFolder()      { return getFileLocation (Environment.DIRECTORY_MUSIC); }\r
1683     public static final String getMoviesFolder()     { return getFileLocation (Environment.DIRECTORY_MOVIES); }\r
1684     public static final String getDownloadsFolder()  { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }\r
1685 \r
1686     //==============================================================================\r
1687     @Override\r
1688     protected void onActivityResult (int requestCode, int resultCode, Intent data)\r
1689     {\r
1690         appActivityResult (requestCode, resultCode, data);\r
1691     }\r
1692 \r
1693     @Override\r
1694     protected void onNewIntent (Intent intent)\r
1695     {\r
1696         super.onNewIntent(intent);\r
1697         setIntent(intent);\r
1698 \r
1699         appNewIntent (intent);\r
1700     }\r
1701 \r
1702     //==============================================================================\r
1703     public final Typeface getTypeFaceFromAsset (String assetName)\r
1704     {\r
1705         try\r
1706         {\r
1707             return Typeface.createFromAsset (this.getResources().getAssets(), assetName);\r
1708         }\r
1709         catch (Throwable e) {}\r
1710 \r
1711         return null;\r
1712     }\r
1713 \r
1714     final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();\r
1715 \r
1716     public static String bytesToHex (byte[] bytes)\r
1717     {\r
1718         char[] hexChars = new char[bytes.length * 2];\r
1719 \r
1720         for (int j = 0; j < bytes.length; ++j)\r
1721         {\r
1722             int v = bytes[j] & 0xff;\r
1723             hexChars[j * 2]     = hexArray[v >>> 4];\r
1724             hexChars[j * 2 + 1] = hexArray[v & 0x0f];\r
1725         }\r
1726 \r
1727         return new String (hexChars);\r
1728     }\r
1729 \r
1730     final private java.util.Map dataCache = new java.util.HashMap();\r
1731 \r
1732     synchronized private final File getDataCacheFile (byte[] data)\r
1733     {\r
1734         try\r
1735         {\r
1736             java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");\r
1737             digest.update (data);\r
1738 \r
1739             String key = bytesToHex (digest.digest());\r
1740 \r
1741             if (dataCache.containsKey (key))\r
1742                 return (File) dataCache.get (key);\r
1743 \r
1744             File f = new File (this.getCacheDir(), "bindata_" + key);\r
1745             f.delete();\r
1746             FileOutputStream os = new FileOutputStream (f);\r
1747             os.write (data, 0, data.length);\r
1748             dataCache.put (key, f);\r
1749             return f;\r
1750         }\r
1751         catch (Throwable e) {}\r
1752 \r
1753         return null;\r
1754     }\r
1755 \r
1756     private final void clearDataCache()\r
1757     {\r
1758         java.util.Iterator it = dataCache.values().iterator();\r
1759 \r
1760         while (it.hasNext())\r
1761         {\r
1762             File f = (File) it.next();\r
1763             f.delete();\r
1764         }\r
1765     }\r
1766 \r
1767     public final Typeface getTypeFaceFromByteArray (byte[] data)\r
1768     {\r
1769         try\r
1770         {\r
1771             File f = getDataCacheFile (data);\r
1772 \r
1773             if (f != null)\r
1774                 return Typeface.createFromFile (f);\r
1775         }\r
1776         catch (Exception e)\r
1777         {\r
1778             Log.e ("JUCE", e.toString());\r
1779         }\r
1780 \r
1781         return null;\r
1782     }\r
1783 \r
1784     public static final int getAndroidSDKVersion()\r
1785     {\r
1786         return android.os.Build.VERSION.SDK_INT;\r
1787     }\r
1788 \r
1789     public final String audioManagerGetProperty (String property)\r
1790     {\r
1791         Object obj = getSystemService (AUDIO_SERVICE);\r
1792         if (obj == null)\r
1793             return null;\r
1794 \r
1795         java.lang.reflect.Method method;\r
1796 \r
1797         try\r
1798         {\r
1799             method = obj.getClass().getMethod ("getProperty", String.class);\r
1800         }\r
1801         catch (SecurityException e)     { return null; }\r
1802         catch (NoSuchMethodException e) { return null; }\r
1803 \r
1804         if (method == null)\r
1805             return null;\r
1806 \r
1807         try\r
1808         {\r
1809             return (String) method.invoke (obj, property);\r
1810         }\r
1811         catch (java.lang.IllegalArgumentException e) {}\r
1812         catch (java.lang.IllegalAccessException e) {}\r
1813         catch (java.lang.reflect.InvocationTargetException e) {}\r
1814 \r
1815         return null;\r
1816     }\r
1817 \r
1818     public final boolean hasSystemFeature (String property)\r
1819     {\r
1820         return getPackageManager().hasSystemFeature (property);\r
1821     }\r
1822 }\r