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.

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