The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1222 lines
42KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. package com.juce;
  18. import android.app.Activity;
  19. import android.app.AlertDialog;
  20. import android.content.DialogInterface;
  21. import android.content.Context;
  22. import android.content.Intent;
  23. import android.content.res.Configuration;
  24. import android.content.pm.PackageInfo;
  25. import android.content.pm.PackageManager;
  26. import android.net.Uri;
  27. import android.os.Bundle;
  28. import android.os.Looper;
  29. import android.os.Handler;
  30. import android.os.ParcelUuid;
  31. import android.os.Environment;
  32. import android.view.*;
  33. import android.view.inputmethod.BaseInputConnection;
  34. import android.view.inputmethod.EditorInfo;
  35. import android.view.inputmethod.InputConnection;
  36. import android.view.inputmethod.InputMethodManager;
  37. import android.graphics.*;
  38. import android.text.ClipboardManager;
  39. import android.text.InputType;
  40. import android.util.DisplayMetrics;
  41. import android.util.Log;
  42. import java.lang.Runnable;
  43. import java.util.*;
  44. import java.io.*;
  45. import java.net.URL;
  46. import java.net.HttpURLConnection;
  47. import android.media.AudioManager;
  48. import android.media.MediaScannerConnection;
  49. import android.media.MediaScannerConnection.MediaScannerConnectionClient;
  50. import android.support.v4.content.ContextCompat;
  51. import android.support.v4.app.ActivityCompat;
  52. import android.Manifest;
  53. $$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer!
  54. //==============================================================================
  55. public class JuceAppActivity extends Activity
  56. {
  57. //==============================================================================
  58. static
  59. {
  60. System.loadLibrary ("juce_jni");
  61. }
  62. //==============================================================================
  63. public boolean isPermissionDeclaredInManifest (int permissionID)
  64. {
  65. String permissionToCheck = getAndroidPermissionName(permissionID);
  66. try
  67. {
  68. PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
  69. if (info.requestedPermissions != null)
  70. for (String permission : info.requestedPermissions)
  71. if (permission.equals (permissionToCheck))
  72. return true;
  73. }
  74. catch (PackageManager.NameNotFoundException e)
  75. {
  76. Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());
  77. }
  78. Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);
  79. return false;
  80. }
  81. //==============================================================================
  82. // these have to match the values of enum PermissionID in C++ class RuntimePermissions:
  83. private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;
  84. private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
  85. private static String getAndroidPermissionName (int permissionID)
  86. {
  87. switch (permissionID)
  88. {
  89. case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO;
  90. case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION;
  91. }
  92. // unknown permission ID!
  93. assert false;
  94. return new String();
  95. }
  96. public boolean isPermissionGranted (int permissionID)
  97. {
  98. return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
  99. }
  100. private Map<Integer, Long> permissionCallbackPtrMap;
  101. public void requestRuntimePermission (int permissionID, long ptrToCallback)
  102. {
  103. String permissionName = getAndroidPermissionName (permissionID);
  104. if (ContextCompat.checkSelfPermission (this, permissionName) != PackageManager.PERMISSION_GRANTED)
  105. {
  106. // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously
  107. permissionCallbackPtrMap.put (permissionID, ptrToCallback);
  108. ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID);
  109. }
  110. else
  111. {
  112. // permissions were already granted before, we can call callback directly
  113. androidRuntimePermissionsCallback (true, ptrToCallback);
  114. }
  115. }
  116. private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);
  117. $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the Projucer!
  118. //==============================================================================
  119. public static class MidiPortID extends Object
  120. {
  121. public MidiPortID (int index, boolean direction)
  122. {
  123. androidIndex = index;
  124. isInput = direction;
  125. }
  126. public int androidIndex;
  127. public boolean isInput;
  128. @Override
  129. public int hashCode()
  130. {
  131. Integer i = new Integer (androidIndex);
  132. return i.hashCode() * (isInput ? -1 : 1);
  133. }
  134. @Override
  135. public boolean equals (Object obj)
  136. {
  137. if (obj == null)
  138. return false;
  139. if (getClass() != obj.getClass())
  140. return false;
  141. MidiPortID other = (MidiPortID) obj;
  142. return (androidIndex == other.androidIndex && isInput == other.isInput);
  143. }
  144. }
  145. public interface JuceMidiPort
  146. {
  147. boolean isInputPort();
  148. // start, stop does nothing on an output port
  149. void start();
  150. void stop();
  151. void close();
  152. MidiPortID getPortId();
  153. // send will do nothing on an input port
  154. void sendMidi (byte[] msg, int offset, int count);
  155. }
  156. //==============================================================================
  157. $$JuceAndroidMidiCode$$ // If you get an error here, you need to re-save your project with the Projucer!
  158. //==============================================================================
  159. @Override
  160. public void onCreate (Bundle savedInstanceState)
  161. {
  162. super.onCreate (savedInstanceState);
  163. isScreenSaverEnabled = true;
  164. hideActionBar();
  165. viewHolder = new ViewHolder (this);
  166. setContentView (viewHolder);
  167. setVolumeControlStream (AudioManager.STREAM_MUSIC);
  168. permissionCallbackPtrMap = new HashMap<Integer, Long>();
  169. }
  170. @Override
  171. protected void onDestroy()
  172. {
  173. quitApp();
  174. super.onDestroy();
  175. clearDataCache();
  176. }
  177. @Override
  178. protected void onPause()
  179. {
  180. suspendApp();
  181. super.onPause();
  182. }
  183. @Override
  184. protected void onResume()
  185. {
  186. super.onResume();
  187. resumeApp();
  188. }
  189. @Override
  190. public void onConfigurationChanged (Configuration cfg)
  191. {
  192. super.onConfigurationChanged (cfg);
  193. setContentView (viewHolder);
  194. }
  195. private void callAppLauncher()
  196. {
  197. launchApp (getApplicationInfo().publicSourceDir,
  198. getApplicationInfo().dataDir);
  199. }
  200. private void hideActionBar()
  201. {
  202. // get "getActionBar" method
  203. java.lang.reflect.Method getActionBarMethod = null;
  204. try
  205. {
  206. getActionBarMethod = this.getClass().getMethod ("getActionBar");
  207. }
  208. catch (SecurityException e) { return; }
  209. catch (NoSuchMethodException e) { return; }
  210. if (getActionBarMethod == null) return;
  211. // invoke "getActionBar" method
  212. Object actionBar = null;
  213. try
  214. {
  215. actionBar = getActionBarMethod.invoke (this);
  216. }
  217. catch (java.lang.IllegalArgumentException e) { return; }
  218. catch (java.lang.IllegalAccessException e) { return; }
  219. catch (java.lang.reflect.InvocationTargetException e) { return; }
  220. if (actionBar == null) return;
  221. // get "hide" method
  222. java.lang.reflect.Method actionBarHideMethod = null;
  223. try
  224. {
  225. actionBarHideMethod = actionBar.getClass().getMethod ("hide");
  226. }
  227. catch (SecurityException e) { return; }
  228. catch (NoSuchMethodException e) { return; }
  229. if (actionBarHideMethod == null) return;
  230. // invoke "hide" method
  231. try
  232. {
  233. actionBarHideMethod.invoke (actionBar);
  234. }
  235. catch (java.lang.IllegalArgumentException e) {}
  236. catch (java.lang.IllegalAccessException e) {}
  237. catch (java.lang.reflect.InvocationTargetException e) {}
  238. }
  239. //==============================================================================
  240. private native void launchApp (String appFile, String appDataDir);
  241. private native void quitApp();
  242. private native void suspendApp();
  243. private native void resumeApp();
  244. private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
  245. //==============================================================================
  246. public native void deliverMessage (long value);
  247. private android.os.Handler messageHandler = new android.os.Handler();
  248. public final void postMessage (long value)
  249. {
  250. messageHandler.post (new MessageCallback (value));
  251. }
  252. private final class MessageCallback implements Runnable
  253. {
  254. public MessageCallback (long value_) { value = value_; }
  255. public final void run() { deliverMessage (value); }
  256. private long value;
  257. }
  258. //==============================================================================
  259. private ViewHolder viewHolder;
  260. private MidiDeviceManager midiDeviceManager = null;
  261. private BluetoothManager bluetoothManager = null;
  262. private boolean isScreenSaverEnabled;
  263. private java.util.Timer keepAliveTimer;
  264. public final ComponentPeerView createNewView (boolean opaque, long host)
  265. {
  266. ComponentPeerView v = new ComponentPeerView (this, opaque, host);
  267. viewHolder.addView (v);
  268. return v;
  269. }
  270. public final void deleteView (ComponentPeerView view)
  271. {
  272. ViewGroup group = (ViewGroup) (view.getParent());
  273. if (group != null)
  274. group.removeView (view);
  275. }
  276. public final void deleteNativeSurfaceView (NativeSurfaceView view)
  277. {
  278. ViewGroup group = (ViewGroup) (view.getParent());
  279. if (group != null)
  280. group.removeView (view);
  281. }
  282. final class ViewHolder extends ViewGroup
  283. {
  284. public ViewHolder (Context context)
  285. {
  286. super (context);
  287. setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
  288. setFocusable (false);
  289. }
  290. protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
  291. {
  292. setScreenSize (getWidth(), getHeight(), getDPI());
  293. if (isFirstResize)
  294. {
  295. isFirstResize = false;
  296. callAppLauncher();
  297. }
  298. }
  299. private final int getDPI()
  300. {
  301. DisplayMetrics metrics = new DisplayMetrics();
  302. getWindowManager().getDefaultDisplay().getMetrics (metrics);
  303. return metrics.densityDpi;
  304. }
  305. private boolean isFirstResize = true;
  306. }
  307. public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
  308. {
  309. canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
  310. }
  311. //==============================================================================
  312. public final void setScreenSaver (boolean enabled)
  313. {
  314. if (isScreenSaverEnabled != enabled)
  315. {
  316. isScreenSaverEnabled = enabled;
  317. if (keepAliveTimer != null)
  318. {
  319. keepAliveTimer.cancel();
  320. keepAliveTimer = null;
  321. }
  322. if (enabled)
  323. {
  324. getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  325. }
  326. else
  327. {
  328. getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  329. // If no user input is received after about 3 seconds, the OS will lower the
  330. // task's priority, so this timer forces it to be kept active.
  331. keepAliveTimer = new java.util.Timer();
  332. keepAliveTimer.scheduleAtFixedRate (new TimerTask()
  333. {
  334. @Override
  335. public void run()
  336. {
  337. android.app.Instrumentation instrumentation = new android.app.Instrumentation();
  338. try
  339. {
  340. instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
  341. }
  342. catch (Exception e)
  343. {
  344. }
  345. }
  346. }, 2000, 2000);
  347. }
  348. }
  349. }
  350. public final boolean getScreenSaver()
  351. {
  352. return isScreenSaverEnabled;
  353. }
  354. //==============================================================================
  355. public final String getClipboardContent()
  356. {
  357. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  358. return clipboard.getText().toString();
  359. }
  360. public final void setClipboardContent (String newText)
  361. {
  362. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  363. clipboard.setText (newText);
  364. }
  365. //==============================================================================
  366. public final void showMessageBox (String title, String message, final long callback)
  367. {
  368. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  369. builder.setTitle (title)
  370. .setMessage (message)
  371. .setCancelable (true)
  372. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  373. {
  374. public void onClick (DialogInterface dialog, int id)
  375. {
  376. dialog.cancel();
  377. JuceAppActivity.this.alertDismissed (callback, 0);
  378. }
  379. });
  380. builder.create().show();
  381. }
  382. public final void showOkCancelBox (String title, String message, final long callback)
  383. {
  384. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  385. builder.setTitle (title)
  386. .setMessage (message)
  387. .setCancelable (true)
  388. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  389. {
  390. public void onClick (DialogInterface dialog, int id)
  391. {
  392. dialog.cancel();
  393. JuceAppActivity.this.alertDismissed (callback, 1);
  394. }
  395. })
  396. .setNegativeButton ("Cancel", new DialogInterface.OnClickListener()
  397. {
  398. public void onClick (DialogInterface dialog, int id)
  399. {
  400. dialog.cancel();
  401. JuceAppActivity.this.alertDismissed (callback, 0);
  402. }
  403. });
  404. builder.create().show();
  405. }
  406. public final void showYesNoCancelBox (String title, String message, final long callback)
  407. {
  408. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  409. builder.setTitle (title)
  410. .setMessage (message)
  411. .setCancelable (true)
  412. .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
  413. {
  414. public void onClick (DialogInterface dialog, int id)
  415. {
  416. dialog.cancel();
  417. JuceAppActivity.this.alertDismissed (callback, 1);
  418. }
  419. })
  420. .setNegativeButton ("No", new DialogInterface.OnClickListener()
  421. {
  422. public void onClick (DialogInterface dialog, int id)
  423. {
  424. dialog.cancel();
  425. JuceAppActivity.this.alertDismissed (callback, 2);
  426. }
  427. })
  428. .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
  429. {
  430. public void onClick (DialogInterface dialog, int id)
  431. {
  432. dialog.cancel();
  433. JuceAppActivity.this.alertDismissed (callback, 0);
  434. }
  435. });
  436. builder.create().show();
  437. }
  438. public native void alertDismissed (long callback, int id);
  439. //==============================================================================
  440. public final class ComponentPeerView extends ViewGroup
  441. implements View.OnFocusChangeListener
  442. {
  443. public ComponentPeerView (Context context, boolean opaque_, long host)
  444. {
  445. super (context);
  446. this.host = host;
  447. setWillNotDraw (false);
  448. opaque = opaque_;
  449. setFocusable (true);
  450. setFocusableInTouchMode (true);
  451. setOnFocusChangeListener (this);
  452. requestFocus();
  453. // swap red and blue colours to match internal opengl texture format
  454. ColorMatrix colorMatrix = new ColorMatrix();
  455. float[] colorTransform = { 0, 0, 1.0f, 0, 0,
  456. 0, 1.0f, 0, 0, 0,
  457. 1.0f, 0, 0, 0, 0,
  458. 0, 0, 0, 1.0f, 0 };
  459. colorMatrix.set (colorTransform);
  460. paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
  461. }
  462. //==============================================================================
  463. private native void handlePaint (long host, Canvas canvas, Paint paint);
  464. @Override
  465. public void onDraw (Canvas canvas)
  466. {
  467. handlePaint (host, canvas, paint);
  468. }
  469. @Override
  470. public boolean isOpaque()
  471. {
  472. return opaque;
  473. }
  474. private boolean opaque;
  475. private long host;
  476. private Paint paint = new Paint();
  477. //==============================================================================
  478. private native void handleMouseDown (long host, int index, float x, float y, long time);
  479. private native void handleMouseDrag (long host, int index, float x, float y, long time);
  480. private native void handleMouseUp (long host, int index, float x, float y, long time);
  481. @Override
  482. public boolean onTouchEvent (MotionEvent event)
  483. {
  484. int action = event.getAction();
  485. long time = event.getEventTime();
  486. switch (action & MotionEvent.ACTION_MASK)
  487. {
  488. case MotionEvent.ACTION_DOWN:
  489. handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
  490. return true;
  491. case MotionEvent.ACTION_CANCEL:
  492. case MotionEvent.ACTION_UP:
  493. handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
  494. return true;
  495. case MotionEvent.ACTION_MOVE:
  496. {
  497. int n = event.getPointerCount();
  498. for (int i = 0; i < n; ++i)
  499. handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  500. return true;
  501. }
  502. case MotionEvent.ACTION_POINTER_UP:
  503. {
  504. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  505. handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  506. return true;
  507. }
  508. case MotionEvent.ACTION_POINTER_DOWN:
  509. {
  510. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  511. handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  512. return true;
  513. }
  514. default:
  515. break;
  516. }
  517. return false;
  518. }
  519. //==============================================================================
  520. private native void handleKeyDown (long host, int keycode, int textchar);
  521. private native void handleKeyUp (long host, int keycode, int textchar);
  522. public void showKeyboard (String type)
  523. {
  524. InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
  525. if (imm != null)
  526. {
  527. if (type.length() > 0)
  528. {
  529. imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
  530. imm.setInputMethod (getWindowToken(), type);
  531. }
  532. else
  533. {
  534. imm.hideSoftInputFromWindow (getWindowToken(), 0);
  535. }
  536. }
  537. }
  538. @Override
  539. public boolean onKeyDown (int keyCode, KeyEvent event)
  540. {
  541. switch (keyCode)
  542. {
  543. case KeyEvent.KEYCODE_VOLUME_UP:
  544. case KeyEvent.KEYCODE_VOLUME_DOWN:
  545. return super.onKeyDown (keyCode, event);
  546. default: break;
  547. }
  548. handleKeyDown (host, keyCode, event.getUnicodeChar());
  549. return true;
  550. }
  551. @Override
  552. public boolean onKeyUp (int keyCode, KeyEvent event)
  553. {
  554. handleKeyUp (host, keyCode, event.getUnicodeChar());
  555. return true;
  556. }
  557. @Override
  558. public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
  559. {
  560. if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
  561. return super.onKeyMultiple (keyCode, count, event);
  562. if (event.getCharacters() != null)
  563. {
  564. int utf8Char = event.getCharacters().codePointAt (0);
  565. handleKeyDown (host, utf8Char, utf8Char);
  566. return true;
  567. }
  568. return false;
  569. }
  570. // this is here to make keyboard entry work on a Galaxy Tab2 10.1
  571. @Override
  572. public InputConnection onCreateInputConnection (EditorInfo outAttrs)
  573. {
  574. outAttrs.actionLabel = "";
  575. outAttrs.hintText = "";
  576. outAttrs.initialCapsMode = 0;
  577. outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
  578. outAttrs.label = "";
  579. outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
  580. outAttrs.inputType = InputType.TYPE_NULL;
  581. return new BaseInputConnection (this, false);
  582. }
  583. //==============================================================================
  584. @Override
  585. protected void onSizeChanged (int w, int h, int oldw, int oldh)
  586. {
  587. super.onSizeChanged (w, h, oldw, oldh);
  588. viewSizeChanged (host);
  589. }
  590. @Override
  591. protected void onLayout (boolean changed, int left, int top, int right, int bottom)
  592. {
  593. for (int i = getChildCount(); --i >= 0;)
  594. requestTransparentRegion (getChildAt (i));
  595. }
  596. private native void viewSizeChanged (long host);
  597. @Override
  598. public void onFocusChange (View v, boolean hasFocus)
  599. {
  600. if (v == this)
  601. focusChanged (host, hasFocus);
  602. }
  603. private native void focusChanged (long host, boolean hasFocus);
  604. public void setViewName (String newName) {}
  605. public boolean isVisible() { return getVisibility() == VISIBLE; }
  606. public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
  607. public boolean containsPoint (int x, int y)
  608. {
  609. return true; //xxx needs to check overlapping views
  610. }
  611. }
  612. //==============================================================================
  613. public static class NativeSurfaceView extends SurfaceView
  614. implements SurfaceHolder.Callback
  615. {
  616. private long nativeContext = 0;
  617. NativeSurfaceView (Context context, long nativeContextPtr)
  618. {
  619. super (context);
  620. nativeContext = nativeContextPtr;
  621. }
  622. public Surface getNativeSurface()
  623. {
  624. Surface retval = null;
  625. SurfaceHolder holder = getHolder();
  626. if (holder != null)
  627. retval = holder.getSurface();
  628. return retval;
  629. }
  630. //==============================================================================
  631. @Override
  632. public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
  633. {
  634. surfaceChangedNative (nativeContext, holder, format, width, height);
  635. }
  636. @Override
  637. public void surfaceCreated (SurfaceHolder holder)
  638. {
  639. surfaceCreatedNative (nativeContext, holder);
  640. }
  641. @Override
  642. public void surfaceDestroyed (SurfaceHolder holder)
  643. {
  644. surfaceDestroyedNative (nativeContext, holder);
  645. }
  646. @Override
  647. protected void dispatchDraw (Canvas canvas)
  648. {
  649. super.dispatchDraw (canvas);
  650. dispatchDrawNative (nativeContext, canvas);
  651. }
  652. //==============================================================================
  653. @Override
  654. protected void onAttachedToWindow ()
  655. {
  656. super.onAttachedToWindow();
  657. getHolder().addCallback (this);
  658. }
  659. @Override
  660. protected void onDetachedFromWindow ()
  661. {
  662. super.onDetachedFromWindow();
  663. getHolder().removeCallback (this);
  664. }
  665. //==============================================================================
  666. private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
  667. private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
  668. private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
  669. private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
  670. int format, int width, int height);
  671. }
  672. public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
  673. {
  674. return new NativeSurfaceView (this, nativeSurfacePtr);
  675. }
  676. //==============================================================================
  677. public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds)
  678. {
  679. Path p = new Path();
  680. paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p);
  681. RectF boundsF = new RectF();
  682. p.computeBounds (boundsF, true);
  683. matrix.mapRect (boundsF);
  684. boundsF.roundOut (bounds);
  685. bounds.left--;
  686. bounds.right++;
  687. final int w = bounds.width();
  688. final int h = Math.max (1, bounds.height());
  689. Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
  690. Canvas c = new Canvas (bm);
  691. matrix.postTranslate (-bounds.left, -bounds.top);
  692. c.setMatrix (matrix);
  693. c.drawPath (p, paint);
  694. final int sizeNeeded = w * h;
  695. if (cachedRenderArray.length < sizeNeeded)
  696. cachedRenderArray = new int [sizeNeeded];
  697. bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
  698. bm.recycle();
  699. return cachedRenderArray;
  700. }
  701. private int[] cachedRenderArray = new int [256];
  702. //==============================================================================
  703. public static class HTTPStream
  704. {
  705. public HTTPStream (HttpURLConnection connection_,
  706. int[] statusCode, StringBuffer responseHeaders) throws IOException
  707. {
  708. connection = connection_;
  709. try
  710. {
  711. inputStream = new BufferedInputStream (connection.getInputStream());
  712. }
  713. catch (IOException e)
  714. {
  715. if (connection.getResponseCode() < 400)
  716. throw e;
  717. }
  718. finally
  719. {
  720. statusCode[0] = connection.getResponseCode();
  721. }
  722. if (statusCode[0] >= 400)
  723. inputStream = connection.getErrorStream();
  724. else
  725. inputStream = connection.getInputStream();
  726. for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
  727. if (entry.getKey() != null && entry.getValue() != null)
  728. responseHeaders.append (entry.getKey() + ": "
  729. + android.text.TextUtils.join (",", entry.getValue()) + "\n");
  730. }
  731. public final void release()
  732. {
  733. try
  734. {
  735. inputStream.close();
  736. }
  737. catch (IOException e)
  738. {}
  739. connection.disconnect();
  740. }
  741. public final int read (byte[] buffer, int numBytes)
  742. {
  743. int num = 0;
  744. try
  745. {
  746. num = inputStream.read (buffer, 0, numBytes);
  747. }
  748. catch (IOException e)
  749. {}
  750. if (num > 0)
  751. position += num;
  752. return num;
  753. }
  754. public final long getPosition() { return position; }
  755. public final long getTotalLength() { return -1; }
  756. public final boolean isExhausted() { return false; }
  757. public final boolean setPosition (long newPos) { return false; }
  758. private HttpURLConnection connection;
  759. private InputStream inputStream;
  760. private long position;
  761. }
  762. public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
  763. String headers, int timeOutMs, int[] statusCode,
  764. StringBuffer responseHeaders, int numRedirectsToFollow,
  765. String httpRequestCmd)
  766. {
  767. // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
  768. if (timeOutMs < 0)
  769. timeOutMs = 0;
  770. else if (timeOutMs == 0)
  771. timeOutMs = 30000;
  772. // 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.
  773. // So convert headers string to an array, with an element for each line
  774. String headerLines[] = headers.split("\\n");
  775. for (;;)
  776. {
  777. try
  778. {
  779. HttpURLConnection connection = (HttpURLConnection) (new URL(address).openConnection());
  780. if (connection != null)
  781. {
  782. try
  783. {
  784. connection.setInstanceFollowRedirects (false);
  785. connection.setConnectTimeout (timeOutMs);
  786. connection.setReadTimeout (timeOutMs);
  787. // Set request headers
  788. for (int i = 0; i < headerLines.length; ++i)
  789. {
  790. int pos = headerLines[i].indexOf (":");
  791. if (pos > 0 && pos < headerLines[i].length())
  792. {
  793. String field = headerLines[i].substring (0, pos);
  794. String value = headerLines[i].substring (pos + 1);
  795. if (value.length() > 0)
  796. connection.setRequestProperty (field, value);
  797. }
  798. }
  799. connection.setRequestMethod (httpRequestCmd);
  800. if (isPost)
  801. {
  802. connection.setDoOutput (true);
  803. if (postData != null)
  804. {
  805. OutputStream out = connection.getOutputStream();
  806. out.write(postData);
  807. out.flush();
  808. }
  809. }
  810. HTTPStream httpStream = new HTTPStream (connection, statusCode, responseHeaders);
  811. // Process redirect & continue as necessary
  812. int status = statusCode[0];
  813. if (--numRedirectsToFollow >= 0
  814. && (status == 301 || status == 302 || status == 303 || status == 307))
  815. {
  816. // Assumes only one occurrence of "Location"
  817. int pos1 = responseHeaders.indexOf ("Location:") + 10;
  818. int pos2 = responseHeaders.indexOf ("\n", pos1);
  819. if (pos2 > pos1)
  820. {
  821. String newLocation = responseHeaders.substring(pos1, pos2);
  822. // Handle newLocation whether it's absolute or relative
  823. URL baseUrl = new URL (address);
  824. URL newUrl = new URL (baseUrl, newLocation);
  825. String transformedNewLocation = newUrl.toString();
  826. if (transformedNewLocation != address)
  827. {
  828. address = transformedNewLocation;
  829. // Clear responseHeaders before next iteration
  830. responseHeaders.delete (0, responseHeaders.length());
  831. continue;
  832. }
  833. }
  834. }
  835. return httpStream;
  836. }
  837. catch (Throwable e)
  838. {
  839. connection.disconnect();
  840. }
  841. }
  842. }
  843. catch (Throwable e) {}
  844. return null;
  845. }
  846. }
  847. public final void launchURL (String url)
  848. {
  849. startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
  850. }
  851. public static final String getLocaleValue (boolean isRegion)
  852. {
  853. java.util.Locale locale = java.util.Locale.getDefault();
  854. return isRegion ? locale.getDisplayCountry (java.util.Locale.US)
  855. : locale.getDisplayLanguage (java.util.Locale.US);
  856. }
  857. private static final String getFileLocation (String type)
  858. {
  859. return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
  860. }
  861. public static final String getDocumentsFolder() { return Environment.getDataDirectory().getAbsolutePath(); }
  862. public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
  863. public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
  864. public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
  865. public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
  866. //==============================================================================
  867. private final class SingleMediaScanner implements MediaScannerConnectionClient
  868. {
  869. public SingleMediaScanner (Context context, String filename)
  870. {
  871. file = filename;
  872. msc = new MediaScannerConnection (context, this);
  873. msc.connect();
  874. }
  875. @Override
  876. public void onMediaScannerConnected()
  877. {
  878. msc.scanFile (file, null);
  879. }
  880. @Override
  881. public void onScanCompleted (String path, Uri uri)
  882. {
  883. msc.disconnect();
  884. }
  885. private MediaScannerConnection msc;
  886. private String file;
  887. }
  888. public final void scanFile (String filename)
  889. {
  890. new SingleMediaScanner (this, filename);
  891. }
  892. public final Typeface getTypeFaceFromAsset (String assetName)
  893. {
  894. try
  895. {
  896. return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
  897. }
  898. catch (Throwable e) {}
  899. return null;
  900. }
  901. final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
  902. public static String bytesToHex (byte[] bytes)
  903. {
  904. char[] hexChars = new char[bytes.length * 2];
  905. for (int j = 0; j < bytes.length; ++j)
  906. {
  907. int v = bytes[j] & 0xff;
  908. hexChars[j * 2] = hexArray[v >>> 4];
  909. hexChars[j * 2 + 1] = hexArray[v & 0x0f];
  910. }
  911. return new String (hexChars);
  912. }
  913. final private java.util.Map dataCache = new java.util.HashMap();
  914. synchronized private final File getDataCacheFile (byte[] data)
  915. {
  916. try
  917. {
  918. java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
  919. digest.update (data);
  920. String key = bytesToHex (digest.digest());
  921. if (dataCache.containsKey (key))
  922. return (File) dataCache.get (key);
  923. File f = new File (this.getCacheDir(), "bindata_" + key);
  924. f.delete();
  925. FileOutputStream os = new FileOutputStream (f);
  926. os.write (data, 0, data.length);
  927. dataCache.put (key, f);
  928. return f;
  929. }
  930. catch (Throwable e) {}
  931. return null;
  932. }
  933. private final void clearDataCache()
  934. {
  935. java.util.Iterator it = dataCache.values().iterator();
  936. while (it.hasNext())
  937. {
  938. File f = (File) it.next();
  939. f.delete();
  940. }
  941. }
  942. public final Typeface getTypeFaceFromByteArray (byte[] data)
  943. {
  944. try
  945. {
  946. File f = getDataCacheFile (data);
  947. if (f != null)
  948. return Typeface.createFromFile (f);
  949. }
  950. catch (Exception e)
  951. {
  952. Log.e ("JUCE", e.toString());
  953. }
  954. return null;
  955. }
  956. public final int getAndroidSDKVersion()
  957. {
  958. return android.os.Build.VERSION.SDK_INT;
  959. }
  960. public final String audioManagerGetProperty (String property)
  961. {
  962. Object obj = getSystemService (AUDIO_SERVICE);
  963. if (obj == null)
  964. return null;
  965. java.lang.reflect.Method method;
  966. try
  967. {
  968. method = obj.getClass().getMethod ("getProperty", String.class);
  969. }
  970. catch (SecurityException e) { return null; }
  971. catch (NoSuchMethodException e) { return null; }
  972. if (method == null)
  973. return null;
  974. try
  975. {
  976. return (String) method.invoke (obj, property);
  977. }
  978. catch (java.lang.IllegalArgumentException e) {}
  979. catch (java.lang.IllegalAccessException e) {}
  980. catch (java.lang.reflect.InvocationTargetException e) {}
  981. return null;
  982. }
  983. public final int setCurrentThreadPriority (int priority)
  984. {
  985. android.os.Process.setThreadPriority (android.os.Process.myTid(), priority);
  986. return android.os.Process.getThreadPriority (android.os.Process.myTid());
  987. }
  988. public final boolean hasSystemFeature (String property)
  989. {
  990. return getPackageManager().hasSystemFeature (property);
  991. }
  992. private static class JuceThread extends Thread
  993. {
  994. public JuceThread (long host, String threadName, long threadStackSize)
  995. {
  996. super (null, null, threadName, threadStackSize);
  997. _this = host;
  998. }
  999. public void run()
  1000. {
  1001. runThread(_this);
  1002. }
  1003. private native void runThread (long host);
  1004. private long _this;
  1005. }
  1006. public final Thread createNewThread(long host, String threadName, long threadStackSize)
  1007. {
  1008. return new JuceThread(host, threadName, threadStackSize);
  1009. }
  1010. }