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.

1155 lines
39KB

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