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.

820 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software 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.net.Uri;
  25. import android.os.Bundle;
  26. import android.view.*;
  27. import android.view.inputmethod.BaseInputConnection;
  28. import android.view.inputmethod.EditorInfo;
  29. import android.view.inputmethod.InputConnection;
  30. import android.view.inputmethod.InputMethodManager;
  31. import android.graphics.*;
  32. import android.opengl.*;
  33. import android.text.ClipboardManager;
  34. import android.text.InputType;
  35. import android.util.DisplayMetrics;
  36. import java.io.BufferedInputStream;
  37. import java.io.IOException;
  38. import java.io.InputStream;
  39. import java.io.OutputStream;
  40. import java.net.URL;
  41. import java.net.HttpURLConnection;
  42. import javax.microedition.khronos.egl.EGLConfig;
  43. import javax.microedition.khronos.opengles.GL10;
  44. import android.media.AudioManager;
  45. import android.media.MediaScannerConnection;
  46. import android.media.MediaScannerConnection.MediaScannerConnectionClient;
  47. //==============================================================================
  48. public class JuceAppActivity extends Activity
  49. {
  50. //==============================================================================
  51. static
  52. {
  53. System.loadLibrary ("juce_jni");
  54. }
  55. @Override
  56. public void onCreate (Bundle savedInstanceState)
  57. {
  58. super.onCreate (savedInstanceState);
  59. viewHolder = new ViewHolder (this);
  60. setContentView (viewHolder);
  61. setVolumeControlStream (AudioManager.STREAM_MUSIC);
  62. }
  63. @Override
  64. protected void onDestroy()
  65. {
  66. quitApp();
  67. super.onDestroy();
  68. }
  69. @Override
  70. protected void onPause()
  71. {
  72. if (viewHolder != null)
  73. viewHolder.onPause();
  74. suspendApp();
  75. super.onPause();
  76. }
  77. @Override
  78. protected void onResume()
  79. {
  80. super.onResume();
  81. if (viewHolder != null)
  82. viewHolder.onResume();
  83. resumeApp();
  84. }
  85. @Override
  86. public void onConfigurationChanged (Configuration cfg)
  87. {
  88. super.onConfigurationChanged (cfg);
  89. setContentView (viewHolder);
  90. }
  91. private void callAppLauncher()
  92. {
  93. launchApp (getApplicationInfo().publicSourceDir,
  94. getApplicationInfo().dataDir);
  95. }
  96. //==============================================================================
  97. private native void launchApp (String appFile, String appDataDir);
  98. private native void quitApp();
  99. private native void suspendApp();
  100. private native void resumeApp();
  101. private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
  102. //==============================================================================
  103. public native void deliverMessage (long value);
  104. private android.os.Handler messageHandler = new android.os.Handler();
  105. public final void postMessage (long value)
  106. {
  107. messageHandler.post (new MessageCallback (value));
  108. }
  109. private final class MessageCallback implements Runnable
  110. {
  111. public MessageCallback (long value_) { value = value_; }
  112. public final void run() { deliverMessage (value); }
  113. private long value;
  114. }
  115. //==============================================================================
  116. private ViewHolder viewHolder;
  117. public final ComponentPeerView createNewView (boolean opaque, long host)
  118. {
  119. ComponentPeerView v = new ComponentPeerView (this, opaque, host);
  120. viewHolder.addView (v);
  121. return v;
  122. }
  123. public final void deleteView (ComponentPeerView view)
  124. {
  125. ViewGroup group = (ViewGroup) (view.getParent());
  126. if (group != null)
  127. group.removeView (view);
  128. }
  129. public final void deleteOpenGLView (OpenGLView view)
  130. {
  131. ViewGroup group = (ViewGroup) (view.getParent());
  132. if (group != null)
  133. group.removeView (view);
  134. }
  135. final class ViewHolder extends ViewGroup
  136. {
  137. public ViewHolder (Context context)
  138. {
  139. super (context);
  140. setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
  141. setFocusable (false);
  142. }
  143. protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
  144. {
  145. setScreenSize (getWidth(), getHeight(), getDPI());
  146. if (isFirstResize)
  147. {
  148. isFirstResize = false;
  149. callAppLauncher();
  150. }
  151. }
  152. public final void onPause()
  153. {
  154. for (int i = getChildCount(); --i >= 0;)
  155. {
  156. View v = getChildAt (i);
  157. if (v instanceof ComponentPeerView)
  158. ((ComponentPeerView) v).onPause();
  159. }
  160. }
  161. public final void onResume()
  162. {
  163. for (int i = getChildCount(); --i >= 0;)
  164. {
  165. View v = getChildAt (i);
  166. if (v instanceof ComponentPeerView)
  167. ((ComponentPeerView) v).onResume();
  168. }
  169. }
  170. private final int getDPI()
  171. {
  172. DisplayMetrics metrics = new DisplayMetrics();
  173. getWindowManager().getDefaultDisplay().getMetrics (metrics);
  174. return metrics.densityDpi;
  175. }
  176. private boolean isFirstResize = true;
  177. }
  178. public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
  179. {
  180. canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
  181. }
  182. //==============================================================================
  183. public final String getClipboardContent()
  184. {
  185. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  186. return clipboard.getText().toString();
  187. }
  188. public final void setClipboardContent (String newText)
  189. {
  190. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  191. clipboard.setText (newText);
  192. }
  193. //==============================================================================
  194. public final void showMessageBox (String title, String message, final long callback)
  195. {
  196. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  197. builder.setTitle (title)
  198. .setMessage (message)
  199. .setCancelable (true)
  200. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  201. {
  202. public void onClick (DialogInterface dialog, int id)
  203. {
  204. dialog.cancel();
  205. JuceAppActivity.this.alertDismissed (callback, 0);
  206. }
  207. });
  208. builder.create().show();
  209. }
  210. public final void showOkCancelBox (String title, String message, final long callback)
  211. {
  212. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  213. builder.setTitle (title)
  214. .setMessage (message)
  215. .setCancelable (true)
  216. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  217. {
  218. public void onClick (DialogInterface dialog, int id)
  219. {
  220. dialog.cancel();
  221. JuceAppActivity.this.alertDismissed (callback, 1);
  222. }
  223. })
  224. .setNegativeButton ("Cancel", new DialogInterface.OnClickListener()
  225. {
  226. public void onClick (DialogInterface dialog, int id)
  227. {
  228. dialog.cancel();
  229. JuceAppActivity.this.alertDismissed (callback, 0);
  230. }
  231. });
  232. builder.create().show();
  233. }
  234. public final void showYesNoCancelBox (String title, String message, final long callback)
  235. {
  236. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  237. builder.setTitle (title)
  238. .setMessage (message)
  239. .setCancelable (true)
  240. .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
  241. {
  242. public void onClick (DialogInterface dialog, int id)
  243. {
  244. dialog.cancel();
  245. JuceAppActivity.this.alertDismissed (callback, 1);
  246. }
  247. })
  248. .setNegativeButton ("No", new DialogInterface.OnClickListener()
  249. {
  250. public void onClick (DialogInterface dialog, int id)
  251. {
  252. dialog.cancel();
  253. JuceAppActivity.this.alertDismissed (callback, 2);
  254. }
  255. })
  256. .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
  257. {
  258. public void onClick (DialogInterface dialog, int id)
  259. {
  260. dialog.cancel();
  261. JuceAppActivity.this.alertDismissed (callback, 0);
  262. }
  263. });
  264. builder.create().show();
  265. }
  266. public native void alertDismissed (long callback, int id);
  267. //==============================================================================
  268. public final class ComponentPeerView extends ViewGroup
  269. implements View.OnFocusChangeListener
  270. {
  271. public ComponentPeerView (Context context, boolean opaque_, long host)
  272. {
  273. super (context);
  274. this.host = host;
  275. setWillNotDraw (false);
  276. opaque = opaque_;
  277. setFocusable (true);
  278. setFocusableInTouchMode (true);
  279. setOnFocusChangeListener (this);
  280. requestFocus();
  281. }
  282. //==============================================================================
  283. private native void handlePaint (long host, Canvas canvas);
  284. @Override
  285. public void onDraw (Canvas canvas)
  286. {
  287. handlePaint (host, canvas);
  288. }
  289. @Override
  290. public boolean isOpaque()
  291. {
  292. return opaque;
  293. }
  294. private boolean opaque;
  295. private long host;
  296. //==============================================================================
  297. private native void handleMouseDown (long host, int index, float x, float y, long time);
  298. private native void handleMouseDrag (long host, int index, float x, float y, long time);
  299. private native void handleMouseUp (long host, int index, float x, float y, long time);
  300. @Override
  301. public boolean onTouchEvent (MotionEvent event)
  302. {
  303. int action = event.getAction();
  304. long time = event.getEventTime();
  305. switch (action & MotionEvent.ACTION_MASK)
  306. {
  307. case MotionEvent.ACTION_DOWN:
  308. handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
  309. return true;
  310. case MotionEvent.ACTION_CANCEL:
  311. case MotionEvent.ACTION_UP:
  312. handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
  313. return true;
  314. case MotionEvent.ACTION_MOVE:
  315. {
  316. int n = event.getPointerCount();
  317. for (int i = 0; i < n; ++i)
  318. handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  319. return true;
  320. }
  321. case MotionEvent.ACTION_POINTER_UP:
  322. {
  323. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  324. handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  325. return true;
  326. }
  327. case MotionEvent.ACTION_POINTER_DOWN:
  328. {
  329. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  330. handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  331. return true;
  332. }
  333. default:
  334. break;
  335. }
  336. return false;
  337. }
  338. //==============================================================================
  339. private native void handleKeyDown (long host, int keycode, int textchar);
  340. private native void handleKeyUp (long host, int keycode, int textchar);
  341. public void showKeyboard (String type)
  342. {
  343. InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
  344. if (imm != null)
  345. {
  346. if (type.length() > 0)
  347. {
  348. imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
  349. imm.setInputMethod (getWindowToken(), type);
  350. }
  351. else
  352. {
  353. imm.hideSoftInputFromWindow (getWindowToken(), 0);
  354. }
  355. }
  356. }
  357. @Override
  358. public boolean onKeyDown (int keyCode, KeyEvent event)
  359. {
  360. switch (keyCode)
  361. {
  362. case KeyEvent.KEYCODE_VOLUME_UP:
  363. case KeyEvent.KEYCODE_VOLUME_DOWN:
  364. return super.onKeyDown (keyCode, event);
  365. default: break;
  366. }
  367. handleKeyDown (host, keyCode, event.getUnicodeChar());
  368. return true;
  369. }
  370. @Override
  371. public boolean onKeyUp (int keyCode, KeyEvent event)
  372. {
  373. handleKeyUp (host, keyCode, event.getUnicodeChar());
  374. return true;
  375. }
  376. // this is here to make keyboard entry work on a Galaxy Tab2 10.1
  377. @Override
  378. public InputConnection onCreateInputConnection (EditorInfo outAttrs)
  379. {
  380. outAttrs.actionLabel = "";
  381. outAttrs.hintText = "";
  382. outAttrs.initialCapsMode = 0;
  383. outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
  384. outAttrs.label = "";
  385. outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
  386. outAttrs.inputType = InputType.TYPE_NULL;
  387. return new BaseInputConnection (this, false);
  388. }
  389. //==============================================================================
  390. @Override
  391. protected void onSizeChanged (int w, int h, int oldw, int oldh)
  392. {
  393. super.onSizeChanged (w, h, oldw, oldh);
  394. viewSizeChanged (host);
  395. }
  396. @Override
  397. protected void onLayout (boolean changed, int left, int top, int right, int bottom)
  398. {
  399. for (int i = getChildCount(); --i >= 0;)
  400. requestTransparentRegion (getChildAt (i));
  401. }
  402. private native void viewSizeChanged (long host);
  403. @Override
  404. public void onFocusChange (View v, boolean hasFocus)
  405. {
  406. if (v == this)
  407. focusChanged (host, hasFocus);
  408. }
  409. private native void focusChanged (long host, boolean hasFocus);
  410. public void setViewName (String newName) {}
  411. public boolean isVisible() { return getVisibility() == VISIBLE; }
  412. public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
  413. public boolean containsPoint (int x, int y)
  414. {
  415. return true; //xxx needs to check overlapping views
  416. }
  417. public final void onPause()
  418. {
  419. for (int i = getChildCount(); --i >= 0;)
  420. {
  421. View v = getChildAt (i);
  422. if (v instanceof OpenGLView)
  423. ((OpenGLView) v).onPause();
  424. }
  425. }
  426. public final void onResume()
  427. {
  428. for (int i = getChildCount(); --i >= 0;)
  429. {
  430. View v = getChildAt (i);
  431. if (v instanceof OpenGLView)
  432. ((OpenGLView) v).onResume();
  433. }
  434. }
  435. public OpenGLView createGLView()
  436. {
  437. OpenGLView glView = new OpenGLView (getContext());
  438. addView (glView);
  439. return glView;
  440. }
  441. }
  442. //==============================================================================
  443. public final class OpenGLView extends GLSurfaceView
  444. implements GLSurfaceView.Renderer
  445. {
  446. OpenGLView (Context context)
  447. {
  448. super (context);
  449. setEGLContextClientVersion (2);
  450. setRenderer (this);
  451. setRenderMode (RENDERMODE_WHEN_DIRTY);
  452. }
  453. @Override
  454. public void onSurfaceCreated (GL10 unused, EGLConfig config)
  455. {
  456. contextCreated();
  457. }
  458. @Override
  459. public void onSurfaceChanged (GL10 unused, int width, int height)
  460. {
  461. contextChangedSize();
  462. }
  463. @Override
  464. public void onDrawFrame (GL10 unused)
  465. {
  466. render();
  467. }
  468. private native void contextCreated();
  469. private native void contextChangedSize();
  470. private native void render();
  471. }
  472. //==============================================================================
  473. public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds)
  474. {
  475. Path p = new Path();
  476. paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p);
  477. RectF boundsF = new RectF();
  478. p.computeBounds (boundsF, true);
  479. matrix.mapRect (boundsF);
  480. boundsF.roundOut (bounds);
  481. bounds.left--;
  482. bounds.right++;
  483. final int w = bounds.width();
  484. final int h = Math.max (1, bounds.height());
  485. Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
  486. Canvas c = new Canvas (bm);
  487. matrix.postTranslate (-bounds.left, -bounds.top);
  488. c.setMatrix (matrix);
  489. c.drawPath (p, paint);
  490. final int sizeNeeded = w * h;
  491. if (cachedRenderArray.length < sizeNeeded)
  492. cachedRenderArray = new int [sizeNeeded];
  493. bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
  494. bm.recycle();
  495. return cachedRenderArray;
  496. }
  497. private int[] cachedRenderArray = new int [256];
  498. //==============================================================================
  499. public static class HTTPStream
  500. {
  501. public HTTPStream (HttpURLConnection connection_,
  502. int[] statusCode, StringBuffer responseHeaders) throws IOException
  503. {
  504. connection = connection_;
  505. try
  506. {
  507. inputStream = new BufferedInputStream (connection.getInputStream());
  508. }
  509. catch (IOException e)
  510. {
  511. if (connection.getResponseCode() < org.apache.http.HttpStatus.SC_BAD_REQUEST)
  512. throw e;
  513. }
  514. finally
  515. {
  516. statusCode[0] = connection.getResponseCode();
  517. }
  518. if (statusCode[0] >= org.apache.http.HttpStatus.SC_BAD_REQUEST)
  519. inputStream = connection.getErrorStream();
  520. else
  521. inputStream = connection.getInputStream();
  522. for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
  523. if (entry.getKey() != null && entry.getValue() != null)
  524. responseHeaders.append (entry.getKey() + ": "
  525. + android.text.TextUtils.join (",", entry.getValue()) + "\n");
  526. }
  527. public final void release()
  528. {
  529. try
  530. {
  531. inputStream.close();
  532. }
  533. catch (IOException e)
  534. {}
  535. connection.disconnect();
  536. }
  537. public final int read (byte[] buffer, int numBytes)
  538. {
  539. int num = 0;
  540. try
  541. {
  542. num = inputStream.read (buffer, 0, numBytes);
  543. }
  544. catch (IOException e)
  545. {}
  546. if (num > 0)
  547. position += num;
  548. return num;
  549. }
  550. public final long getPosition() { return position; }
  551. public final long getTotalLength() { return -1; }
  552. public final boolean isExhausted() { return false; }
  553. public final boolean setPosition (long newPos) { return false; }
  554. private HttpURLConnection connection;
  555. private InputStream inputStream;
  556. private long position;
  557. }
  558. public static final HTTPStream createHTTPStream (String address,
  559. boolean isPost, byte[] postData, String headers,
  560. int timeOutMs, int[] statusCode,
  561. StringBuffer responseHeaders,
  562. int numRedirectsToFollow)
  563. {
  564. // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
  565. if (timeOutMs < 0)
  566. timeOutMs = 0;
  567. else if (timeOutMs == 0)
  568. timeOutMs = 30000;
  569. // 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.
  570. // So convert headers string to an array, with an element for each line
  571. String headerLines[] = headers.split("\\n");
  572. for (;;)
  573. {
  574. try
  575. {
  576. HttpURLConnection connection = (HttpURLConnection) (new URL(address).openConnection());
  577. if (connection != null)
  578. {
  579. try
  580. {
  581. connection.setInstanceFollowRedirects (false);
  582. connection.setConnectTimeout (timeOutMs);
  583. connection.setReadTimeout (timeOutMs);
  584. // Set request headers
  585. for (int i = 0; i < headerLines.length; ++i)
  586. {
  587. int pos = headerLines[i].indexOf (":");
  588. if (pos > 0 && pos < headerLines[i].length())
  589. {
  590. String field = headerLines[i].substring (0, pos);
  591. String value = headerLines[i].substring (pos + 1);
  592. if (value.length() > 0)
  593. connection.setRequestProperty (field, value);
  594. }
  595. }
  596. if (isPost)
  597. {
  598. connection.setRequestMethod ("POST");
  599. connection.setDoOutput (true);
  600. if (postData != null)
  601. {
  602. OutputStream out = connection.getOutputStream();
  603. out.write(postData);
  604. out.flush();
  605. }
  606. }
  607. HTTPStream httpStream = new HTTPStream (connection, statusCode, responseHeaders);
  608. // Process redirect & continue as necessary
  609. int status = statusCode[0];
  610. if (--numRedirectsToFollow >= 0
  611. && (status == 301 || status == 302 || status == 303 || status == 307))
  612. {
  613. // Assumes only one occurrence of "Location"
  614. int pos1 = responseHeaders.indexOf ("Location:") + 10;
  615. int pos2 = responseHeaders.indexOf ("\n", pos1);
  616. if (pos2 > pos1)
  617. {
  618. String newLocation = responseHeaders.substring(pos1, pos2);
  619. // Handle newLocation whether it's absolute or relative
  620. URL baseUrl = new URL (address);
  621. URL newUrl = new URL (baseUrl, newLocation);
  622. String transformedNewLocation = newUrl.toString();
  623. if (transformedNewLocation != address)
  624. {
  625. address = transformedNewLocation;
  626. // Clear responseHeaders before next iteration
  627. responseHeaders.delete (0, responseHeaders.length());
  628. continue;
  629. }
  630. }
  631. }
  632. return httpStream;
  633. }
  634. catch (Throwable e)
  635. {
  636. connection.disconnect();
  637. }
  638. }
  639. }
  640. catch (Throwable e) {}
  641. return null;
  642. }
  643. }
  644. public final void launchURL (String url)
  645. {
  646. startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
  647. }
  648. public static final String getLocaleValue (boolean isRegion)
  649. {
  650. java.util.Locale locale = java.util.Locale.getDefault();
  651. return isRegion ? locale.getDisplayCountry (java.util.Locale.US)
  652. : locale.getDisplayLanguage (java.util.Locale.US);
  653. }
  654. //==============================================================================
  655. private final class SingleMediaScanner implements MediaScannerConnectionClient
  656. {
  657. public SingleMediaScanner (Context context, String filename)
  658. {
  659. file = filename;
  660. msc = new MediaScannerConnection (context, this);
  661. msc.connect();
  662. }
  663. @Override
  664. public void onMediaScannerConnected()
  665. {
  666. msc.scanFile (file, null);
  667. }
  668. @Override
  669. public void onScanCompleted (String path, Uri uri)
  670. {
  671. msc.disconnect();
  672. }
  673. private MediaScannerConnection msc;
  674. private String file;
  675. }
  676. public final void scanFile (String filename)
  677. {
  678. new SingleMediaScanner (this, filename);
  679. }
  680. }