diff --git a/.gitignore b/.gitignore index 9f5e35a36c..2c80521770 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ extras/JuceDemo/Builds/VisualStudio2008/Release extras/JuceDemo/Builds/VisualStudio2010/Debug extras/JuceDemo/Builds/VisualStudio2010/Release extras/JuceDemo/Builds/Android/bin -extras/JuceDemo/Builds/Android/src/com extras/JuceDemo/Builds/iPhone/build extras/audio plugin demo/Builds/MacOSX/build extras/audio plugin demo/Builds/Linux/build diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h index 7c80608f35..98897d2db8 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Android.h @@ -42,7 +42,7 @@ public: if (settings.hasType (getValueTreeTypeName())) return new AndroidProjectExporter (project, settings); - return 0; + return nullptr; } //============================================================================== @@ -54,6 +54,9 @@ public: if (getTargetLocation().toString().isEmpty()) getTargetLocation() = getDefaultBuildsRootFolder() + "Android"; + if (getActivityClassPath().toString().isEmpty()) + getActivityClassPath() = createDefaultClassName(); + if (getSDKPath().toString().isEmpty()) getSDKPath() = "${user.home}/SDKs/android-sdk-macosx"; @@ -86,6 +89,9 @@ public: { ProjectExporter::createPropertyEditors (props); + props.add (new TextPropertyComponent (getActivityClassPath(), "Android Activity class name", 256, false), + "The full java class name to use for the app's Activity class."); + props.add (new TextPropertyComponent (getSDKPath(), "Android SDK Path", 1024, false), "The path to the Android SDK folder on the target build machine"); @@ -96,19 +102,40 @@ public: "If enabled, this will set the android.permission.INTERNET flag in the manifest."); } + Value getActivityClassPath() const { return getSetting (Ids::androidActivityClass); } Value getSDKPath() const { return getSetting (Ids::androidSDKPath); } Value getNDKPath() const { return getSetting (Ids::androidNDKPath); } Value getInternetNeeded() const { return getSetting (Ids::androidInternetNeeded); } + String createDefaultClassName() const + { + String s (project.getBundleIdentifier().toString().toLowerCase()); + + if (s.length() > 5 + && s.containsChar ('.') + && s.containsOnly ("abcdefghijklmnopqrstuvwxyz_.") + && ! s.startsWithChar ('.')) + { + if (! s.endsWithChar ('.')) + s << "."; + } + else + { + s = "com.yourcompany."; + } + + return s + CodeHelpers::makeValidIdentifier (project.getProjectFilenameRoot(), false, true, false); + } + //============================================================================== - void create() + void create (const OwnedArray& modules) { const File target (getTargetFolder()); const File jniFolder (target.getChildFile ("jni")); - createDirectoryOrThrow (target.getChildFile ("src/com")); + copyActivityJavaFiles (modules); createDirectoryOrThrow (jniFolder); - createDirectoryOrThrow (target.getChildFile ("res/values")); + createDirectoryOrThrow (target.getChildFile ("res").getChildFile ("values")); createDirectoryOrThrow (target.getChildFile ("libs")); createDirectoryOrThrow (target.getChildFile ("bin")); @@ -156,12 +183,19 @@ protected: { androidDynamicLibs.add ("GLESv1_CM"); androidDynamicLibs.add ("GLESv2"); + + if (getArchitectures().toString().isEmpty()) + getArchitectures() = "armeabi armeabi-v7a"; } + Value getArchitectures() const { return getValue (Ids::androidArchitectures); } + void createPropertyEditors (PropertyListBuilder& props) { createBasicPropertyEditors (props); + props.add (new TextPropertyComponent (getArchitectures(), "Architectures", 256, false), + "A list of the ARM architectures to build (for a fat binary)."); } StringArray androidDynamicLibs; @@ -181,7 +215,7 @@ private: manifest->setAttribute ("xmlns:android", "http://schemas.android.com/apk/res/android"); manifest->setAttribute ("android:versionCode", "1"); manifest->setAttribute ("android:versionName", "1.0"); - manifest->setAttribute ("package", "com.juce"); + manifest->setAttribute ("package", getActivityClassPackage()); XmlElement* screens = manifest->createNewChildElement ("supports-screens"); screens->setAttribute ("android:smallScreens", "true"); @@ -201,7 +235,7 @@ private: app->setAttribute ("android:icon", "@drawable/icon"); XmlElement* act = app->createNewChildElement ("activity"); - act->setAttribute ("android:name", "JuceAppActivity"); + act->setAttribute ("android:name", getActivityName()); act->setAttribute ("android:label", "@string/app_name"); XmlElement* intent = act->createNewChildElement ("intent-filter"); @@ -226,6 +260,63 @@ private: } } + //============================================================================== + String getActivityName() const + { + return getActivityClassPath().toString().fromLastOccurrenceOf (".", false, false); + } + + String getActivityClassPackage() const + { + return getActivityClassPath().toString().upToLastOccurrenceOf (".", false, false); + } + + String getJNIActivityClassName() const + { + return getActivityClassPath().toString().replaceCharacter ('.', '/'); + } + + static LibraryModule* getCoreModule (const OwnedArray& modules) + { + for (int i = modules.size(); --i >= 0;) + if (modules.getUnchecked(i)->getID() == "juce_core") + return modules.getUnchecked(i); + + return nullptr; + } + + void copyActivityJavaFiles (const OwnedArray& modules) + { + const String className (getActivityName()); + const String package (getActivityClassPackage()); + String path (package.replaceCharacter ('.', File::separator)); + + if (path.isEmpty() || className.isEmpty()) + throw SaveError ("Invalid Android Activity class name: " + getActivityClassPath().toString()); + + const File classFolder (getTargetFolder().getChildFile ("src") + .getChildFile (path)); + createDirectoryOrThrow (classFolder); + + LibraryModule* const coreModule = getCoreModule (modules); + + if (coreModule == nullptr) + throw SaveError ("To build an Android app, the juce_core module must be included in your project!"); + + File javaDestFile (classFolder.getChildFile (className + ".java")); + + File javaSourceFile (coreModule->getFolder().getChildFile ("native") + .getChildFile ("java") + .getChildFile ("JuceAppActivity.java")); + + MemoryOutputStream newFile; + newFile << javaSourceFile.loadFileAsString() + .replace ("JuceAppActivity", className) + .replace ("package com.juce;", "package " + package + ";"); + + overwriteFileIfDifferentOrThrow (javaDestFile, newFile); + } + void writeApplicationMk (const File& file) { MemoryOutputStream mo; @@ -235,8 +326,7 @@ private: << newLine << "APP_STL := gnustl_static" << newLine << "APP_CPPFLAGS += -fsigned-char -fexceptions -frtti" << newLine - << "APP_PLATFORM := android-7" << newLine - << "APP_ABI := armeabi armeabi-v7a" << newLine; + << "APP_PLATFORM := android-7" << newLine; overwriteFileIfDifferentOrThrow (file, mo); } @@ -287,8 +377,11 @@ private: { if (config->isDebug() == forDebug) { + const AndroidBuildConfiguration& androidConfig = dynamic_cast (*config); + out << " LOCAL_CPPFLAGS += " << createCPPFlags (*config) << newLine - << getDynamicLibs (dynamic_cast (*config)); + << " APP_ABI := " << androidConfig.getArchitectures().toString() << newLine + << getDynamicLibs (androidConfig); break; } @@ -327,6 +420,8 @@ private: { StringPairArray defines; defines.set ("JUCE_ANDROID", "1"); + defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_')); + defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getJNIActivityClassName() + "\\\""); String flags ("-fsigned-char -fexceptions -frtti"); @@ -371,9 +466,6 @@ private: addNDKBuildStep (proj, "clean", "clean"); - //addLinkStep (proj, "${basedir}/" + rebaseFromProjectFolderToBuildTarget (RelativePath()).toUnixStyle() + "/", "jni/app"); - addLinkStep (proj, "${basedir}/" + getJucePathFromTargetFolder().toUnixStyle() + "/modules/juce_core/native/java/", "src/com/juce"); - addNDKBuildStep (proj, "debug", "CONFIG=Debug"); addNDKBuildStep (proj, "release", "CONFIG=Release"); @@ -396,18 +488,6 @@ private: executable->createNewChildElement ("arg")->setAttribute ("value", arg); } - static void addLinkStep (XmlElement* project, const String& from, const String& to) - { - XmlElement* executable = project->createNewChildElement ("exec"); - executable->setAttribute ("executable", "ln"); - executable->setAttribute ("dir", "${basedir}"); - executable->setAttribute ("failonerror", "false"); - - executable->createNewChildElement ("arg")->setAttribute ("value", "-s"); - executable->createNewChildElement ("arg")->setAttribute ("value", from); - executable->createNewChildElement ("arg")->setAttribute ("value", to); - } - void writeProjectPropertiesFile (const File& file) { MemoryOutputStream mo; diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h index d5b41070b8..920dad3c82 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_MSVC.h @@ -457,7 +457,7 @@ public: } //============================================================================== - void create() + void create (const OwnedArray&) { createIconFile(); @@ -802,7 +802,7 @@ public: if (settings.hasType (getValueTreeTypeName())) return new MSVCProjectExporterVC2005 (project, settings); - return 0; + return nullptr; } protected: @@ -845,7 +845,7 @@ public: } //============================================================================== - void create() + void create (const OwnedArray&) { createIconFile(); diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h index efd241ab8d..41aa34c9e9 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_Make.h @@ -42,7 +42,7 @@ public: if (settings.hasType (getValueTreeTypeName())) return new MakefileProjectExporter (project, settings); - return 0; + return nullptr; } @@ -82,7 +82,7 @@ public: } //============================================================================== - void create() + void create (const OwnedArray&) { Array files; for (int i = 0; i < groups.size(); ++i) diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h index 08857e6c64..7699b26d0a 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExport_XCode.h @@ -73,7 +73,7 @@ public: else if (settings.hasType (getValueTreeTypeName (true))) return new XCodeProjectExporter (project, settings, true); - return 0; + return nullptr; } //============================================================================== @@ -144,7 +144,7 @@ public: } //============================================================================== - void create() + void create (const OwnedArray&) { infoPlistFile = getTargetFolder().getChildFile ("Info.plist"); diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h index e7d0b7eea1..362b07e646 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectExporter.h @@ -55,7 +55,7 @@ public: virtual bool usesMMFiles() const = 0; virtual void createPropertyEditors (PropertyListBuilder&); virtual void launchProject() = 0; - virtual void create() = 0; // may throw a SaveError + virtual void create (const OwnedArray&) = 0; // may throw a SaveError virtual bool shouldFileBeCompiledByDefault (const RelativePath& path) const; virtual bool canCopeWithDuplicateFiles() = 0; diff --git a/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h b/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h index ac7589fbd0..a46130214d 100644 --- a/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h +++ b/extras/Introjucer/Source/Project Saving/jucer_ProjectSaver.h @@ -432,7 +432,7 @@ private: try { - exporter->create(); + exporter->create (modules); } catch (ProjectExporter::SaveError& error) { diff --git a/extras/Introjucer/Source/Project/jucer_Module.h b/extras/Introjucer/Source/Project/jucer_Module.h index e73e0ee7aa..c8170b83ea 100644 --- a/extras/Introjucer/Source/Project/jucer_Module.h +++ b/extras/Introjucer/Source/Project/jucer_Module.h @@ -44,6 +44,7 @@ public: String getVersion() const { return moduleInfo ["version"].toString(); } String getName() const { return moduleInfo ["name"].toString(); } String getDescription() const { return moduleInfo ["description"].toString(); } + const File& getFolder() const { return moduleFolder; } void writeIncludes (ProjectSaver&, OutputStream&); void prepareExporter (ProjectExporter&, ProjectSaver&) const; diff --git a/extras/Introjucer/Source/Utility/jucer_MiscUtilities.cpp b/extras/Introjucer/Source/Utility/jucer_MiscUtilities.cpp index 266f696956..c890f41ff5 100644 --- a/extras/Introjucer/Source/Utility/jucer_MiscUtilities.cpp +++ b/extras/Introjucer/Source/Utility/jucer_MiscUtilities.cpp @@ -134,7 +134,10 @@ String createGCCPreprocessorFlags (const StringPairArray& defs) if (value.isNotEmpty()) def << "=" << value; - s += " -D " + def.quoted(); + if (! def.endsWithChar ('"')) + def = def.quoted(); + + s += " -D " + def; } return s; diff --git a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h index 7c3d63945e..9062bd73f3 100644 --- a/extras/Introjucer/Source/Utility/jucer_PresetIDs.h +++ b/extras/Introjucer/Source/Utility/jucer_PresetIDs.h @@ -97,9 +97,11 @@ namespace Ids DECLARE_ID (useStdCall); DECLARE_ID (showAllCode); DECLARE_ID (useLocalCopy); + DECLARE_ID (androidActivityClass); DECLARE_ID (androidSDKPath); DECLARE_ID (androidNDKPath); DECLARE_ID (androidInternetNeeded); + DECLARE_ID (androidArchitectures); const Identifier class_ ("class"); #undef DECLARE_ID diff --git a/extras/JuceDemo/Builds/Android/AndroidManifest.xml b/extras/JuceDemo/Builds/Android/AndroidManifest.xml index ff797b2b99..c41c43e7e1 100644 --- a/extras/JuceDemo/Builds/Android/AndroidManifest.xml +++ b/extras/JuceDemo/Builds/Android/AndroidManifest.xml @@ -5,7 +5,7 @@ - + diff --git a/extras/JuceDemo/Builds/Android/build.xml b/extras/JuceDemo/Builds/Android/build.xml index 8f4047f5a3..f61023c4d0 100644 --- a/extras/JuceDemo/Builds/Android/build.xml +++ b/extras/JuceDemo/Builds/Android/build.xml @@ -15,11 +15,6 @@ - - - - - diff --git a/extras/JuceDemo/Builds/Android/jni/Android.mk b/extras/JuceDemo/Builds/Android/jni/Android.mk index 534aea681f..c838b2f94d 100644 --- a/extras/JuceDemo/Builds/Android/jni/Android.mk +++ b/extras/JuceDemo/Builds/Android/jni/Android.mk @@ -46,10 +46,12 @@ LOCAL_SRC_FILES := \ ../../../../../modules/juce_video/juce_video.cpp\ ifeq ($(CONFIG),Debug) - LOCAL_CPPFLAGS += -fsigned-char -fexceptions -frtti -g -I "../../JuceLibraryCode" -O0 -D "JUCE_ANDROID=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCE_UNIT_TESTS=1" -D "JUCER_ANDROID_7F0E4A25=1" + LOCAL_CPPFLAGS += -fsigned-char -fexceptions -frtti -g -I "../../JuceLibraryCode" -O0 -D "JUCE_ANDROID=1" -D "JUCE_ANDROID_ACTIVITY_CLASSNAME=com_juce_JuceDemo" -D JUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/juce/JuceDemo\" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCE_UNIT_TESTS=1" -D "JUCER_ANDROID_7F0E4A25=1" + APP_ABI := armeabi LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 else - LOCAL_CPPFLAGS += -fsigned-char -fexceptions -frtti -I "../../JuceLibraryCode" -Os -D "JUCE_ANDROID=1" -D "NDEBUG=1" -D "JUCE_UNIT_TESTS=1" -D "JUCER_ANDROID_7F0E4A25=1" + LOCAL_CPPFLAGS += -fsigned-char -fexceptions -frtti -I "../../JuceLibraryCode" -Os -D "JUCE_ANDROID=1" -D "JUCE_ANDROID_ACTIVITY_CLASSNAME=com_juce_JuceDemo" -D JUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/juce/JuceDemo\" -D "NDEBUG=1" -D "JUCE_UNIT_TESTS=1" -D "JUCER_ANDROID_7F0E4A25=1" + APP_ABI := armeabi armeabi-v7a LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 endif diff --git a/extras/JuceDemo/Builds/Android/jni/Application.mk b/extras/JuceDemo/Builds/Android/jni/Application.mk index beace8400c..ae028cd907 100644 --- a/extras/JuceDemo/Builds/Android/jni/Application.mk +++ b/extras/JuceDemo/Builds/Android/jni/Application.mk @@ -4,4 +4,3 @@ APP_STL := gnustl_static APP_CPPFLAGS += -fsigned-char -fexceptions -frtti APP_PLATFORM := android-7 -APP_ABI := armeabi armeabi-v7a diff --git a/extras/JuceDemo/Builds/Android/src/com/juce/JuceDemo.java b/extras/JuceDemo/Builds/Android/src/com/juce/JuceDemo.java new file mode 100644 index 0000000000..5bae77fa5c --- /dev/null +++ b/extras/JuceDemo/Builds/Android/src/com/juce/JuceDemo.java @@ -0,0 +1,456 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-10 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online 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.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +package com.juce; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Context; +import android.os.Bundle; +import android.view.*; +import android.graphics.*; +import android.text.ClipboardManager; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.HttpURLConnection; + +//============================================================================== +public final class JuceDemo extends Activity +{ + //============================================================================== + static + { + System.loadLibrary ("juce_jni"); + } + + @Override + public final void onCreate (Bundle savedInstanceState) + { + super.onCreate (savedInstanceState); + + viewHolder = new ViewHolder (this); + setContentView (viewHolder); + } + + @Override + protected final void onDestroy() + { + quitApp(); + super.onDestroy(); + } + + private void callAppLauncher() + { + launchApp (getApplicationInfo().publicSourceDir, + getApplicationInfo().dataDir); + } + + //============================================================================== + public native void launchApp (String appFile, String appDataDir); + public native void quitApp(); + public native void setScreenSize (int screenWidth, int screenHeight); + + //============================================================================== + public static final void printToConsole (String s) + { + android.util.Log.i ("Juce", s); + } + + //============================================================================== + 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; + + public final ComponentPeerView createNewView (boolean opaque) + { + ComponentPeerView v = new ComponentPeerView (this, opaque); + viewHolder.addView (v); + return v; + } + + public final void deleteView (ComponentPeerView view) + { + viewHolder.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()); + + if (isFirstResize) + { + isFirstResize = false; + callAppLauncher(); + } + } + + 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 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(); + JuceDemo.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(); + JuceDemo.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceDemo.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(); + JuceDemo.this.alertDismissed (callback, 1); + } + }) + .setNegativeButton ("No", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceDemo.this.alertDismissed (callback, 2); + } + }) + .setNeutralButton ("Cancel", new DialogInterface.OnClickListener() + { + public void onClick (DialogInterface dialog, int id) + { + dialog.cancel(); + JuceDemo.this.alertDismissed (callback, 0); + } + }); + + builder.create().show(); + } + + public native void alertDismissed (long callback, int id); + + //============================================================================== + public class ComponentPeerView extends View + implements View.OnFocusChangeListener + { + public ComponentPeerView (Context context, boolean opaque_) + { + super (context); + opaque = opaque_; + + setFocusable (true); + setFocusableInTouchMode (true); + setOnFocusChangeListener (this); + requestFocus(); + } + + //============================================================================== + private native void handlePaint (Canvas canvas); + + @Override + public void draw (Canvas canvas) + { + handlePaint (canvas); + } + + @Override + public boolean isOpaque() + { + return opaque; + } + + private boolean opaque; + + //============================================================================== + private native void handleMouseDown (float x, float y, long time); + private native void handleMouseDrag (float x, float y, long time); + private native void handleMouseUp (float x, float y, long time); + + @Override + public boolean onTouchEvent (MotionEvent event) + { + switch (event.getAction()) + { + case MotionEvent.ACTION_DOWN: handleMouseDown (event.getX(), event.getY(), event.getEventTime()); return true; + case MotionEvent.ACTION_MOVE: handleMouseDrag (event.getX(), event.getY(), event.getEventTime()); return true; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: handleMouseUp (event.getX(), event.getY(), event.getEventTime()); return true; + default: break; + } + + return false; + } + + //============================================================================== + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) + { + viewSizeChanged(); + } + + @Override + protected void onLayout (boolean changed, int left, int top, int right, int bottom) {} + + private native void viewSizeChanged(); + + @Override + public void onFocusChange (View v, boolean hasFocus) + { + if (v == this) + focusChanged (hasFocus); + } + + private native void focusChanged (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 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 = 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); + + 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_) throws IOException + { + connection = connection_; + inputStream = new BufferedInputStream (connection.getInputStream()); + } + + 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, java.lang.StringBuffer responseHeaders) + { + try + { + HttpURLConnection connection = (HttpURLConnection) (new URL (address).openConnection()); + + if (connection != null) + { + try + { + if (isPost) + { + connection.setConnectTimeout (timeOutMs); + connection.setDoOutput (true); + connection.setChunkedStreamingMode (0); + + OutputStream out = connection.getOutputStream(); + out.write (postData); + out.flush(); + } + + return new HTTPStream (connection); + } + catch (Throwable e) + { + connection.disconnect(); + } + } + } + catch (Throwable e) + {} + + return null; + } +} diff --git a/extras/JuceDemo/Juce Demo.jucer b/extras/JuceDemo/Juce Demo.jucer index c9f0a9c061..8a88c7ba35 100644 --- a/extras/JuceDemo/Juce Demo.jucer +++ b/extras/JuceDemo/Juce Demo.jucer @@ -74,12 +74,12 @@ + androidInternetNeeded="1" bigIcon="f4hwldS" androidActivityClass="com.juce.JuceDemo"> + defines="JUCE_UNIT_TESTS=1" androidArchitectures="armeabi"/> + defines="JUCE_UNIT_TESTS=1" androidArchitectures="armeabi armeabi-v7a"/> diff --git a/modules/juce_core/native/java/ComponentPeerView.java b/modules/juce_core/native/java/ComponentPeerView.java deleted file mode 100644 index edd982e869..0000000000 --- a/modules/juce_core/native/java/ComponentPeerView.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-10 by Raw Material Software Ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the GNU General - Public License (Version 2), as published by the Free Software Foundation. - A copy of the license is included in the JUCE distribution, or can be found - online 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.rawmaterialsoftware.com/juce for more information. - - ============================================================================== -*/ - -package com.juce; - -import android.content.Context; -import android.view.*; -import android.graphics.*; - -//============================================================================== -public class ComponentPeerView extends View - implements View.OnFocusChangeListener -{ - public ComponentPeerView (Context context, boolean opaque_) - { - super (context); - opaque = opaque_; - setFocusable (true); - setFocusableInTouchMode (true); - setOnFocusChangeListener (this); - requestFocus(); - } - - //============================================================================== - private native void handlePaint (Canvas canvas); - - @Override - public void draw (Canvas canvas) - { - handlePaint (canvas); - } - - @Override - public boolean isOpaque() - { - return opaque; - } - - private boolean opaque; - - //============================================================================== - private native void handleMouseDown (float x, float y, long time); - private native void handleMouseDrag (float x, float y, long time); - private native void handleMouseUp (float x, float y, long time); - - @Override - public boolean onTouchEvent (MotionEvent event) - { - switch (event.getAction()) - { - case MotionEvent.ACTION_DOWN: handleMouseDown (event.getX(), event.getY(), event.getEventTime()); return true; - case MotionEvent.ACTION_MOVE: handleMouseDrag (event.getX(), event.getY(), event.getEventTime()); return true; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: handleMouseUp (event.getX(), event.getY(), event.getEventTime()); return true; - default: break; - } - - return false; - } - - //============================================================================== - @Override - protected void onSizeChanged (int w, int h, int oldw, int oldh) - { - viewSizeChanged(); - } - - @Override - protected void onLayout (boolean changed, int left, int top, int right, int bottom) - { - } - - private native void viewSizeChanged(); - - @Override - public void onFocusChange (View v, boolean hasFocus) - { - if (v == this) - focusChanged (hasFocus); - } - - private native void focusChanged (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 - } -} diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java index 950590859c..725300d3e3 100644 --- a/modules/juce_core/native/java/JuceAppActivity.java +++ b/modules/juce_core/native/java/JuceAppActivity.java @@ -28,18 +28,11 @@ package com.juce; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; -import android.os.Bundle; import android.content.Context; -import android.view.ViewGroup; -import android.graphics.Paint; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.graphics.RectF; -import android.graphics.Rect; +import android.os.Bundle; +import android.view.*; +import android.graphics.*; import android.text.ClipboardManager; -import com.juce.ComponentPeerView; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -98,17 +91,10 @@ public final class JuceAppActivity extends Activity messageHandler.post (new MessageCallback (value)); } - final class MessageCallback implements Runnable + private final class MessageCallback implements Runnable { - public MessageCallback (long value_) - { - value = value_; - } - - public final void run() - { - deliverMessage (value); - } + public MessageCallback (long value_) { value = value_; } + public final void run() { deliverMessage (value); } private long value; } @@ -251,7 +237,91 @@ public final class JuceAppActivity extends Activity public native void alertDismissed (long callback, int id); //============================================================================== - public final int[] renderGlyph (char glyph, Paint paint, Matrix matrix, Rect bounds) + public class ComponentPeerView extends View + implements View.OnFocusChangeListener + { + public ComponentPeerView (Context context, boolean opaque_) + { + super (context); + opaque = opaque_; + + setFocusable (true); + setFocusableInTouchMode (true); + setOnFocusChangeListener (this); + requestFocus(); + } + + //============================================================================== + private native void handlePaint (Canvas canvas); + + @Override + public void draw (Canvas canvas) + { + handlePaint (canvas); + } + + @Override + public boolean isOpaque() + { + return opaque; + } + + private boolean opaque; + + //============================================================================== + private native void handleMouseDown (float x, float y, long time); + private native void handleMouseDrag (float x, float y, long time); + private native void handleMouseUp (float x, float y, long time); + + @Override + public boolean onTouchEvent (MotionEvent event) + { + switch (event.getAction()) + { + case MotionEvent.ACTION_DOWN: handleMouseDown (event.getX(), event.getY(), event.getEventTime()); return true; + case MotionEvent.ACTION_MOVE: handleMouseDrag (event.getX(), event.getY(), event.getEventTime()); return true; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: handleMouseUp (event.getX(), event.getY(), event.getEventTime()); return true; + default: break; + } + + return false; + } + + //============================================================================== + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) + { + viewSizeChanged(); + } + + @Override + protected void onLayout (boolean changed, int left, int top, int right, int bottom) {} + + private native void viewSizeChanged(); + + @Override + public void onFocusChange (View v, boolean hasFocus) + { + if (v == this) + focusChanged (hasFocus); + } + + private native void focusChanged (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 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); diff --git a/modules/juce_core/native/juce_android_JNIHelpers.h b/modules/juce_core/native/juce_android_JNIHelpers.h index 20062970ab..411312cd4a 100644 --- a/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/modules/juce_core/native/juce_android_JNIHelpers.h @@ -30,6 +30,10 @@ #define USE_ANDROID_CANVAS 0 #endif +#if ! (defined (JUCE_ANDROID_ACTIVITY_CLASSNAME) && defined (JUCE_ANDROID_ACTIVITY_CLASSPATH)) + #error "The JUCE_ANDROID_ACTIVITY_CLASSNAME and JUCE_ANDROID_ACTIVITY_CLASSPATH macros must be set!" +#endif + //============================================================================== extern JNIEnv* getEnv() noexcept; @@ -226,7 +230,7 @@ private: //============================================================================== #define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ - extern "C" __attribute__ ((visibility("default"))) returnType Java_com_juce_ ## className ## _ ## methodName params + extern "C" __attribute__ ((visibility("default"))) returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params //============================================================================== class AndroidSystem @@ -330,20 +334,20 @@ extern ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ STATICMETHOD (printToConsole, "printToConsole", "(Ljava/lang/String;)V") \ - METHOD (createNewView, "createNewView", "(Z)Lcom/juce/ComponentPeerView;") \ - METHOD (deleteView, "deleteView", "(Lcom/juce/ComponentPeerView;)V") \ + METHOD (createNewView, "createNewView", "(Z)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ + METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ METHOD (postMessage, "postMessage", "(J)V") \ METHOD (finish, "finish", "()V") \ METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ METHOD (renderGlyph, "renderGlyph", "(CLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ - STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;ILjava/lang/StringBuffer;)Lcom/juce/JuceAppActivity$HTTPStream;") \ + STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;ILjava/lang/StringBuffer;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ -DECLARE_JNI_CLASS (JuceAppActivity, "com/juce/JuceAppActivity"); +DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); #undef JNI_CLASS_MEMBERS //============================================================================== diff --git a/modules/juce_core/native/juce_android_Network.cpp b/modules/juce_core/native/juce_android_Network.cpp index db05fcd422..415e73515c 100644 --- a/modules/juce_core/native/juce_android_Network.cpp +++ b/modules/juce_core/native/juce_android_Network.cpp @@ -40,7 +40,7 @@ DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer"); METHOD (isExhausted, "isExhausted", "()Z") \ METHOD (setPosition, "setPosition", "(J)Z") \ -DECLARE_JNI_CLASS (HTTPStream, "com/juce/JuceAppActivity$HTTPStream"); +DECLARE_JNI_CLASS (HTTPStream, JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream"); #undef JNI_CLASS_MEMBERS diff --git a/modules/juce_events/native/juce_android_Messaging.cpp b/modules/juce_events/native/juce_android_Messaging.cpp index 95fa894975..d0047b3e45 100644 --- a/modules/juce_events/native/juce_android_Messaging.cpp +++ b/modules/juce_events/native/juce_android_Messaging.cpp @@ -43,7 +43,7 @@ bool MessageManager::postMessageToSystemQueue (Message* message) return true; } -JUCE_JNI_CALLBACK (JuceAppActivity, deliverMessage, void, (jobject activity, jlong value)) +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, deliverMessage, void, (jobject activity, jlong value)) { Message* const message = (Message*) (pointer_sized_uint) value; MessageManager::getInstance()->deliverMessage (message); diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 7a4fa84f41..995597f59e 100644 --- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -31,8 +31,8 @@ namespace juce { //============================================================================== -JUCE_JNI_CALLBACK (JuceAppActivity, launchApp, void, (JNIEnv* env, jobject activity, - jstring appFile, jstring appDataDir)) +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, + jstring appFile, jstring appDataDir)) { android.initialise (env, activity, appFile, appDataDir); @@ -47,7 +47,7 @@ JUCE_JNI_CALLBACK (JuceAppActivity, launchApp, void, (JNIEnv* env, jobject activ exit (0); } -JUCE_JNI_CALLBACK (JuceAppActivity, quitApp, void, (JNIEnv* env, jobject activity)) +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject activity)) { JUCEApplicationBase::appWillTerminateByForce(); @@ -79,7 +79,7 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); METHOD (invalidate, "invalidate", "(IIII)V") \ METHOD (containsPoint, "containsPoint", "(II)Z") \ -DECLARE_JNI_CLASS (ComponentPeerView, "com/juce/ComponentPeerView"); +DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); #undef JNI_CLASS_MEMBERS @@ -565,7 +565,7 @@ Point AndroidComponentPeer::lastMousePos; //============================================================================== #define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \ - JUCE_JNI_CALLBACK (ComponentPeerView, javaMethodName, returnType, params) \ + JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \ { \ AndroidComponentPeer* const peer = AndroidComponentPeer::findPeerForJavaView (env, view); \ if (peer != nullptr) \ @@ -671,8 +671,8 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconTy return 0; } -JUCE_JNI_CALLBACK (JuceAppActivity, alertDismissed, void, (JNIEnv* env, jobject activity, - jlong callbackAsLong, jint result)) +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject activity, + jlong callbackAsLong, jint result)) { ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong; @@ -703,8 +703,8 @@ void Desktop::getCurrentMonitorPositions (Array >& monitorCoords monitorCoords.add (Rectangle (0, 0, android.screenWidth, android.screenHeight)); } -JUCE_JNI_CALLBACK (JuceAppActivity, setScreenSize, void, (JNIEnv* env, jobject activity, - jint screenWidth, jint screenHeight)) +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv* env, jobject activity, + jint screenWidth, jint screenHeight)) { const bool isSystemInitialised = android.screenWidth != 0; android.screenWidth = screenWidth;