Java tutorial
/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ package com.codegarden.nativenavigation; import android.support.design.widget.AppBarLayout; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.os.Looper; import android.os.Handler; import android.os.ParcelUuid; import android.view.*; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.graphics.*; import android.text.ClipboardManager; import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.List; import java.util.Arrays; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.TimerTask; import java.io.*; import java.net.URL; import java.net.HttpURLConnection; import android.media.AudioManager; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; import android.widget.LinearLayout; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; //============================================================================== public class JuceActivity extends AppCompatActivity { //============================================================================== static { System.loadLibrary("juce_jni"); } //============================================================================== //============================================================================== private List<Message> messages; private String drawerTitle = "Messages"; private StringBuilder messageTitle; private DrawerLayout drawerLayout; private void initialiseData() { Gson gson = new Gson(); messages = new ArrayList<>(); Type collectionType = new TypeToken<List<Message>>() { }.getType(); String json = JuceActivity.getJsonData(); Log.d("JSON", json); messages = gson.fromJson(json, collectionType); } public static native void setMessage(String message); public static String getJsonData() { return new String(getJsonDataBytes(), Charset.forName("UTF-8")); } private static native byte[] getJsonDataBytes(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); isScreenSaverEnabled = true; viewHolder = new ViewHolder(this); setContentView(viewHolder); // -- Custom native UI setContentView(R.layout.main_activity); LinearLayout juceViewContainer = (LinearLayout) findViewById(R.id.juce_view_container); juceViewContainer.addView(viewHolder); final Toolbar toolbar = (Toolbar) findViewById(R.id.detail_toolbar); messageTitle = new StringBuilder(); messageTitle.append("JUCE meets Android"); toolbar.setTitle(messageTitle); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own detail action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); initialiseData(); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.left_drawer); LinearLayoutManager llm = new LinearLayoutManager(this); recyclerView.setLayoutManager(llm); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) { public void onDrawerClosed(View view) { toolbar.setTitle(messageTitle); } public void onDrawerOpened(View drawerView) { toolbar.setTitle(drawerTitle); } }; drawerLayout.setDrawerListener(toggle); toggle.syncState(); MessageListAdapter adapter = new MessageListAdapter(messages, messageTitle, drawerLayout); recyclerView.setAdapter(adapter); //-------------------------------------- setVolumeControlStream(AudioManager.STREAM_MUSIC); } @Override protected void onDestroy() { quitApp(); super.onDestroy(); clearDataCache(); } @Override protected void onPause() { suspendApp(); super.onPause(); } @Override protected void onResume() { super.onResume(); resumeApp(); } @Override public void onConfigurationChanged(Configuration cfg) { super.onConfigurationChanged(cfg); setContentView(viewHolder); } private void callAppLauncher() { launchApp(getApplicationInfo().publicSourceDir, getApplicationInfo().dataDir); } //============================================================================== private native void launchApp(String appFile, String appDataDir); private native void quitApp(); private native void suspendApp(); private native void resumeApp(); private native void setScreenSize(int screenWidth, int screenHeight, int dpi); //============================================================================== public native void deliverMessage(long value); private android.os.Handler messageHandler = new android.os.Handler(); public final void postMessage(long value) { messageHandler.post(new MessageCallback(value)); } private final class MessageCallback implements Runnable { public MessageCallback(long value_) { value = value_; } public final void run() { deliverMessage(value); } private long value; } //============================================================================== private ViewHolder viewHolder; private boolean isScreenSaverEnabled; private java.util.Timer keepAliveTimer; public final ComponentPeerView createNewView(boolean opaque, long host) { ComponentPeerView v = new ComponentPeerView(this, opaque, host); viewHolder.addView(v); return v; } public final void deleteView(ComponentPeerView view) { ViewGroup group = (ViewGroup) (view.getParent()); if (group != null) group.removeView(view); } public final void deleteNativeSurfaceView(NativeSurfaceView view) { ViewGroup group = (ViewGroup) (view.getParent()); if (group != null) group.removeView(view); } final class ViewHolder extends ViewGroup { public ViewHolder(Context context) { super(context); setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); setFocusable(false); } protected final void onLayout(boolean changed, int left, int top, int right, int bottom) { setScreenSize(getWidth(), getHeight(), getDPI()); if (isFirstResize) { isFirstResize = false; callAppLauncher(); } } private final int getDPI() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); return metrics.densityDpi; } private boolean isFirstResize = true; } public final void excludeClipRegion(android.graphics.Canvas canvas, float left, float top, float right, float bottom) { canvas.clipRect(left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE); } //============================================================================== public final void setScreenSaver(boolean enabled) { if (isScreenSaverEnabled != enabled) { isScreenSaverEnabled = enabled; if (keepAliveTimer != null) { keepAliveTimer.cancel(); keepAliveTimer = null; } if (enabled) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // If no user input is received after about 3 seconds, the OS will lower the // task's priority, so this timer forces it to be kept active. keepAliveTimer = new java.util.Timer(); keepAliveTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { android.app.Instrumentation instrumentation = new android.app.Instrumentation(); try { instrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_UNKNOWN); } catch (Exception e) { } } }, 2000, 2000); } } } public final boolean getScreenSaver() { return isScreenSaverEnabled; } //============================================================================== public final String getClipboardContent() { ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); return clipboard.getText().toString(); } public final void setClipboardContent(String newText) { ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); clipboard.setText(newText); } //============================================================================== public final void showMessageBox(String title, String message, final long callback) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(title).setMessage(message).setCancelable(true).setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); JuceActivity.this.alertDismissed(callback, 0); } }); builder.create().show(); } public final void showOkCancelBox(String title, String message, final long callback) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(title).setMessage(message).setCancelable(true) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); JuceActivity.this.alertDismissed(callback, 1); } }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); JuceActivity.this.alertDismissed(callback, 0); } }); builder.create().show(); } public final void showYesNoCancelBox(String title, String message, final long callback) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(title).setMessage(message).setCancelable(true) .setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); JuceActivity.this.alertDismissed(callback, 1); } }).setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); JuceActivity.this.alertDismissed(callback, 2); } }).setNeutralButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); JuceActivity.this.alertDismissed(callback, 0); } }); builder.create().show(); } public native void alertDismissed(long callback, int id); //============================================================================== public final class ComponentPeerView extends ViewGroup implements View.OnFocusChangeListener { public ComponentPeerView(Context context, boolean opaque_, long host) { super(context); this.host = host; setWillNotDraw(false); opaque = opaque_; setFocusable(true); setFocusableInTouchMode(true); setOnFocusChangeListener(this); requestFocus(); // swap red and blue colours to match internal opengl texture format ColorMatrix colorMatrix = new ColorMatrix(); float[] colorTransform = { 0, 0, 1.0f, 0, 0, 0, 1.0f, 0, 0, 0, 1.0f, 0, 0, 0, 0, 0, 0, 0, 1.0f, 0 }; colorMatrix.set(colorTransform); paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); } //============================================================================== private native void handlePaint(long host, Canvas canvas, Paint paint); @Override public void onDraw(Canvas canvas) { handlePaint(host, canvas, paint); } @Override public boolean isOpaque() { return opaque; } private boolean opaque; private long host; private Paint paint = new Paint(); //============================================================================== private native void handleMouseDown(long host, int index, float x, float y, long time); private native void handleMouseDrag(long host, int index, float x, float y, long time); private native void handleMouseUp(long host, int index, float x, float y, long time); @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); long time = event.getEventTime(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: handleMouseDown(host, event.getPointerId(0), event.getX(), event.getY(), time); return true; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: handleMouseUp(host, event.getPointerId(0), event.getX(), event.getY(), time); return true; case MotionEvent.ACTION_MOVE: { int n = event.getPointerCount(); for (int i = 0; i < n; ++i) handleMouseDrag(host, event.getPointerId(i), event.getX(i), event.getY(i), time); return true; } case MotionEvent.ACTION_POINTER_UP: { int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; handleMouseUp(host, event.getPointerId(i), event.getX(i), event.getY(i), time); return true; } case MotionEvent.ACTION_POINTER_DOWN: { int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; handleMouseDown(host, event.getPointerId(i), event.getX(i), event.getY(i), time); return true; } default: break; } return false; } //============================================================================== private native void handleKeyDown(long host, int keycode, int textchar); private native void handleKeyUp(long host, int keycode, int textchar); public void showKeyboard(String type) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { if (type.length() > 0) { imm.showSoftInput(this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); imm.setInputMethod(getWindowToken(), type); } else { imm.hideSoftInputFromWindow(getWindowToken(), 0); } } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: return super.onKeyDown(keyCode, event); default: break; } handleKeyDown(host, keyCode, event.getUnicodeChar()); return true; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { handleKeyUp(host, keyCode, event.getUnicodeChar()); return true; } @Override public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE) return super.onKeyMultiple(keyCode, count, event); if (event.getCharacters() != null) { int utf8Char = event.getCharacters().codePointAt(0); handleKeyDown(host, utf8Char, utf8Char); return true; } return false; } // this is here to make keyboard entry work on a Galaxy Tab2 10.1 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.actionLabel = ""; outAttrs.hintText = ""; outAttrs.initialCapsMode = 0; outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; outAttrs.label = ""; outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI; outAttrs.inputType = InputType.TYPE_NULL; return new BaseInputConnection(this, false); } //============================================================================== @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); viewSizeChanged(host); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { for (int i = getChildCount(); --i >= 0;) requestTransparentRegion(getChildAt(i)); } private native void viewSizeChanged(long host); @Override public void onFocusChange(View v, boolean hasFocus) { if (v == this) focusChanged(host, hasFocus); } private native void focusChanged(long host, boolean hasFocus); public void setViewName(String newName) { } public boolean isVisible() { return getVisibility() == VISIBLE; } public void setVisible(boolean b) { setVisibility(b ? VISIBLE : INVISIBLE); } public boolean containsPoint(int x, int y) { return true; //xxx needs to check overlapping views } } //============================================================================== public static class NativeSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private long nativeContext = 0; NativeSurfaceView(Context context, long nativeContextPtr) { super(context); nativeContext = nativeContextPtr; } public Surface getNativeSurface() { Surface retval = null; SurfaceHolder holder = getHolder(); if (holder != null) retval = holder.getSurface(); return retval; } //============================================================================== @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { surfaceChangedNative(nativeContext, holder, format, width, height); } @Override public void surfaceCreated(SurfaceHolder holder) { surfaceCreatedNative(nativeContext, holder); } @Override public void surfaceDestroyed(SurfaceHolder holder) { surfaceDestroyedNative(nativeContext, holder); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); dispatchDrawNative(nativeContext, canvas); } //============================================================================== @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getHolder().addCallback(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getHolder().removeCallback(this); } //============================================================================== private native void dispatchDrawNative(long nativeContextPtr, Canvas canvas); private native void surfaceCreatedNative(long nativeContextptr, SurfaceHolder holder); private native void surfaceDestroyedNative(long nativeContextptr, SurfaceHolder holder); private native void surfaceChangedNative(long nativeContextptr, SurfaceHolder holder, int format, int width, int height); } public NativeSurfaceView createNativeSurfaceView(long nativeSurfacePtr) { return new NativeSurfaceView(this, nativeSurfacePtr); } //============================================================================== public final int[] renderGlyph(char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds) { Path p = new Path(); paint.getTextPath(String.valueOf(glyph), 0, 1, 0.0f, 0.0f, p); RectF boundsF = new RectF(); p.computeBounds(boundsF, true); matrix.mapRect(boundsF); boundsF.roundOut(bounds); bounds.left--; bounds.right++; final int w = bounds.width(); final int h = Math.max(1, bounds.height()); Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bm); matrix.postTranslate(-bounds.left, -bounds.top); c.setMatrix(matrix); c.drawPath(p, paint); final int sizeNeeded = w * h; if (cachedRenderArray.length < sizeNeeded) cachedRenderArray = new int[sizeNeeded]; bm.getPixels(cachedRenderArray, 0, w, 0, 0, w, h); bm.recycle(); return cachedRenderArray; } private int[] cachedRenderArray = new int[256]; //============================================================================== public static class HTTPStream { public HTTPStream(HttpURLConnection connection_, int[] statusCode, StringBuffer responseHeaders) throws IOException { connection = connection_; try { inputStream = new BufferedInputStream(connection.getInputStream()); } catch (IOException e) { if (connection.getResponseCode() < 400) throw e; } finally { statusCode[0] = connection.getResponseCode(); } if (statusCode[0] >= 400) inputStream = connection.getErrorStream(); else inputStream = connection.getInputStream(); for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields() .entrySet()) if (entry.getKey() != null && entry.getValue() != null) responseHeaders.append( entry.getKey() + ": " + android.text.TextUtils.join(",", entry.getValue()) + "\n"); } public final void release() { try { inputStream.close(); } catch (IOException e) { } connection.disconnect(); } public final int read(byte[] buffer, int numBytes) { int num = 0; try { num = inputStream.read(buffer, 0, numBytes); } catch (IOException e) { } if (num > 0) position += num; return num; } public final long getPosition() { return position; } public final long getTotalLength() { return -1; } public final boolean isExhausted() { return false; } public final boolean setPosition(long newPos) { return false; } private HttpURLConnection connection; private InputStream inputStream; private long position; } public static final HTTPStream createHTTPStream(String address, boolean isPost, byte[] postData, String headers, int timeOutMs, int[] statusCode, StringBuffer responseHeaders, int numRedirectsToFollow, String httpRequestCmd) { // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL) if (timeOutMs < 0) timeOutMs = 0; else if (timeOutMs == 0) timeOutMs = 30000; // 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. // So convert headers string to an array, with an element for each line String headerLines[] = headers.split("\\n"); for (;;) { try { HttpURLConnection connection = (HttpURLConnection) (new URL(address).openConnection()); if (connection != null) { try { connection.setInstanceFollowRedirects(false); connection.setConnectTimeout(timeOutMs); connection.setReadTimeout(timeOutMs); // Set request headers for (int i = 0; i < headerLines.length; ++i) { int pos = headerLines[i].indexOf(":"); if (pos > 0 && pos < headerLines[i].length()) { String field = headerLines[i].substring(0, pos); String value = headerLines[i].substring(pos + 1); if (value.length() > 0) connection.setRequestProperty(field, value); } } connection.setRequestMethod(httpRequestCmd); if (isPost) { connection.setDoOutput(true); if (postData != null) { OutputStream out = connection.getOutputStream(); out.write(postData); out.flush(); } } HTTPStream httpStream = new HTTPStream(connection, statusCode, responseHeaders); // Process redirect & continue as necessary int status = statusCode[0]; if (--numRedirectsToFollow >= 0 && (status == 301 || status == 302 || status == 303 || status == 307)) { // Assumes only one occurrence of "Location" int pos1 = responseHeaders.indexOf("Location:") + 10; int pos2 = responseHeaders.indexOf("\n", pos1); if (pos2 > pos1) { String newLocation = responseHeaders.substring(pos1, pos2); // Handle newLocation whether it's absolute or relative URL baseUrl = new URL(address); URL newUrl = new URL(baseUrl, newLocation); String transformedNewLocation = newUrl.toString(); if (transformedNewLocation != address) { address = transformedNewLocation; // Clear responseHeaders before next iteration responseHeaders.delete(0, responseHeaders.length()); continue; } } } return httpStream; } catch (Throwable e) { connection.disconnect(); } } } catch (Throwable e) { } return null; } } public final void launchURL(String url) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); } public static final String getLocaleValue(boolean isRegion) { java.util.Locale locale = java.util.Locale.getDefault(); return isRegion ? locale.getDisplayCountry(java.util.Locale.US) : locale.getDisplayLanguage(java.util.Locale.US); } //============================================================================== private final class SingleMediaScanner implements MediaScannerConnectionClient { public SingleMediaScanner(Context context, String filename) { file = filename; msc = new MediaScannerConnection(context, this); msc.connect(); } @Override public void onMediaScannerConnected() { msc.scanFile(file, null); } @Override public void onScanCompleted(String path, Uri uri) { msc.disconnect(); } private MediaScannerConnection msc; private String file; } public final void scanFile(String filename) { new SingleMediaScanner(this, filename); } public final Typeface getTypeFaceFromAsset(String assetName) { try { return Typeface.createFromAsset(this.getResources().getAssets(), assetName); } catch (Throwable e) { } return null; } final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; ++j) { int v = bytes[j] & 0xff; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0f]; } return new String(hexChars); } final private java.util.Map dataCache = new java.util.HashMap(); synchronized private final File getDataCacheFile(byte[] data) { try { java.security.MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); digest.update(data); String key = bytesToHex(digest.digest()); if (dataCache.containsKey(key)) return (File) dataCache.get(key); File f = new File(this.getCacheDir(), "bindata_" + key); f.delete(); FileOutputStream os = new FileOutputStream(f); os.write(data, 0, data.length); dataCache.put(key, f); return f; } catch (Throwable e) { } return null; } private final void clearDataCache() { java.util.Iterator it = dataCache.values().iterator(); while (it.hasNext()) { File f = (File) it.next(); f.delete(); } } public final Typeface getTypeFaceFromByteArray(byte[] data) { try { File f = getDataCacheFile(data); if (f != null) return Typeface.createFromFile(f); } catch (Exception e) { Log.e("JUCE", e.toString()); } return null; } public final int getAndroidSDKVersion() { return android.os.Build.VERSION.SDK_INT; } public final String audioManagerGetProperty(String property) { Object obj = getSystemService(AUDIO_SERVICE); if (obj == null) return null; java.lang.reflect.Method method; try { method = obj.getClass().getMethod("getProperty", String.class); } catch (SecurityException e) { return null; } catch (NoSuchMethodException e) { return null; } if (method == null) return null; try { return (String) method.invoke(obj, property); } catch (java.lang.IllegalArgumentException e) { } catch (java.lang.IllegalAccessException e) { } catch (java.lang.reflect.InvocationTargetException e) { } return null; } public final int setCurrentThreadPriority(int priority) { android.os.Process.setThreadPriority(android.os.Process.myTid(), priority); return android.os.Process.getThreadPriority(android.os.Process.myTid()); } public final boolean hasSystemFeature(String property) { return getPackageManager().hasSystemFeature(property); } private static class JuceThread extends Thread { public JuceThread(long host) { _this = host; } public void run() { runThread(_this); } private native void runThread(long host); private long _this; } public final Thread createNewThread(long host) { return new JuceThread(host); } }