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