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.

1098 lines
37KB

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