+
+#define LOGI(...) do {} while (0)
+#define LOGE(...) do {} while (0)
+
+#include "android/log.h"
+
+/* These JNI management functions are taken from SDL2, but modified to refer to pyjnius */
+
+/* #define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x)) */
+/* #define LOGP(x) LOG("python", (x)) */
+#define LOG_TAG "Python_android"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
+
+
+/* Function headers */
+JNIEnv* Android_JNI_GetEnv(void);
+static void Android_JNI_ThreadDestroyed(void*);
+
+static pthread_key_t mThreadKey;
+static JavaVM* mJavaVM;
+
+int Android_JNI_SetupThread(void)
+{
+ Android_JNI_GetEnv();
+ return 1;
+}
+
+/* Library init */
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv *env;
+ mJavaVM = vm;
+ LOGI("JNI_OnLoad called");
+ if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("Failed to get the environment using GetEnv()");
+ return -1;
+ }
+ /*
+ * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
+ * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
+ */
+ if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
+
+ __android_log_print(ANDROID_LOG_ERROR, "pyjniusjni", "Error initializing pthread key");
+ }
+ Android_JNI_SetupThread();
+
+ return JNI_VERSION_1_4;
+}
+
+JNIEnv* Android_JNI_GetEnv(void)
+{
+ /* From http://developer.android.com/guide/practices/jni.html
+ * All threads are Linux threads, scheduled by the kernel.
+ * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
+ * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
+ * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
+ * and cannot make JNI calls.
+ * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
+ * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
+ * is a no-op.
+ * Note: You can call this function any number of times for the same thread, there's no harm in it
+ */
+
+ JNIEnv *env;
+ int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
+ if(status < 0) {
+ LOGE("failed to attach current thread");
+ return 0;
+ }
+
+ /* From http://developer.android.com/guide/practices/jni.html
+ * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
+ * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
+ * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
+ * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
+ * Note: The destructor is not called unless the stored value is != NULL
+ * Note: You can call this function any number of times for the same thread, there's no harm in it
+ * (except for some lost CPU cycles)
+ */
+ pthread_setspecific(mThreadKey, (void*) env);
+
+ return env;
+}
+
+static void Android_JNI_ThreadDestroyed(void* value)
+{
+ /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
+ JNIEnv *env = (JNIEnv*) value;
+ if (env != NULL) {
+ (*mJavaVM)->DetachCurrentThread(mJavaVM);
+ pthread_setspecific(mThreadKey, NULL);
+ }
+}
+
+void *WebView_AndroidGetJNIEnv()
+{
+ return Android_JNI_GetEnv();
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/proguard-project.txt b/p4a/pythonforandroidold/bootstraps/lbry/build/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-hdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-mdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xhdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xhdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-xhdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxhdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-xxhdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable-xxxhdpi/lbry-icon.png
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable/.gitkeep b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/res/drawable/icon.png b/p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable/icon.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/res/drawable/icon.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/drawable/icon.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/chooser_item.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/chooser_item.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/main.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/main.xml
new file mode 100644
index 0000000..123c4b6
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/main.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/project_chooser.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/project_chooser.xml
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/project_empty.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/layout/project_empty.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/res/layout/project_empty.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/res/values/strings.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/res/values/strings.xml
new file mode 100644
index 0000000..daebceb
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ SDL App
+ 0.1
+
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/assets/.gitkeep b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/assets/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/.gitkeep b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java
new file mode 100755
index 0000000..dd10624
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/Octal.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class Octal {
+
+ /**
+ * Parse an octal string from a header buffer. This is used for the file
+ * permission mode value.
+ *
+ * @param header
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ *
+ * @return The long value of the octal string.
+ */
+ public static long parseOctal(byte[] header, int offset, int length) {
+ long result = 0;
+ boolean stillPadding = true;
+
+ int end = offset + length;
+ for (int i = offset; i < end; ++i) {
+ if (header[i] == 0)
+ break;
+
+ if (header[i] == (byte) ' ' || header[i] == '0') {
+ if (stillPadding)
+ continue;
+
+ if (header[i] == (byte) ' ')
+ break;
+ }
+
+ stillPadding = false;
+
+ result = ( result << 3 ) + ( header[i] - '0' );
+ }
+
+ return result;
+ }
+
+ /**
+ * Parse an octal integer from a header buffer.
+ *
+ * @param value
+ * @param buf
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ *
+ * @return The integer value of the octal bytes.
+ */
+ public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+ int idx = length - 1;
+
+ buf[offset + idx] = 0;
+ --idx;
+ buf[offset + idx] = (byte) ' ';
+ --idx;
+
+ if (value == 0) {
+ buf[offset + idx] = (byte) '0';
+ --idx;
+ } else {
+ for (long val = value; idx >= 0 && val > 0; --idx) {
+ buf[offset + idx] = (byte) ( (byte) '0' + (byte) ( val & 7 ) );
+ val = val >> 3;
+ }
+ }
+
+ for (; idx >= 0; --idx) {
+ buf[offset + idx] = (byte) ' ';
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Parse the checksum octal integer from a header buffer.
+ *
+ * @param value
+ * @param buf
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ * @return The integer value of the entry's checksum.
+ */
+ public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+ getOctalBytes( value, buf, offset, length );
+ buf[offset + length - 1] = (byte) ' ';
+ buf[offset + length - 2] = 0;
+ return offset + length;
+ }
+
+ /**
+ * Parse an octal long integer from a header buffer.
+ *
+ * @param value
+ * @param buf
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ *
+ * @return The long value of the octal bytes.
+ */
+ public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+ byte[] temp = new byte[length + 1];
+ getOctalBytes( value, temp, 0, length + 1 );
+ System.arraycopy( temp, 0, buf, offset, length );
+ return offset + length;
+ }
+
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
new file mode 100755
index 0000000..4611e20
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarConstants.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarConstants {
+ public static final int EOF_BLOCK = 1024;
+ public static final int DATA_BLOCK = 512;
+ public static final int HEADER_BLOCK = 512;
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
new file mode 100755
index 0000000..fe01db4
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarEntry.java
@@ -0,0 +1,284 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarEntry {
+ protected File file;
+ protected TarHeader header;
+
+ private TarEntry() {
+ this.file = null;
+ header = new TarHeader();
+ }
+
+ public TarEntry(File file, String entryName) {
+ this();
+ this.file = file;
+ this.extractTarHeader(entryName);
+ }
+
+ public TarEntry(byte[] headerBuf) {
+ this();
+ this.parseTarHeader(headerBuf);
+ }
+
+ /**
+ * Constructor to create an entry from an existing TarHeader object.
+ *
+ * This method is useful to add new entries programmatically (e.g. for
+ * adding files or directories that do not exist in the file system).
+ *
+ * @param header
+ *
+ */
+ public TarEntry(TarHeader header) {
+ this.file = null;
+ this.header = header;
+ }
+
+ public boolean equals(TarEntry it) {
+ return header.name.toString().equals(it.header.name.toString());
+ }
+
+ public boolean isDescendent(TarEntry desc) {
+ return desc.header.name.toString().startsWith(header.name.toString());
+ }
+
+ public TarHeader getHeader() {
+ return header;
+ }
+
+ public String getName() {
+ String name = header.name.toString();
+ if (header.namePrefix != null && !header.namePrefix.toString().equals("")) {
+ name = header.namePrefix.toString() + "/" + name;
+ }
+
+ return name;
+ }
+
+ public void setName(String name) {
+ header.name = new StringBuffer(name);
+ }
+
+ public int getUserId() {
+ return header.userId;
+ }
+
+ public void setUserId(int userId) {
+ header.userId = userId;
+ }
+
+ public int getGroupId() {
+ return header.groupId;
+ }
+
+ public void setGroupId(int groupId) {
+ header.groupId = groupId;
+ }
+
+ public String getUserName() {
+ return header.userName.toString();
+ }
+
+ public void setUserName(String userName) {
+ header.userName = new StringBuffer(userName);
+ }
+
+ public String getGroupName() {
+ return header.groupName.toString();
+ }
+
+ public void setGroupName(String groupName) {
+ header.groupName = new StringBuffer(groupName);
+ }
+
+ public void setIds(int userId, int groupId) {
+ this.setUserId(userId);
+ this.setGroupId(groupId);
+ }
+
+ public void setModTime(long time) {
+ header.modTime = time / 1000;
+ }
+
+ public void setModTime(Date time) {
+ header.modTime = time.getTime() / 1000;
+ }
+
+ public Date getModTime() {
+ return new Date(header.modTime * 1000);
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ public long getSize() {
+ return header.size;
+ }
+
+ public void setSize(long size) {
+ header.size = size;
+ }
+
+ /**
+ * Checks if the org.kamrazafar.jtar entry is a directory
+ *
+ * @return
+ */
+ public boolean isDirectory() {
+ if (this.file != null)
+ return this.file.isDirectory();
+
+ if (header != null) {
+ if (header.linkFlag == TarHeader.LF_DIR)
+ return true;
+
+ if (header.name.toString().endsWith("/"))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Extract header from File
+ *
+ * @param entryName
+ */
+ public void extractTarHeader(String entryName) {
+ header = TarHeader.createHeader(entryName, file.length(), file.lastModified() / 1000, file.isDirectory());
+ }
+
+ /**
+ * Calculate checksum
+ *
+ * @param buf
+ * @return
+ */
+ public long computeCheckSum(byte[] buf) {
+ long sum = 0;
+
+ for (int i = 0; i < buf.length; ++i) {
+ sum += 255 & buf[i];
+ }
+
+ return sum;
+ }
+
+ /**
+ * Writes the header to the byte buffer
+ *
+ * @param outbuf
+ */
+ public void writeEntryHeader(byte[] outbuf) {
+ int offset = 0;
+
+ offset = TarHeader.getNameBytes(header.name, outbuf, offset, TarHeader.NAMELEN);
+ offset = Octal.getOctalBytes(header.mode, outbuf, offset, TarHeader.MODELEN);
+ offset = Octal.getOctalBytes(header.userId, outbuf, offset, TarHeader.UIDLEN);
+ offset = Octal.getOctalBytes(header.groupId, outbuf, offset, TarHeader.GIDLEN);
+
+ long size = header.size;
+
+ offset = Octal.getLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
+ offset = Octal.getLongOctalBytes(header.modTime, outbuf, offset, TarHeader.MODTIMELEN);
+
+ int csOffset = offset;
+ for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
+ outbuf[offset++] = (byte) ' ';
+
+ outbuf[offset++] = header.linkFlag;
+
+ offset = TarHeader.getNameBytes(header.linkName, outbuf, offset, TarHeader.NAMELEN);
+ offset = TarHeader.getNameBytes(header.magic, outbuf, offset, TarHeader.USTAR_MAGICLEN);
+ offset = TarHeader.getNameBytes(header.userName, outbuf, offset, TarHeader.USTAR_USER_NAMELEN);
+ offset = TarHeader.getNameBytes(header.groupName, outbuf, offset, TarHeader.USTAR_GROUP_NAMELEN);
+ offset = Octal.getOctalBytes(header.devMajor, outbuf, offset, TarHeader.USTAR_DEVLEN);
+ offset = Octal.getOctalBytes(header.devMinor, outbuf, offset, TarHeader.USTAR_DEVLEN);
+ offset = TarHeader.getNameBytes(header.namePrefix, outbuf, offset, TarHeader.USTAR_FILENAME_PREFIX);
+
+ for (; offset < outbuf.length;)
+ outbuf[offset++] = 0;
+
+ long checkSum = this.computeCheckSum(outbuf);
+
+ Octal.getCheckSumOctalBytes(checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN);
+ }
+
+ /**
+ * Parses the tar header to the byte buffer
+ *
+ * @param header
+ * @param bh
+ */
+ public void parseTarHeader(byte[] bh) {
+ int offset = 0;
+
+ header.name = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+ offset += TarHeader.NAMELEN;
+
+ header.mode = (int) Octal.parseOctal(bh, offset, TarHeader.MODELEN);
+ offset += TarHeader.MODELEN;
+
+ header.userId = (int) Octal.parseOctal(bh, offset, TarHeader.UIDLEN);
+ offset += TarHeader.UIDLEN;
+
+ header.groupId = (int) Octal.parseOctal(bh, offset, TarHeader.GIDLEN);
+ offset += TarHeader.GIDLEN;
+
+ header.size = Octal.parseOctal(bh, offset, TarHeader.SIZELEN);
+ offset += TarHeader.SIZELEN;
+
+ header.modTime = Octal.parseOctal(bh, offset, TarHeader.MODTIMELEN);
+ offset += TarHeader.MODTIMELEN;
+
+ header.checkSum = (int) Octal.parseOctal(bh, offset, TarHeader.CHKSUMLEN);
+ offset += TarHeader.CHKSUMLEN;
+
+ header.linkFlag = bh[offset++];
+
+ header.linkName = TarHeader.parseName(bh, offset, TarHeader.NAMELEN);
+ offset += TarHeader.NAMELEN;
+
+ header.magic = TarHeader.parseName(bh, offset, TarHeader.USTAR_MAGICLEN);
+ offset += TarHeader.USTAR_MAGICLEN;
+
+ header.userName = TarHeader.parseName(bh, offset, TarHeader.USTAR_USER_NAMELEN);
+ offset += TarHeader.USTAR_USER_NAMELEN;
+
+ header.groupName = TarHeader.parseName(bh, offset, TarHeader.USTAR_GROUP_NAMELEN);
+ offset += TarHeader.USTAR_GROUP_NAMELEN;
+
+ header.devMajor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
+ offset += TarHeader.USTAR_DEVLEN;
+
+ header.devMinor = (int) Octal.parseOctal(bh, offset, TarHeader.USTAR_DEVLEN);
+ offset += TarHeader.USTAR_DEVLEN;
+
+ header.namePrefix = TarHeader.parseName(bh, offset, TarHeader.USTAR_FILENAME_PREFIX);
+ }
+}
\ No newline at end of file
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
new file mode 100755
index 0000000..b9d3a86
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarHeader.java
@@ -0,0 +1,243 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * Header
+ *
+ *
+ * Offset Size Field
+ * 0 100 File name
+ * 100 8 File mode
+ * 108 8 Owner's numeric user ID
+ * 116 8 Group's numeric user ID
+ * 124 12 File size in bytes
+ * 136 12 Last modification time in numeric Unix time format
+ * 148 8 Checksum for header block
+ * 156 1 Link indicator (file type)
+ * 157 100 Name of linked file
+ *
+ *
+ *
+ * File Types
+ *
+ *
+ * Value Meaning
+ * '0' Normal file
+ * (ASCII NUL) Normal file (now obsolete)
+ * '1' Hard link
+ * '2' Symbolic link
+ * '3' Character special
+ * '4' Block special
+ * '5' Directory
+ * '6' FIFO
+ * '7' Contigous
+ *
+ *
+ *
+ *
+ * Ustar header
+ *
+ *
+ * Offset Size Field
+ * 257 6 UStar indicator "ustar"
+ * 263 2 UStar version "00"
+ * 265 32 Owner user name
+ * 297 32 Owner group name
+ * 329 8 Device major number
+ * 337 8 Device minor number
+ * 345 155 Filename prefix
+ *
+ */
+
+public class TarHeader {
+
+ /*
+ * Header
+ */
+ public static final int NAMELEN = 100;
+ public static final int MODELEN = 8;
+ public static final int UIDLEN = 8;
+ public static final int GIDLEN = 8;
+ public static final int SIZELEN = 12;
+ public static final int MODTIMELEN = 12;
+ public static final int CHKSUMLEN = 8;
+ public static final byte LF_OLDNORM = 0;
+
+ /*
+ * File Types
+ */
+ public static final byte LF_NORMAL = (byte) '0';
+ public static final byte LF_LINK = (byte) '1';
+ public static final byte LF_SYMLINK = (byte) '2';
+ public static final byte LF_CHR = (byte) '3';
+ public static final byte LF_BLK = (byte) '4';
+ public static final byte LF_DIR = (byte) '5';
+ public static final byte LF_FIFO = (byte) '6';
+ public static final byte LF_CONTIG = (byte) '7';
+
+ /*
+ * Ustar header
+ */
+
+ public static final String USTAR_MAGIC = "ustar"; // POSIX
+
+ public static final int USTAR_MAGICLEN = 8;
+ public static final int USTAR_USER_NAMELEN = 32;
+ public static final int USTAR_GROUP_NAMELEN = 32;
+ public static final int USTAR_DEVLEN = 8;
+ public static final int USTAR_FILENAME_PREFIX = 155;
+
+ // Header values
+ public StringBuffer name;
+ public int mode;
+ public int userId;
+ public int groupId;
+ public long size;
+ public long modTime;
+ public int checkSum;
+ public byte linkFlag;
+ public StringBuffer linkName;
+ public StringBuffer magic; // ustar indicator and version
+ public StringBuffer userName;
+ public StringBuffer groupName;
+ public int devMajor;
+ public int devMinor;
+ public StringBuffer namePrefix;
+
+ public TarHeader() {
+ this.magic = new StringBuffer(TarHeader.USTAR_MAGIC);
+
+ this.name = new StringBuffer();
+ this.linkName = new StringBuffer();
+
+ String user = System.getProperty("user.name", "");
+
+ if (user.length() > 31)
+ user = user.substring(0, 31);
+
+ this.userId = 0;
+ this.groupId = 0;
+ this.userName = new StringBuffer(user);
+ this.groupName = new StringBuffer("");
+ this.namePrefix = new StringBuffer();
+ }
+
+ /**
+ * Parse an entry name from a header buffer.
+ *
+ * @param name
+ * @param header
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ * @return The header's entry name.
+ */
+ public static StringBuffer parseName(byte[] header, int offset, int length) {
+ StringBuffer result = new StringBuffer(length);
+
+ int end = offset + length;
+ for (int i = offset; i < end; ++i) {
+ if (header[i] == 0)
+ break;
+ result.append((char) header[i]);
+ }
+
+ return result;
+ }
+
+ /**
+ * Determine the number of bytes in an entry name.
+ *
+ * @param name
+ * @param header
+ * The header buffer from which to parse.
+ * @param offset
+ * The offset into the buffer from which to parse.
+ * @param length
+ * The number of header bytes to parse.
+ * @return The number of bytes in a header's entry name.
+ */
+ public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+ int i;
+
+ for (i = 0; i < length && i < name.length(); ++i) {
+ buf[offset + i] = (byte) name.charAt(i);
+ }
+
+ for (; i < length; ++i) {
+ buf[offset + i] = 0;
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Creates a new header for a file/directory entry.
+ *
+ *
+ * @param name
+ * File name
+ * @param size
+ * File size in bytes
+ * @param modTime
+ * Last modification time in numeric Unix time format
+ * @param dir
+ * Is directory
+ *
+ * @return
+ */
+ public static TarHeader createHeader(String entryName, long size, long modTime, boolean dir) {
+ String name = entryName;
+ name = TarUtils.trim(name.replace(File.separatorChar, '/'), '/');
+
+ TarHeader header = new TarHeader();
+ header.linkName = new StringBuffer("");
+
+ if (name.length() > 100) {
+ header.namePrefix = new StringBuffer(name.substring(0, name.lastIndexOf('/')));
+ header.name = new StringBuffer(name.substring(name.lastIndexOf('/') + 1));
+ } else {
+ header.name = new StringBuffer(name);
+ }
+
+ if (dir) {
+ header.mode = 040755;
+ header.linkFlag = TarHeader.LF_DIR;
+ if (header.name.charAt(header.name.length() - 1) != '/') {
+ header.name.append("/");
+ }
+ header.size = 0;
+ } else {
+ header.mode = 0100644;
+ header.linkFlag = TarHeader.LF_NORMAL;
+ header.size = size;
+ }
+
+ header.modTime = modTime;
+ header.checkSum = 0;
+ header.devMajor = 0;
+ header.devMinor = 0;
+
+ return header;
+ }
+}
\ No newline at end of file
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
new file mode 100755
index 0000000..ec50a1b
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarInputStream.java
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarInputStream extends FilterInputStream {
+
+ private static final int SKIP_BUFFER_SIZE = 2048;
+ private TarEntry currentEntry;
+ private long currentFileSize;
+ private long bytesRead;
+ private boolean defaultSkip = false;
+
+ public TarInputStream(InputStream in) {
+ super(in);
+ currentFileSize = 0;
+ bytesRead = 0;
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Not supported
+ *
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ }
+
+ /**
+ * Not supported
+ *
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new IOException("mark/reset not supported");
+ }
+
+ /**
+ * Read a byte
+ *
+ * @see java.io.FilterInputStream#read()
+ */
+ @Override
+ public int read() throws IOException {
+ byte[] buf = new byte[1];
+
+ int res = this.read(buf, 0, 1);
+
+ if (res != -1) {
+ return 0xFF & buf[0];
+ }
+
+ return res;
+ }
+
+ /**
+ * Checks if the bytes being read exceed the entry size and adjusts the byte
+ * array length. Updates the byte counters
+ *
+ *
+ * @see java.io.FilterInputStream#read(byte[], int, int)
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (currentEntry != null) {
+ if (currentFileSize == currentEntry.getSize()) {
+ return -1;
+ } else if ((currentEntry.getSize() - currentFileSize) < len) {
+ len = (int) (currentEntry.getSize() - currentFileSize);
+ }
+ }
+
+ int br = super.read(b, off, len);
+
+ if (br != -1) {
+ if (currentEntry != null) {
+ currentFileSize += br;
+ }
+
+ bytesRead += br;
+ }
+
+ return br;
+ }
+
+ /**
+ * Returns the next entry in the tar file
+ *
+ * @return TarEntry
+ * @throws IOException
+ */
+ public TarEntry getNextEntry() throws IOException {
+ closeCurrentEntry();
+
+ byte[] header = new byte[TarConstants.HEADER_BLOCK];
+ byte[] theader = new byte[TarConstants.HEADER_BLOCK];
+ int tr = 0;
+
+ // Read full header
+ while (tr < TarConstants.HEADER_BLOCK) {
+ int res = read(theader, 0, TarConstants.HEADER_BLOCK - tr);
+
+ if (res < 0) {
+ break;
+ }
+
+ System.arraycopy(theader, 0, header, tr, res);
+ tr += res;
+ }
+
+ // Check if record is null
+ boolean eof = true;
+ for (byte b : header) {
+ if (b != 0) {
+ eof = false;
+ break;
+ }
+ }
+
+ if (!eof) {
+ currentEntry = new TarEntry(header);
+ }
+
+ return currentEntry;
+ }
+
+ /**
+ * Returns the current offset (in bytes) from the beginning of the stream.
+ * This can be used to find out at which point in a tar file an entry's content begins, for instance.
+ */
+ public long getCurrentOffset() {
+ return bytesRead;
+ }
+
+ /**
+ * Closes the current tar entry
+ *
+ * @throws IOException
+ */
+ protected void closeCurrentEntry() throws IOException {
+ if (currentEntry != null) {
+ if (currentEntry.getSize() > currentFileSize) {
+ // Not fully read, skip rest of the bytes
+ long bs = 0;
+ while (bs < currentEntry.getSize() - currentFileSize) {
+ long res = skip(currentEntry.getSize() - currentFileSize - bs);
+
+ if (res == 0 && currentEntry.getSize() - currentFileSize > 0) {
+ // I suspect file corruption
+ throw new IOException("Possible tar file corruption");
+ }
+
+ bs += res;
+ }
+ }
+
+ currentEntry = null;
+ currentFileSize = 0L;
+ skipPad();
+ }
+ }
+
+ /**
+ * Skips the pad at the end of each tar entry file content
+ *
+ * @throws IOException
+ */
+ protected void skipPad() throws IOException {
+ if (bytesRead > 0) {
+ int extra = (int) (bytesRead % TarConstants.DATA_BLOCK);
+
+ if (extra > 0) {
+ long bs = 0;
+ while (bs < TarConstants.DATA_BLOCK - extra) {
+ long res = skip(TarConstants.DATA_BLOCK - extra - bs);
+ bs += res;
+ }
+ }
+ }
+ }
+
+ /**
+ * Skips 'n' bytes on the InputStream
+ * Overrides default implementation of skip
+ *
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ if (defaultSkip) {
+ // use skip method of parent stream
+ // may not work if skip not implemented by parent
+ long bs = super.skip(n);
+ bytesRead += bs;
+
+ return bs;
+ }
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ long left = n;
+ byte[] sBuff = new byte[SKIP_BUFFER_SIZE];
+
+ while (left > 0) {
+ int res = read(sBuff, 0, (int) (left < SKIP_BUFFER_SIZE ? left : SKIP_BUFFER_SIZE));
+ if (res < 0) {
+ break;
+ }
+ left -= res;
+ }
+
+ return n - left;
+ }
+
+ public boolean isDefaultSkip() {
+ return defaultSkip;
+ }
+
+ public void setDefaultSkip(boolean defaultSkip) {
+ this.defaultSkip = defaultSkip;
+ }
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
new file mode 100755
index 0000000..ffdfe87
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarOutputStream.java
@@ -0,0 +1,163 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * @author Kamran Zafar
+ *
+ */
+public class TarOutputStream extends OutputStream {
+ private final OutputStream out;
+ private long bytesWritten;
+ private long currentFileSize;
+ private TarEntry currentEntry;
+
+ public TarOutputStream(OutputStream out) {
+ this.out = out;
+ bytesWritten = 0;
+ currentFileSize = 0;
+ }
+
+ public TarOutputStream(final File fout) throws FileNotFoundException {
+ this.out = new BufferedOutputStream(new FileOutputStream(fout));
+ bytesWritten = 0;
+ currentFileSize = 0;
+ }
+
+ /**
+ * Opens a file for writing.
+ */
+ public TarOutputStream(final File fout, final boolean append) throws IOException {
+ @SuppressWarnings("resource")
+ RandomAccessFile raf = new RandomAccessFile(fout, "rw");
+ final long fileSize = fout.length();
+ if (append && fileSize > TarConstants.EOF_BLOCK) {
+ raf.seek(fileSize - TarConstants.EOF_BLOCK);
+ }
+ out = new BufferedOutputStream(new FileOutputStream(raf.getFD()));
+ }
+
+ /**
+ * Appends the EOF record and closes the stream
+ *
+ * @see java.io.FilterOutputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ closeCurrentEntry();
+ write( new byte[TarConstants.EOF_BLOCK] );
+ out.close();
+ }
+ /**
+ * Writes a byte to the stream and updates byte counters
+ *
+ * @see java.io.FilterOutputStream#write(int)
+ */
+ @Override
+ public void write(int b) throws IOException {
+ out.write( b );
+ bytesWritten += 1;
+
+ if (currentEntry != null) {
+ currentFileSize += 1;
+ }
+ }
+
+ /**
+ * Checks if the bytes being written exceed the current entry size.
+ *
+ * @see java.io.FilterOutputStream#write(byte[], int, int)
+ */
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (currentEntry != null && !currentEntry.isDirectory()) {
+ if (currentEntry.getSize() < currentFileSize + len) {
+ throw new IOException( "The current entry[" + currentEntry.getName() + "] size["
+ + currentEntry.getSize() + "] is smaller than the bytes[" + ( currentFileSize + len )
+ + "] being written." );
+ }
+ }
+
+ out.write( b, off, len );
+
+ bytesWritten += len;
+
+ if (currentEntry != null) {
+ currentFileSize += len;
+ }
+ }
+
+ /**
+ * Writes the next tar entry header on the stream
+ *
+ * @param entry
+ * @throws IOException
+ */
+ public void putNextEntry(TarEntry entry) throws IOException {
+ closeCurrentEntry();
+
+ byte[] header = new byte[TarConstants.HEADER_BLOCK];
+ entry.writeEntryHeader( header );
+
+ write( header );
+
+ currentEntry = entry;
+ }
+
+ /**
+ * Closes the current tar entry
+ *
+ * @throws IOException
+ */
+ protected void closeCurrentEntry() throws IOException {
+ if (currentEntry != null) {
+ if (currentEntry.getSize() > currentFileSize) {
+ throw new IOException( "The current entry[" + currentEntry.getName() + "] of size["
+ + currentEntry.getSize() + "] has not been fully written." );
+ }
+
+ currentEntry = null;
+ currentFileSize = 0;
+
+ pad();
+ }
+ }
+
+ /**
+ * Pads the last content block
+ *
+ * @throws IOException
+ */
+ protected void pad() throws IOException {
+ if (bytesWritten > 0) {
+ int extra = (int) ( bytesWritten % TarConstants.DATA_BLOCK );
+
+ if (extra > 0) {
+ write( new byte[TarConstants.DATA_BLOCK - extra] );
+ }
+ }
+ }
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
new file mode 100755
index 0000000..5016576
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kamranzafar/jtar/TarUtils.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright 2012 Kamran Zafar
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.kamranzafar.jtar;
+
+import java.io.File;
+
+/**
+ * @author Kamran
+ *
+ */
+public class TarUtils {
+ /**
+ * Determines the tar file size of the given folder/file path
+ *
+ * @param path
+ * @return
+ */
+ public static long calculateTarSize(File path) {
+ return tarSize(path) + TarConstants.EOF_BLOCK;
+ }
+
+ private static long tarSize(File dir) {
+ long size = 0;
+
+ if (dir.isFile()) {
+ return entrySize(dir.length());
+ } else {
+ File[] subFiles = dir.listFiles();
+
+ if (subFiles != null && subFiles.length > 0) {
+ for (File file : subFiles) {
+ if (file.isFile()) {
+ size += entrySize(file.length());
+ } else {
+ size += tarSize(file);
+ }
+ }
+ } else {
+ // Empty folder header
+ return TarConstants.HEADER_BLOCK;
+ }
+ }
+
+ return size;
+ }
+
+ private static long entrySize(long fileSize) {
+ long size = 0;
+ size += TarConstants.HEADER_BLOCK; // Header
+ size += fileSize; // File size
+
+ long extra = size % TarConstants.DATA_BLOCK;
+
+ if (extra > 0) {
+ size += (TarConstants.DATA_BLOCK - extra); // pad
+ }
+
+ return size;
+ }
+
+ public static String trim(String s, char c) {
+ StringBuffer tmp = new StringBuffer(s);
+ for (int i = 0; i < tmp.length(); i++) {
+ if (tmp.charAt(i) != c) {
+ break;
+ } else {
+ tmp.deleteCharAt(i);
+ }
+ }
+
+ for (int i = tmp.length() - 1; i >= 0; i--) {
+ if (tmp.charAt(i) != c) {
+ break;
+ } else {
+ tmp.deleteCharAt(i);
+ }
+ }
+
+ return tmp.toString();
+ }
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
new file mode 100644
index 0000000..58a1c5e
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java
@@ -0,0 +1,19 @@
+package org.kivy.android;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.Context;
+
+public class GenericBroadcastReceiver extends BroadcastReceiver {
+
+ GenericBroadcastReceiverCallback listener;
+
+ public GenericBroadcastReceiver(GenericBroadcastReceiverCallback listener) {
+ super();
+ this.listener = listener;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ this.listener.onReceive(context, intent);
+ }
+}
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
new file mode 100644
index 0000000..1a87c98
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java
@@ -0,0 +1,8 @@
+package org.kivy.android;
+
+import android.content.Intent;
+import android.content.Context;
+
+public interface GenericBroadcastReceiverCallback {
+ void onReceive(Context context, Intent intent);
+};
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonActivity.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonService.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/PythonUtil.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonEvent.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/concurrency/PythonLock.java
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java
new file mode 100644
index 0000000..9177b43
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/Project.java
@@ -0,0 +1,99 @@
+package org.kivy.android.launcher;
+
+import java.io.UnsupportedEncodingException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Properties;
+
+import android.util.Log;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+
+/**
+ * This represents a project we've scanned for.
+ */
+public class Project {
+
+ public String dir = null;
+ String title = null;
+ String author = null;
+ Bitmap icon = null;
+ public boolean landscape = false;
+
+ static String decode(String s) {
+ try {
+ return new String(s.getBytes("ISO-8859-1"), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return s;
+ }
+ }
+
+ /**
+ * Scans directory for a android.txt file. If it finds one,
+ * and it looks valid enough, then it creates a new Project,
+ * and returns that. Otherwise, returns null.
+ */
+ public static Project scanDirectory(File dir) {
+
+ // We might have a link file.
+ if (dir.getAbsolutePath().endsWith(".link")) {
+ try {
+
+ // Scan the android.txt file.
+ File propfile = new File(dir, "android.txt");
+ FileInputStream in = new FileInputStream(propfile);
+ Properties p = new Properties();
+ p.load(in);
+ in.close();
+
+ String directory = p.getProperty("directory", null);
+
+ if (directory == null) {
+ return null;
+ }
+
+ dir = new File(directory);
+
+ } catch (Exception e) {
+ Log.i("Project", "Couldn't open link file " + dir, e);
+ }
+ }
+
+ // Make sure we're dealing with a directory.
+ if (! dir.isDirectory()) {
+ return null;
+ }
+
+ try {
+
+ // Scan the android.txt file.
+ File propfile = new File(dir, "android.txt");
+ FileInputStream in = new FileInputStream(propfile);
+ Properties p = new Properties();
+ p.load(in);
+ in.close();
+
+ // Get the various properties.
+ String title = decode(p.getProperty("title", "Untitled"));
+ String author = decode(p.getProperty("author", ""));
+ boolean landscape = p.getProperty("orientation", "portrait").equals("landscape");
+
+ // Create the project object.
+ Project rv = new Project();
+ rv.title = title;
+ rv.author = author;
+ rv.icon = BitmapFactory.decodeFile(new File(dir, "icon.png").getAbsolutePath());
+ rv.landscape = landscape;
+ rv.dir = dir.getAbsolutePath();
+
+ return rv;
+
+ } catch (Exception e) {
+ Log.i("Project", "Couldn't open android.txt", e);
+ }
+
+ return null;
+
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/libsdl/app/SDLActivity.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/AssetExtract.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/Hardware.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonActivity.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/PythonService.java
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/java/org/renpy/android/ResourceManager.java
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/jniLibs/.gitkeep b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/jniLibs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/libs/.gitkeep b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/libs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/baseline_search_black_24.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-hdpi/baseline_search_black_24.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/baseline_search_black_24.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-hdpi/baseline_search_black_24.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-hdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/baseline_search_black_24.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-mdpi/baseline_search_black_24.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/baseline_search_black_24.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-mdpi/baseline_search_black_24.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-mdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/baseline_search_black_24.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xhdpi/baseline_search_black_24.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/baseline_search_black_24.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xhdpi/baseline_search_black_24.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xhdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/baseline_search_black_24.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/baseline_search_black_24.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/baseline_search_black_24.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/baseline_search_black_24.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxhdpi/ic_launcher.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/baseline_search_black_24.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/baseline_search_black_24.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/baseline_search_black_24.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/baseline_search_black_24.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable-xxxhdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable/.gitkeep b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/baseline_search_24.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable/baseline_search_24.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/src/main/res/drawable/baseline_search_24.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/drawable/baseline_search_24.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml
new file mode 100644
index 0000000..1823b13
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/chooser_item.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/main.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/main.xml
new file mode 100644
index 0000000..123c4b6
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/main.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml
new file mode 100644
index 0000000..23828e6
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/project_chooser.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/project_empty.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/project_empty.xml
new file mode 100644
index 0000000..ee54814
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/src/main/res/layout/project_empty.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/AndroidManifest.tmpl.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/Service.tmpl.java b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/Service.tmpl.java
new file mode 100644
index 0000000..3ed10c2
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/Service.tmpl.java
@@ -0,0 +1,77 @@
+package {{ args.package }};
+
+import android.os.Build;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import android.content.Intent;
+import android.content.Context;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.os.Bundle;
+import org.kivy.android.PythonService;
+import org.kivy.android.PythonActivity;
+
+
+public class Service{{ name|capitalize }} extends PythonService {
+ {% if sticky %}
+ @Override
+ public int startType() {
+ return START_STICKY;
+ }
+ {% endif %}
+
+ {% if not foreground %}
+ @Override
+ public boolean canDisplayNotification() {
+ return false;
+ }
+ {% endif %}
+
+ @Override
+ protected void doStartForeground(Bundle extras) {
+ Notification notification;
+ Context context = getApplicationContext();
+ Intent contextIntent = new Intent(context, PythonActivity.class);
+ PendingIntent pIntent = PendingIntent.getActivity(context, 0, contextIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ notification = new Notification(
+ context.getApplicationInfo().icon, "{{ args.name }}", System.currentTimeMillis());
+ try {
+ // prevent using NotificationCompat, this saves 100kb on apk
+ Method func = notification.getClass().getMethod(
+ "setLatestEventInfo", Context.class, CharSequence.class,
+ CharSequence.class, PendingIntent.class);
+ func.invoke(notification, context, "{{ args.name }}", "{{ name| capitalize }}", pIntent);
+ } catch (NoSuchMethodException | IllegalAccessException |
+ IllegalArgumentException | InvocationTargetException e) {
+ }
+ } else {
+ Notification.Builder builder = new Notification.Builder(context);
+ builder.setContentTitle("{{ args.name }}");
+ builder.setContentText("{{ name| capitalize }}");
+ builder.setContentIntent(pIntent);
+ builder.setSmallIcon(context.getApplicationInfo().icon);
+ notification = builder.build();
+ }
+ startForeground({{ service_id }}, notification);
+ }
+
+ static public void start(Context ctx, String pythonServiceArgument) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
+ intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
+ intent.putExtra("androidArgument", argument);
+ intent.putExtra("serviceEntrypoint", "{{ entrypoint }}");
+ intent.putExtra("pythonName", "{{ name }}");
+ intent.putExtra("pythonHome", argument);
+ intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
+ intent.putExtra("pythonServiceArgument", pythonServiceArgument);
+ ctx.startService(intent);
+ }
+
+ static public void stop(Context ctx) {
+ Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
+ ctx.stopService(intent);
+ }
+}
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/activity_service_control.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/activity_service_control.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/activity_service_control.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/activity_service_control.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.properties b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.properties
new file mode 100644
index 0000000..f12e258
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.properties
@@ -0,0 +1,21 @@
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+# 'source.dir' for the location of your java source folder and
+# 'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+# 'key.store' for the location of your keystore and
+# 'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
+key.store=${env.P4A_RELEASE_KEYSTORE}
+key.alias=${env.P4A_RELEASE_KEYALIAS}
+key.store.password=${env.P4A_RELEASE_KEYSTORE_PASSWD}
+key.alias.password=${env.P4A_RELEASE_KEYALIAS_PASSWD}
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.gradle
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.gradle
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.gradle.arm
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/build.tmpl.gradle.arm
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.gradle.arm
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.xml
new file mode 100644
index 0000000..9ab301a
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/build.tmpl.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/colors.tmpl.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/colors.tmpl.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/colors.tmpl.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/colors.tmpl.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/custom_rules.tmpl.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/custom_rules.tmpl.xml
new file mode 100644
index 0000000..a6a7eba
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/custom_rules.tmpl.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ {% if args.launcher %}
+
+ {% else %}
+
+
+
+
+ {% endif %}
+ {% for dir, includes in args.extra_source_dirs %}
+
+ {% endfor %}
+
+
+
+
+
+
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/google-services.json.secret b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/google-services.json.secret
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/google-services.json.secret
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/google-services.json.secret
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/gradle.properties
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/gradle.properties
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/gradle.properties
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/lbry-icon.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/lbry-icon.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/lbry-icon.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/lbry-icon.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..d50bdaa
Binary files /dev/null and b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-hdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0a299eb
Binary files /dev/null and b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-mdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..a336ad5
Binary files /dev/null and b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d423dac
Binary files /dev/null and b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/res/drawable-xxxhdpi/ic_file_download_black_24dp.png
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/strings.tmpl.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/strings.tmpl.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/strings.tmpl.xml
diff --git a/p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml b/p4a/pythonforandroidold/bootstraps/lbry/build/templates/themes.tmpl.xml
similarity index 100%
rename from p4a/pythonforandroid/bootstraps/lbry/build/templates/themes.tmpl.xml
rename to p4a/pythonforandroidold/bootstraps/lbry/build/templates/themes.tmpl.xml
diff --git a/p4a/pythonforandroidold/bootstraps/lbry/build/whitelist.txt b/p4a/pythonforandroidold/bootstraps/lbry/build/whitelist.txt
new file mode 100644
index 0000000..41b06ee
--- /dev/null
+++ b/p4a/pythonforandroidold/bootstraps/lbry/build/whitelist.txt
@@ -0,0 +1 @@
+# put files here that you need to un-blacklist
diff --git a/p4a/pythonforandroidold/build.py b/p4a/pythonforandroidold/build.py
new file mode 100644
index 0000000..374929d
--- /dev/null
+++ b/p4a/pythonforandroidold/build.py
@@ -0,0 +1,900 @@
+from __future__ import print_function
+
+from os.path import (join, realpath, dirname, expanduser, exists,
+ split, isdir)
+from os import environ
+import copy
+import os
+import glob
+import sys
+import re
+import sh
+import subprocess
+
+from pythonforandroid.util import (
+ current_directory, ensure_dir, get_virtualenv_executable,
+ BuildInterruptingException
+)
+from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint)
+from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64
+from pythonforandroid.recipe import CythonRecipe, Recipe
+from pythonforandroid.recommendations import (
+ check_ndk_version, check_target_api, check_ndk_api,
+ RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
+
+
+class Context(object):
+ '''A build context. If anything will be built, an instance this class
+ will be instantiated and used to hold all the build state.'''
+
+ env = environ.copy()
+ # the filepath of toolchain.py
+ root_dir = None
+ # the root dir where builds and dists will be stored
+ storage_dir = None
+
+ # in which bootstraps are copied for building
+ # and recipes are built
+ build_dir = None
+ # the Android project folder where everything ends up
+ dist_dir = None
+ # where Android libs are cached after build
+ # but before being placed in dists
+ libs_dir = None
+ aars_dir = None
+
+ ccache = None # whether to use ccache
+ cython = None # the cython interpreter name
+
+ ndk_platform = None # the ndk platform directory
+
+ dist_name = None # should be deprecated in favour of self.dist.dist_name
+ bootstrap = None
+ bootstrap_build_dir = None
+
+ recipe_build_order = None # Will hold the list of all built recipes
+
+ symlink_java_src = False # If True, will symlink instead of copying during build
+
+ java_build_tool = 'auto'
+
+ @property
+ def packages_path(self):
+ '''Where packages are downloaded before being unpacked'''
+ return join(self.storage_dir, 'packages')
+
+ @property
+ def templates_dir(self):
+ return join(self.root_dir, 'templates')
+
+ @property
+ def libs_dir(self):
+ # Was previously hardcoded as self.build_dir/libs
+ dir = join(self.build_dir, 'libs_collections',
+ self.bootstrap.distribution.name)
+ ensure_dir(dir)
+ return dir
+
+ @property
+ def javaclass_dir(self):
+ # Was previously hardcoded as self.build_dir/java
+ dir = join(self.build_dir, 'javaclasses',
+ self.bootstrap.distribution.name)
+ ensure_dir(dir)
+ return dir
+
+ @property
+ def aars_dir(self):
+ dir = join(self.build_dir, 'aars', self.bootstrap.distribution.name)
+ ensure_dir(dir)
+ return dir
+
+ @property
+ def python_installs_dir(self):
+ dir = join(self.build_dir, 'python-installs')
+ ensure_dir(dir)
+ return dir
+
+ def get_python_install_dir(self):
+ dir = join(self.python_installs_dir, self.bootstrap.distribution.name)
+ return dir
+
+ def setup_dirs(self, storage_dir):
+ '''Calculates all the storage and build dirs, and makes sure
+ the directories exist where necessary.'''
+ self.storage_dir = expanduser(storage_dir)
+ if ' ' in self.storage_dir:
+ raise ValueError('storage dir path cannot contain spaces, please '
+ 'specify a path with --storage-dir')
+ self.build_dir = join(self.storage_dir, 'build')
+ self.dist_dir = join(self.storage_dir, 'dists')
+
+ def ensure_dirs(self):
+ ensure_dir(self.storage_dir)
+ ensure_dir(self.build_dir)
+ ensure_dir(self.dist_dir)
+ ensure_dir(join(self.build_dir, 'bootstrap_builds'))
+ ensure_dir(join(self.build_dir, 'other_builds'))
+
+ @property
+ def android_api(self):
+ '''The Android API being targeted.'''
+ if self._android_api is None:
+ raise ValueError('Tried to access android_api but it has not '
+ 'been set - this should not happen, something '
+ 'went wrong!')
+ return self._android_api
+
+ @android_api.setter
+ def android_api(self, value):
+ self._android_api = value
+
+ @property
+ def ndk_api(self):
+ '''The API number compile against'''
+ if self._ndk_api is None:
+ raise ValueError('Tried to access ndk_api but it has not '
+ 'been set - this should not happen, something '
+ 'went wrong!')
+ return self._ndk_api
+
+ @ndk_api.setter
+ def ndk_api(self, value):
+ self._ndk_api = value
+
+ @property
+ def sdk_dir(self):
+ '''The path to the Android SDK.'''
+ if self._sdk_dir is None:
+ raise ValueError('Tried to access sdk_dir but it has not '
+ 'been set - this should not happen, something '
+ 'went wrong!')
+ return self._sdk_dir
+
+ @sdk_dir.setter
+ def sdk_dir(self, value):
+ self._sdk_dir = value
+
+ @property
+ def ndk_dir(self):
+ '''The path to the Android NDK.'''
+ if self._ndk_dir is None:
+ raise ValueError('Tried to access ndk_dir but it has not '
+ 'been set - this should not happen, something '
+ 'went wrong!')
+ return self._ndk_dir
+
+ @ndk_dir.setter
+ def ndk_dir(self, value):
+ self._ndk_dir = value
+
+ def prepare_build_environment(self,
+ user_sdk_dir,
+ user_ndk_dir,
+ user_android_api,
+ user_ndk_api):
+ '''Checks that build dependencies exist and sets internal variables
+ for the Android SDK etc.
+
+ ..warning:: This *must* be called before trying any build stuff
+
+ '''
+
+ self.ensure_dirs()
+
+ if self._build_env_prepared:
+ return
+
+ ok = True
+
+ # Work out where the Android SDK is
+ sdk_dir = None
+ if user_sdk_dir:
+ sdk_dir = user_sdk_dir
+ # This is the old P4A-specific var
+ if sdk_dir is None:
+ sdk_dir = environ.get('ANDROIDSDK', None)
+ # This seems used more conventionally
+ if sdk_dir is None:
+ sdk_dir = environ.get('ANDROID_HOME', None)
+ # Checks in the buildozer SDK dir, useful for debug tests of p4a
+ if sdk_dir is None:
+ possible_dirs = glob.glob(expanduser(join(
+ '~', '.buildozer', 'android', 'platform', 'android-sdk-*')))
+ possible_dirs = [d for d in possible_dirs if not
+ (d.endswith('.bz2') or d.endswith('.gz'))]
+ if possible_dirs:
+ info('Found possible SDK dirs in buildozer dir: {}'.format(
+ ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
+ info('Will attempt to use SDK at {}'.format(possible_dirs[0]))
+ warning('This SDK lookup is intended for debug only, if you '
+ 'use python-for-android much you should probably '
+ 'maintain your own SDK download.')
+ sdk_dir = possible_dirs[0]
+ if sdk_dir is None:
+ raise BuildInterruptingException('Android SDK dir was not specified, exiting.')
+ self.sdk_dir = realpath(sdk_dir)
+
+ # Check what Android API we're using
+ android_api = None
+ if user_android_api:
+ android_api = user_android_api
+ info('Getting Android API version from user argument: {}'.format(android_api))
+ elif 'ANDROIDAPI' in environ:
+ android_api = environ['ANDROIDAPI']
+ info('Found Android API target in $ANDROIDAPI: {}'.format(android_api))
+ else:
+ info('Android API target was not set manually, using '
+ 'the default of {}'.format(RECOMMENDED_TARGET_API))
+ android_api = RECOMMENDED_TARGET_API
+ android_api = int(android_api)
+ self.android_api = android_api
+
+ check_target_api(android_api, self.archs[0].arch)
+
+ if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
+ avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
+ targets = avdmanager('list', 'target').stdout.decode('utf-8').split('\n')
+ elif exists(join(sdk_dir, 'tools', 'android')):
+ android = sh.Command(join(sdk_dir, 'tools', 'android'))
+ targets = android('list').stdout.decode('utf-8').split('\n')
+ else:
+ raise BuildInterruptingException(
+ 'Could not find `android` or `sdkmanager` binaries in Android SDK',
+ instructions='Make sure the path to the Android SDK is correct')
+ apis = [s for s in targets if re.match(r'^ *API level: ', s)]
+ apis = [re.findall(r'[0-9]+', s) for s in apis]
+ apis = [int(s[0]) for s in apis if s]
+ info('Available Android APIs are ({})'.format(
+ ', '.join(map(str, apis))))
+ if android_api in apis:
+ info(('Requested API target {} is available, '
+ 'continuing.').format(android_api))
+ else:
+ raise BuildInterruptingException(
+ ('Requested API target {} is not available, install '
+ 'it with the SDK android tool.').format(android_api))
+
+ # Find the Android NDK
+ # Could also use ANDROID_NDK, but doesn't look like many tools use this
+ ndk_dir = None
+ if user_ndk_dir:
+ ndk_dir = user_ndk_dir
+ info('Getting NDK dir from from user argument')
+ if ndk_dir is None: # The old P4A-specific dir
+ ndk_dir = environ.get('ANDROIDNDK', None)
+ if ndk_dir is not None:
+ info('Found NDK dir in $ANDROIDNDK: {}'.format(ndk_dir))
+ if ndk_dir is None: # Apparently the most common convention
+ ndk_dir = environ.get('NDK_HOME', None)
+ if ndk_dir is not None:
+ info('Found NDK dir in $NDK_HOME: {}'.format(ndk_dir))
+ if ndk_dir is None: # Another convention (with maven?)
+ ndk_dir = environ.get('ANDROID_NDK_HOME', None)
+ if ndk_dir is not None:
+ info('Found NDK dir in $ANDROID_NDK_HOME: {}'.format(ndk_dir))
+ if ndk_dir is None: # Checks in the buildozer NDK dir, useful
+ # # for debug tests of p4a
+ possible_dirs = glob.glob(expanduser(join(
+ '~', '.buildozer', 'android', 'platform', 'android-ndk-r*')))
+ if possible_dirs:
+ info('Found possible NDK dirs in buildozer dir: {}'.format(
+ ', '.join([d.split(os.sep)[-1] for d in possible_dirs])))
+ info('Will attempt to use NDK at {}'.format(possible_dirs[0]))
+ warning('This NDK lookup is intended for debug only, if you '
+ 'use python-for-android much you should probably '
+ 'maintain your own NDK download.')
+ ndk_dir = possible_dirs[0]
+ if ndk_dir is None:
+ raise BuildInterruptingException('Android NDK dir was not specified')
+ self.ndk_dir = realpath(ndk_dir)
+
+ check_ndk_version(ndk_dir)
+
+ self.ndk = 'crystax' # force crystax detection
+
+ ndk_api = None
+ if user_ndk_api:
+ ndk_api = user_ndk_api
+ info('Getting NDK API version (i.e. minimum supported API) from user argument')
+ elif 'NDKAPI' in environ:
+ ndk_api = environ.get('NDKAPI', None)
+ info('Found Android API target in $NDKAPI')
+ else:
+ ndk_api = min(self.android_api, RECOMMENDED_NDK_API)
+ warning('NDK API target was not set manually, using '
+ 'the default of {} = min(android-api={}, default ndk-api={})'.format(
+ ndk_api, self.android_api, RECOMMENDED_NDK_API))
+ ndk_api = int(ndk_api)
+ self.ndk_api = ndk_api
+
+ check_ndk_api(ndk_api, self.android_api)
+
+ virtualenv = get_virtualenv_executable()
+ if virtualenv is None:
+ raise IOError('Couldn\'t find a virtualenv executable, '
+ 'you must install this to use p4a.')
+ self.virtualenv = virtualenv
+ info('Found virtualenv at {}'.format(virtualenv))
+
+ # path to some tools
+ self.ccache = sh.which("ccache")
+ if not self.ccache:
+ info('ccache is missing, the build will not be optimized in the '
+ 'future.')
+ for cython_fn in ("cython", "cython3", "cython2", "cython-2.7"):
+ cython = sh.which(cython_fn)
+ if cython:
+ self.cython = cython
+ break
+ else:
+ raise BuildInterruptingException('No cython binary found.')
+ if not self.cython:
+ ok = False
+ warning("Missing requirement: cython is not installed")
+
+ # This would need to be changed if supporting multiarch APKs
+ arch = self.archs[0]
+ platform_dir = arch.platform_dir
+ toolchain_prefix = arch.toolchain_prefix
+ toolchain_version = None
+ self.ndk_platform = join(
+ self.ndk_dir,
+ 'platforms',
+ 'android-{}'.format(self.ndk_api),
+ platform_dir)
+ if not exists(self.ndk_platform):
+ warning('ndk_platform doesn\'t exist: {}'.format(
+ self.ndk_platform))
+ ok = False
+
+ py_platform = sys.platform
+ if py_platform in ['linux2', 'linux3']:
+ py_platform = 'linux'
+
+ toolchain_versions = []
+ toolchain_path = join(self.ndk_dir, 'toolchains')
+ if isdir(toolchain_path):
+ toolchain_contents = glob.glob('{}/{}-*'.format(toolchain_path,
+ toolchain_prefix))
+ toolchain_versions = [split(path)[-1][len(toolchain_prefix) + 1:]
+ for path in toolchain_contents]
+ else:
+ warning('Could not find toolchain subdirectory!')
+ ok = False
+ toolchain_versions.sort()
+
+ toolchain_versions_gcc = []
+ for toolchain_version in toolchain_versions:
+ if toolchain_version[0].isdigit():
+ # GCC toolchains begin with a number
+ toolchain_versions_gcc.append(toolchain_version)
+
+ if toolchain_versions:
+ info('Found the following toolchain versions: {}'.format(
+ toolchain_versions))
+ info('Picking the latest gcc toolchain, here {}'.format(
+ toolchain_versions_gcc[-1]))
+ toolchain_version = toolchain_versions_gcc[-1]
+ else:
+ warning('Could not find any toolchain for {}!'.format(
+ toolchain_prefix))
+ ok = False
+
+ self.toolchain_prefix = toolchain_prefix
+ self.toolchain_version = toolchain_version
+ # Modify the path so that sh finds modules appropriately
+ environ['PATH'] = (
+ '{ndk_dir}/toolchains/{toolchain_prefix}-{toolchain_version}/'
+ 'prebuilt/{py_platform}-x86/bin/:{ndk_dir}/toolchains/'
+ '{toolchain_prefix}-{toolchain_version}/prebuilt/'
+ '{py_platform}-x86_64/bin/:{ndk_dir}:{sdk_dir}/'
+ 'tools:{path}').format(
+ sdk_dir=self.sdk_dir, ndk_dir=self.ndk_dir,
+ toolchain_prefix=toolchain_prefix,
+ toolchain_version=toolchain_version,
+ py_platform=py_platform, path=environ.get('PATH'))
+
+ for executable in ("pkg-config", "autoconf", "automake", "libtoolize",
+ "tar", "bzip2", "unzip", "make", "gcc", "g++"):
+ if not sh.which(executable):
+ warning("Missing executable: {} is not installed".format(
+ executable))
+
+ if not ok:
+ raise BuildInterruptingException(
+ 'python-for-android cannot continue due to the missing executables above')
+
+ def __init__(self):
+ super(Context, self).__init__()
+ self.include_dirs = []
+
+ self._build_env_prepared = False
+
+ self._sdk_dir = None
+ self._ndk_dir = None
+ self._android_api = None
+ self._ndk_api = None
+ self.ndk = None
+
+ self.toolchain_prefix = None
+ self.toolchain_version = None
+
+ self.local_recipes = None
+ self.copy_libs = False
+
+ # this list should contain all Archs, it is pruned later
+ self.archs = (
+ ArchARM(self),
+ ArchARMv7_a(self),
+ Archx86(self),
+ Archx86_64(self),
+ ArchAarch_64(self),
+ )
+
+ self.root_dir = realpath(dirname(__file__))
+
+ # remove the most obvious flags that can break the compilation
+ self.env.pop("LDFLAGS", None)
+ self.env.pop("ARCHFLAGS", None)
+ self.env.pop("CFLAGS", None)
+
+ self.python_recipe = None # Set by TargetPythonRecipe
+
+ def set_archs(self, arch_names):
+ all_archs = self.archs
+ new_archs = set()
+ for name in arch_names:
+ matching = [arch for arch in all_archs if arch.arch == name]
+ for match in matching:
+ new_archs.add(match)
+ self.archs = list(new_archs)
+ if not self.archs:
+ raise BuildInterruptingException('Asked to compile for no Archs, so failing.')
+ info('Will compile for the following archs: {}'.format(
+ ', '.join([arch.arch for arch in self.archs])))
+
+ def prepare_bootstrap(self, bs):
+ bs.ctx = self
+ self.bootstrap = bs
+ self.bootstrap.prepare_build_dir()
+ self.bootstrap_build_dir = self.bootstrap.build_dir
+
+ def prepare_dist(self, name):
+ self.dist_name = name
+ self.bootstrap.prepare_dist_dir(self.dist_name)
+
+ def get_site_packages_dir(self, arch=None):
+ '''Returns the location of site-packages in the python-install build
+ dir.
+ '''
+ if self.python_recipe.name == 'python2legacy':
+ return join(self.get_python_install_dir(),
+ 'lib', 'python2.7', 'site-packages')
+ return self.get_python_install_dir()
+
+ def get_libs_dir(self, arch):
+ '''The libs dir for a given arch.'''
+ ensure_dir(join(self.libs_dir, arch))
+ return join(self.libs_dir, arch)
+
+ def has_lib(self, arch, lib):
+ return exists(join(self.get_libs_dir(arch), lib))
+
+ def has_package(self, name, arch=None):
+ # If this is a file path, it'll need special handling:
+ if (name.find("/") >= 0 or name.find("\\") >= 0) and \
+ name.find("://") < 0: # (:// would indicate an url)
+ if not os.path.exists(name):
+ # Non-existing dir, cannot look this up.
+ return False
+ if os.path.exists(os.path.join(name, "setup.py")):
+ # Get name from setup.py:
+ name = subprocess.check_output([
+ sys.executable, "setup.py", "--name"],
+ cwd=name)
+ try:
+ name = name.decode('utf-8', 'replace')
+ except AttributeError:
+ pass
+ name = name.strip()
+ if len(name) == 0:
+ # Failed to look up any meaningful name.
+ return False
+ else:
+ # A folder with whatever, cannot look this up.
+ return False
+
+ # Try to look up recipe by name:
+ try:
+ recipe = Recipe.get_recipe(name, self)
+ except ValueError:
+ pass
+ else:
+ name = getattr(recipe, 'site_packages_name', None) or name
+ name = name.replace('.', '/')
+ site_packages_dir = self.get_site_packages_dir(arch)
+ return (exists(join(site_packages_dir, name)) or
+ exists(join(site_packages_dir, name + '.py')) or
+ exists(join(site_packages_dir, name + '.pyc')) or
+ exists(join(site_packages_dir, name + '.pyo')) or
+ exists(join(site_packages_dir, name + '.so')) or
+ glob.glob(join(site_packages_dir, name + '-*.egg')))
+
+ def not_has_package(self, name, arch=None):
+ return not self.has_package(name, arch)
+
+
+def build_recipes(build_order, python_modules, ctx):
+ # Put recipes in correct build order
+ info_notify("Recipe build order is {}".format(build_order))
+ if python_modules:
+ python_modules = sorted(set(python_modules))
+ info_notify(
+ ('The requirements ({}) were not found as recipes, they will be '
+ 'installed with pip.').format(', '.join(python_modules)))
+
+ recipes = [Recipe.get_recipe(name, ctx) for name in build_order]
+
+ # download is arch independent
+ info_main('# Downloading recipes ')
+ for recipe in recipes:
+ recipe.download_if_necessary()
+
+ for arch in ctx.archs:
+ info_main('# Building all recipes for arch {}'.format(arch.arch))
+
+ info_main('# Unpacking recipes')
+ for recipe in recipes:
+ ensure_dir(recipe.get_build_container_dir(arch.arch))
+ recipe.prepare_build_dir(arch.arch)
+
+ info_main('# Prebuilding recipes')
+ # 2) prebuild packages
+ for recipe in recipes:
+ info_main('Prebuilding {} for {}'.format(recipe.name, arch.arch))
+ recipe.prebuild_arch(arch)
+ recipe.apply_patches(arch)
+
+ # 3) build packages
+ info_main('# Building recipes')
+ for recipe in recipes:
+ info_main('Building {} for {}'.format(recipe.name, arch.arch))
+ if recipe.should_build(arch):
+ recipe.build_arch(arch)
+ else:
+ info('{} said it is already built, skipping'
+ .format(recipe.name))
+
+ # 4) biglink everything
+ info_main('# Biglinking object files')
+ if not ctx.python_recipe or not ctx.python_recipe.from_crystax:
+ biglink(ctx, arch)
+ else:
+ info('NDK is crystax, skipping biglink (will this work?)')
+
+ # 5) postbuild packages
+ info_main('# Postbuilding recipes')
+ for recipe in recipes:
+ info_main('Postbuilding {} for {}'.format(recipe.name, arch.arch))
+ recipe.postbuild_arch(arch)
+
+ info_main('# Installing pure Python modules')
+ run_pymodules_install(ctx, python_modules)
+
+ return
+
+
+def run_pymodules_install(ctx, modules):
+ modules = list(filter(ctx.not_has_package, modules))
+
+ if not modules:
+ info('There are no Python modules to install, skipping')
+ return
+
+ info('The requirements ({}) don\'t have recipes, attempting to install '
+ 'them with pip'.format(', '.join(modules)))
+ info('If this fails, it may mean that the module has compiled '
+ 'components and needs a recipe.')
+
+ venv = sh.Command(ctx.virtualenv)
+ with current_directory(join(ctx.build_dir)):
+ shprint(venv,
+ '--python=python{}.{}'.format(
+ ctx.python_recipe.major_minor_version_string.partition(".")[0],
+ ctx.python_recipe.major_minor_version_string.partition(".")[2]
+ ),
+ 'venv'
+ )
+
+ info('Creating a requirements.txt file for the Python modules')
+ with open('requirements.txt', 'w') as fileh:
+ for module in modules:
+ key = 'VERSION_' + module
+ if key in environ:
+ line = '{}=={}\n'.format(module, environ[key])
+ else:
+ line = '{}\n'.format(module)
+ fileh.write(line)
+
+ # Prepare base environment and upgrade pip:
+ base_env = copy.copy(os.environ)
+ base_env["PYTHONPATH"] = ctx.get_site_packages_dir()
+ info('Upgrade pip to latest version')
+ shprint(sh.bash, '-c', (
+ "source venv/bin/activate && pip install -U pip"
+ ), _env=copy.copy(base_env))
+
+ # Install Cython in case modules need it to build:
+ info('Install Cython in case one of the modules needs it to build')
+ shprint(sh.bash, '-c', (
+ "venv/bin/pip install Cython"
+ ), _env=copy.copy(base_env))
+
+ # Get environment variables for build (with CC/compiler set):
+ standard_recipe = CythonRecipe()
+ standard_recipe.ctx = ctx
+ # (note: following line enables explicit -lpython... linker options)
+ standard_recipe.call_hostpython_via_targetpython = False
+ recipe_env = standard_recipe.get_recipe_env(ctx.archs[0])
+ env = copy.copy(base_env)
+ env.update(recipe_env)
+
+ info('Installing Python modules with pip')
+ info('IF THIS FAILS, THE MODULES MAY NEED A RECIPE. '
+ 'A reason for this is often modules compiling '
+ 'native code that is unaware of Android cross-compilation '
+ 'and does not work without additional '
+ 'changes / workarounds.')
+
+ # Make sure our build package dir is available, and the virtualenv
+ # site packages come FIRST (so the proper pip version is used):
+ env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir()
+ env["PYTHONPATH"] = os.path.abspath(join(
+ ctx.build_dir, "venv", "lib",
+ "python" + ctx.python_recipe.major_minor_version_string,
+ "site-packages")) + ":" + env["PYTHONPATH"]
+
+ '''
+ # Do actual install:
+ shprint(sh.bash, '-c', (
+ "venv/bin/pip " +
+ "install -v --target '{0}' --no-deps -r requirements.txt"
+ ).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")),
+ _env=copy.copy(env))
+ '''
+
+ # use old install script
+ shprint(sh.bash, '-c', (
+ "source venv/bin/activate && env CC=/bin/false CXX=/bin/false "
+ "PYTHONPATH={0} pip install --target '{0}' --no-deps -r requirements.txt"
+ ).format(ctx.get_site_packages_dir()))
+
+ # Strip object files after potential Cython or native code builds:
+ standard_recipe.strip_object_files(ctx.archs[0], env,
+ build_dir=ctx.build_dir)
+
+
+def biglink(ctx, arch):
+ # First, collate object files from each recipe
+ info('Collating object files from each recipe')
+ obj_dir = join(ctx.bootstrap.build_dir, 'collated_objects')
+ ensure_dir(obj_dir)
+ recipes = [Recipe.get_recipe(name, ctx) for name in ctx.recipe_build_order]
+ for recipe in recipes:
+ recipe_obj_dir = join(recipe.get_build_container_dir(arch.arch),
+ 'objects_{}'.format(recipe.name))
+ if not exists(recipe_obj_dir):
+ info('{} recipe has no biglinkable files dir, skipping'
+ .format(recipe.name))
+ continue
+ files = glob.glob(join(recipe_obj_dir, '*'))
+ if not len(files):
+ info('{} recipe has no biglinkable files, skipping'
+ .format(recipe.name))
+ continue
+ info('{} recipe has object files, copying'.format(recipe.name))
+ files.append(obj_dir)
+ shprint(sh.cp, '-r', *files)
+
+ env = arch.get_env()
+ env['LDFLAGS'] = env['LDFLAGS'] + ' -L{}'.format(
+ join(ctx.bootstrap.build_dir, 'obj', 'local', arch.arch))
+
+ if not len(glob.glob(join(obj_dir, '*'))):
+ info('There seem to be no libraries to biglink, skipping.')
+ return
+ info('Biglinking')
+ info('target {}'.format(join(ctx.get_libs_dir(arch.arch),
+ 'libpymodules.so')))
+ do_biglink = copylibs_function if ctx.copy_libs else biglink_function
+
+ # Move to the directory containing crtstart_so.o and crtend_so.o
+ # This is necessary with newer NDKs? A gcc bug?
+ with current_directory(join(ctx.ndk_platform, 'usr', 'lib')):
+ do_biglink(
+ join(ctx.get_libs_dir(arch.arch), 'libpymodules.so'),
+ obj_dir.split(' '),
+ extra_link_dirs=[join(ctx.bootstrap.build_dir,
+ 'obj', 'local', arch.arch),
+ os.path.abspath('.')],
+ env=env)
+
+
+def biglink_function(soname, objs_paths, extra_link_dirs=[], env=None):
+ print('objs_paths are', objs_paths)
+ sofiles = []
+
+ for directory in objs_paths:
+ for fn in os.listdir(directory):
+ fn = os.path.join(directory, fn)
+
+ if not fn.endswith(".so.o"):
+ continue
+ if not os.path.exists(fn[:-2] + ".libs"):
+ continue
+
+ sofiles.append(fn[:-2])
+
+ # The raw argument list.
+ args = []
+
+ for fn in sofiles:
+ afn = fn + ".o"
+ libsfn = fn + ".libs"
+
+ args.append(afn)
+ with open(libsfn) as fd:
+ data = fd.read()
+ args.extend(data.split(" "))
+
+ unique_args = []
+ while args:
+ a = args.pop()
+ if a in ('-L', ):
+ continue
+ if a not in unique_args:
+ unique_args.insert(0, a)
+
+ for dir in extra_link_dirs:
+ link = '-L{}'.format(dir)
+ if link not in unique_args:
+ unique_args.append(link)
+
+ cc_name = env['CC']
+ cc = sh.Command(cc_name.split()[0])
+ cc = cc.bake(*cc_name.split()[1:])
+
+ shprint(cc, '-shared', '-O3', '-o', soname, *unique_args, _env=env)
+
+
+def copylibs_function(soname, objs_paths, extra_link_dirs=[], env=None):
+ print('objs_paths are', objs_paths)
+
+ re_needso = re.compile(r'^.*\(NEEDED\)\s+Shared library: \[lib(.*)\.so\]\s*$')
+ blacklist_libs = (
+ 'c',
+ 'stdc++',
+ 'dl',
+ 'python2.7',
+ 'sdl',
+ 'sdl_image',
+ 'sdl_ttf',
+ 'z',
+ 'm',
+ 'GLESv2',
+ 'jpeg',
+ 'png',
+ 'log',
+
+ # bootstrap takes care of sdl2 libs (if applicable)
+ 'SDL2',
+ 'SDL2_ttf',
+ 'SDL2_image',
+ 'SDL2_mixer',
+ )
+ found_libs = []
+ sofiles = []
+ if env and 'READELF' in env:
+ readelf = env['READELF']
+ elif 'READELF' in os.environ:
+ readelf = os.environ['READELF']
+ else:
+ readelf = sh.which('readelf').strip()
+ readelf = sh.Command(readelf).bake('-d')
+
+ dest = dirname(soname)
+
+ for directory in objs_paths:
+ for fn in os.listdir(directory):
+ fn = join(directory, fn)
+
+ if not fn.endswith('.libs'):
+ continue
+
+ dirfn = fn[:-1] + 'dirs'
+ if not exists(dirfn):
+ continue
+
+ with open(fn) as f:
+ libs = f.read().strip().split(' ')
+ needed_libs = [lib for lib in libs
+ if lib and
+ lib not in blacklist_libs and
+ lib not in found_libs]
+
+ while needed_libs:
+ print('need libs:\n\t' + '\n\t'.join(needed_libs))
+
+ start_needed_libs = needed_libs[:]
+ found_sofiles = []
+
+ with open(dirfn) as f:
+ libdirs = f.read().split()
+ for libdir in libdirs:
+ if not needed_libs:
+ break
+
+ if libdir == dest:
+ # don't need to copy from dest to dest!
+ continue
+
+ libdir = libdir.strip()
+ print('scanning', libdir)
+ for lib in needed_libs[:]:
+ if lib in found_libs:
+ continue
+
+ if lib.endswith('.a'):
+ needed_libs.remove(lib)
+ found_libs.append(lib)
+ continue
+
+ lib_a = 'lib' + lib + '.a'
+ libpath_a = join(libdir, lib_a)
+ lib_so = 'lib' + lib + '.so'
+ libpath_so = join(libdir, lib_so)
+ plain_so = lib + '.so'
+ plainpath_so = join(libdir, plain_so)
+
+ sopath = None
+ if exists(libpath_so):
+ sopath = libpath_so
+ elif exists(plainpath_so):
+ sopath = plainpath_so
+
+ if sopath:
+ print('found', lib, 'in', libdir)
+ found_sofiles.append(sopath)
+ needed_libs.remove(lib)
+ found_libs.append(lib)
+ continue
+
+ if exists(libpath_a):
+ print('found', lib, '(static) in', libdir)
+ needed_libs.remove(lib)
+ found_libs.append(lib)
+ continue
+
+ for sofile in found_sofiles:
+ print('scanning dependencies for', sofile)
+ out = readelf(sofile)
+ for line in out.splitlines():
+ needso = re_needso.match(line)
+ if needso:
+ lib = needso.group(1)
+ if (lib not in needed_libs
+ and lib not in found_libs
+ and lib not in blacklist_libs):
+ needed_libs.append(needso.group(1))
+
+ sofiles += found_sofiles
+
+ if needed_libs == start_needed_libs:
+ raise RuntimeError(
+ 'Failed to locate needed libraries!\n\t' +
+ '\n\t'.join(needed_libs))
+
+ print('Copying libraries')
+ for lib in sofiles:
+ shprint(sh.cp, lib, dest)
diff --git a/p4a/pythonforandroidold/distribution.py b/p4a/pythonforandroidold/distribution.py
new file mode 100644
index 0000000..9fa7b4c
--- /dev/null
+++ b/p4a/pythonforandroidold/distribution.py
@@ -0,0 +1,237 @@
+from os.path import exists, join
+import glob
+import json
+
+from pythonforandroid.logger import (info, info_notify, warning, Err_Style, Err_Fore)
+from pythonforandroid.util import current_directory, BuildInterruptingException
+from shutil import rmtree
+
+
+class Distribution(object):
+ '''State container for information about a distribution (i.e. an
+ Android project).
+
+ This is separate from a Bootstrap because the Bootstrap is
+ concerned with building and populating the dist directory, whereas
+ the dist itself could also come from e.g. a binary download.
+ '''
+ ctx = None
+
+ name = None # A name identifying the dist. May not be None.
+ needs_build = False # Whether the dist needs compiling
+ url = None
+ dist_dir = None # Where the dist dir ultimately is. Should not be None.
+ ndk_api = None
+
+ archs = []
+ '''The arch targets that the dist is built for.'''
+
+ recipes = []
+
+ description = '' # A long description
+
+ def __init__(self, ctx):
+ self.ctx = ctx
+
+ def __str__(self):
+ return ''.format(
+ # self.name, ', '.join([recipe.name for recipe in self.recipes]))
+ self.name, ', '.join(self.recipes))
+
+ def __repr__(self):
+ return str(self)
+
+ @classmethod
+ def get_distribution(cls, ctx, name=None, recipes=[],
+ ndk_api=None,
+ force_build=False,
+ extra_dist_dirs=[],
+ require_perfect_match=False,
+ allow_replace_dist=True):
+ '''Takes information about the distribution, and decides what kind of
+ distribution it will be.
+
+ If parameters conflict (e.g. a dist with that name already
+ exists, but doesn't have the right set of recipes),
+ an error is thrown.
+
+ Parameters
+ ----------
+ name : str
+ The name of the distribution. If a dist with this name already '
+ exists, it will be used.
+ recipes : list
+ The recipes that the distribution must contain.
+ force_download: bool
+ If True, only downloaded dists are considered.
+ force_build : bool
+ If True, the dist is forced to be built locally.
+ extra_dist_dirs : list
+ Any extra directories in which to search for dists.
+ require_perfect_match : bool
+ If True, will only match distributions with precisely the
+ correct set of recipes.
+ allow_replace_dist : bool
+ If True, will allow an existing dist with the specified
+ name but incompatible requirements to be overwritten by
+ a new one with the current requirements.
+ '''
+
+ existing_dists = Distribution.get_distributions(ctx)
+
+ possible_dists = existing_dists
+
+ name_match_dist = None
+
+ # 0) Check if a dist with that name already exists
+ if name is not None and name:
+ possible_dists = [d for d in possible_dists if d.name == name]
+ if possible_dists:
+ name_match_dist = possible_dists[0]
+
+ # 1) Check if any existing dists meet the requirements
+ _possible_dists = []
+ for dist in possible_dists:
+ if (
+ ndk_api is not None and dist.ndk_api != ndk_api
+ ) or dist.ndk_api is None:
+ continue
+ for recipe in recipes:
+ if recipe not in dist.recipes:
+ break
+ else:
+ _possible_dists.append(dist)
+ possible_dists = _possible_dists
+
+ if possible_dists:
+ info('Of the existing distributions, the following meet '
+ 'the given requirements:')
+ pretty_log_dists(possible_dists)
+ else:
+ info('No existing dists meet the given requirements!')
+
+ # If any dist has perfect recipes and ndk API, return it
+ for dist in possible_dists:
+ if force_build:
+ continue
+ if ndk_api is not None and dist.ndk_api != ndk_api:
+ continue
+ if (set(dist.recipes) == set(recipes) or
+ (set(recipes).issubset(set(dist.recipes)) and
+ not require_perfect_match)):
+ info_notify('{} has compatible recipes, using this one'
+ .format(dist.name))
+ return dist
+
+ assert len(possible_dists) < 2
+
+ # If there was a name match but we didn't already choose it,
+ # then the existing dist is incompatible with the requested
+ # configuration and the build cannot continue
+ if name_match_dist is not None and not allow_replace_dist:
+ raise BuildInterruptingException(
+ 'Asked for dist with name {name} with recipes ({req_recipes}) and '
+ 'NDK API {req_ndk_api}, but a dist '
+ 'with this name already exists and has either incompatible recipes '
+ '({dist_recipes}) or NDK API {dist_ndk_api}'.format(
+ name=name,
+ req_ndk_api=ndk_api,
+ dist_ndk_api=name_match_dist.ndk_api,
+ req_recipes=', '.join(recipes),
+ dist_recipes=', '.join(name_match_dist.recipes)))
+
+ # If we got this far, we need to build a new dist
+ dist = Distribution(ctx)
+ dist.needs_build = True
+
+ if not name:
+ filen = 'unnamed_dist_{}'
+ i = 1
+ while exists(join(ctx.dist_dir, filen.format(i))):
+ i += 1
+ name = filen.format(i)
+
+ dist.name = name
+ dist.dist_dir = join(ctx.dist_dir, dist.name)
+ dist.recipes = recipes
+ dist.ndk_api = ctx.ndk_api
+
+ return dist
+
+ def folder_exists(self):
+ return exists(self.dist_dir)
+
+ def delete(self):
+ rmtree(self.dist_dir)
+
+ @classmethod
+ def get_distributions(cls, ctx, extra_dist_dirs=[]):
+ '''Returns all the distributions found locally.'''
+ if extra_dist_dirs:
+ raise BuildInterruptingException(
+ 'extra_dist_dirs argument to get_distributions '
+ 'is not yet implemented')
+ dist_dir = ctx.dist_dir
+ folders = glob.glob(join(dist_dir, '*'))
+ for dir in extra_dist_dirs:
+ folders.extend(glob.glob(join(dir, '*')))
+
+ dists = []
+ for folder in folders:
+ if exists(join(folder, 'dist_info.json')):
+ with open(join(folder, 'dist_info.json')) as fileh:
+ dist_info = json.load(fileh)
+ dist = cls(ctx)
+ dist.name = folder.split('/')[-1]
+ dist.dist_dir = folder
+ dist.needs_build = False
+ dist.recipes = dist_info['recipes']
+ if 'archs' in dist_info:
+ dist.archs = dist_info['archs']
+ if 'ndk_api' in dist_info:
+ dist.ndk_api = dist_info['ndk_api']
+ else:
+ dist.ndk_api = None
+ warning(
+ "Distribution {distname}: ({distdir}) has been "
+ "built with an unknown api target, ignoring it, "
+ "you might want to delete it".format(
+ distname=dist.name,
+ distdir=dist.dist_dir
+ )
+ )
+ dists.append(dist)
+ return dists
+
+ def save_info(self, dirn):
+ '''
+ Save information about the distribution in its dist_dir.
+ '''
+ with current_directory(dirn):
+ info('Saving distribution info')
+ with open('dist_info.json', 'w') as fileh:
+ json.dump({'dist_name': self.ctx.dist_name,
+ 'bootstrap': self.ctx.bootstrap.name,
+ 'archs': [arch.arch for arch in self.ctx.archs],
+ 'ndk_api': self.ctx.ndk_api,
+ 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
+ 'hostpython': self.ctx.hostpython,
+ 'python_version': self.ctx.python_recipe.major_minor_version_string},
+ fileh)
+
+
+def pretty_log_dists(dists, log_func=info):
+ infos = []
+ for dist in dists:
+ ndk_api = 'unknown' if dist.ndk_api is None else dist.ndk_api
+ infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: min API {ndk_api}, '
+ 'includes recipes ({Fore.GREEN}{recipes}'
+ '{Style.RESET_ALL}), built for archs ({Fore.BLUE}'
+ '{archs}{Style.RESET_ALL})'.format(
+ ndk_api=ndk_api,
+ name=dist.name, recipes=', '.join(dist.recipes),
+ archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN',
+ Fore=Err_Fore, Style=Err_Style))
+
+ for line in infos:
+ log_func('\t' + line)
diff --git a/p4a/pythonforandroidold/graph.py b/p4a/pythonforandroidold/graph.py
new file mode 100644
index 0000000..646a66e
--- /dev/null
+++ b/p4a/pythonforandroidold/graph.py
@@ -0,0 +1,340 @@
+from copy import deepcopy
+from itertools import product
+
+from pythonforandroid.logger import info
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.util import BuildInterruptingException
+
+
+def fix_deplist(deps):
+ """ Turn a dependency list into lowercase, and make sure all entries
+ that are just a string become a tuple of strings
+ """
+ deps = [
+ ((dep.lower(),)
+ if not isinstance(dep, (list, tuple))
+ else tuple([dep_entry.lower()
+ for dep_entry in dep
+ ]))
+ for dep in deps
+ ]
+ return deps
+
+
+class RecipeOrder(dict):
+ def __init__(self, ctx):
+ self.ctx = ctx
+
+ def conflicts(self):
+ for name in self.keys():
+ try:
+ recipe = Recipe.get_recipe(name, self.ctx)
+ conflicts = [dep.lower() for dep in recipe.conflicts]
+ except ValueError:
+ conflicts = []
+
+ if any([c in self for c in conflicts]):
+ return True
+ return False
+
+
+def get_dependency_tuple_list_for_recipe(recipe, blacklist=None):
+ """ Get the dependencies of a recipe with filtered out blacklist, and
+ turned into tuples with fix_deplist()
+ """
+ if blacklist is None:
+ blacklist = set()
+ assert(type(blacklist) == set)
+ if recipe.depends is None:
+ dependencies = []
+ else:
+ # Turn all dependencies into tuples so that product will work
+ dependencies = fix_deplist(recipe.depends)
+
+ # Filter out blacklisted items and turn lowercase:
+ dependencies = [
+ tuple(set(deptuple) - blacklist)
+ for deptuple in dependencies
+ if tuple(set(deptuple) - blacklist)
+ ]
+ return dependencies
+
+
+def recursively_collect_orders(
+ name, ctx, all_inputs, orders=None, blacklist=None
+ ):
+ '''For each possible recipe ordering, try to add the new recipe name
+ to that order. Recursively do the same thing with all the
+ dependencies of each recipe.
+
+ '''
+ name = name.lower()
+ if orders is None:
+ orders = []
+ if blacklist is None:
+ blacklist = set()
+ try:
+ recipe = Recipe.get_recipe(name, ctx)
+ dependencies = get_dependency_tuple_list_for_recipe(
+ recipe, blacklist=blacklist
+ )
+
+ # handle opt_depends: these impose requirements on the build
+ # order only if already present in the list of recipes to build
+ dependencies.extend(fix_deplist(
+ [[d] for d in recipe.get_opt_depends_in_list(all_inputs)
+ if d.lower() not in blacklist]
+ ))
+
+ if recipe.conflicts is None:
+ conflicts = []
+ else:
+ conflicts = [dep.lower() for dep in recipe.conflicts]
+ except ValueError:
+ # The recipe does not exist, so we assume it can be installed
+ # via pip with no extra dependencies
+ dependencies = []
+ conflicts = []
+
+ new_orders = []
+ # for each existing recipe order, see if we can add the new recipe name
+ for order in orders:
+ if name in order:
+ new_orders.append(deepcopy(order))
+ continue
+ if order.conflicts():
+ continue
+ if any([conflict in order for conflict in conflicts]):
+ continue
+
+ for dependency_set in product(*dependencies):
+ new_order = deepcopy(order)
+ new_order[name] = set(dependency_set)
+
+ dependency_new_orders = [new_order]
+ for dependency in dependency_set:
+ dependency_new_orders = recursively_collect_orders(
+ dependency, ctx, all_inputs, dependency_new_orders,
+ blacklist=blacklist
+ )
+
+ new_orders.extend(dependency_new_orders)
+
+ return new_orders
+
+
+def find_order(graph):
+ '''
+ Do a topological sort on the dependency graph dict.
+ '''
+ while graph:
+ # Find all items without a parent
+ leftmost = [l for l, s in graph.items() if not s]
+ if not leftmost:
+ raise ValueError('Dependency cycle detected! %s' % graph)
+ # If there is more than one, sort them for predictable order
+ leftmost.sort()
+ for result in leftmost:
+ # Yield and remove them from the graph
+ yield result
+ graph.pop(result)
+ for bset in graph.values():
+ bset.discard(result)
+
+
+def obvious_conflict_checker(ctx, name_tuples, blacklist=None):
+ """ This is a pre-flight check function that will completely ignore
+ recipe order or choosing an actual value in any of the multiple
+ choice tuples/dependencies, and just do a very basic obvious
+ conflict check.
+ """
+ deps_were_added_by = dict()
+ deps = set()
+ if blacklist is None:
+ blacklist = set()
+
+ # Add dependencies for all recipes:
+ to_be_added = [(name_tuple, None) for name_tuple in name_tuples]
+ while len(to_be_added) > 0:
+ current_to_be_added = list(to_be_added)
+ to_be_added = []
+ for (added_tuple, adding_recipe) in current_to_be_added:
+ assert(type(added_tuple) == tuple)
+ if len(added_tuple) > 1:
+ # No obvious commitment in what to add, don't check it itself
+ # but throw it into deps for later comparing against
+ # (Remember this function only catches obvious issues)
+ deps.add(added_tuple)
+ continue
+
+ name = added_tuple[0]
+ recipe_conflicts = set()
+ recipe_dependencies = []
+ try:
+ # Get recipe to add and who's ultimately adding it:
+ recipe = Recipe.get_recipe(name, ctx)
+ recipe_conflicts = {c.lower() for c in recipe.conflicts}
+ recipe_dependencies = get_dependency_tuple_list_for_recipe(
+ recipe, blacklist=blacklist
+ )
+ except ValueError:
+ pass
+ adder_first_recipe_name = adding_recipe or name
+
+ # Collect the conflicts:
+ triggered_conflicts = []
+ for dep_tuple_list in deps:
+ # See if the new deps conflict with things added before:
+ if set(dep_tuple_list).intersection(
+ recipe_conflicts) == set(dep_tuple_list):
+ triggered_conflicts.append(dep_tuple_list)
+ continue
+
+ # See if what was added before conflicts with the new deps:
+ if len(dep_tuple_list) > 1:
+ # Not an obvious commitment to a specific recipe/dep
+ # to be added, so we won't check.
+ # (remember this function only catches obvious issues)
+ continue
+ try:
+ dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx)
+ except ValueError:
+ continue
+ conflicts = [c.lower() for c in dep_recipe.conflicts]
+ if name in conflicts:
+ triggered_conflicts.append(dep_tuple_list)
+
+ # Throw error on conflict:
+ if triggered_conflicts:
+ # Get first conflict and see who added that one:
+ adder_second_recipe_name = "'||'".join(triggered_conflicts[0])
+ second_recipe_original_adder = deps_were_added_by.get(
+ (adder_second_recipe_name,), None
+ )
+ if second_recipe_original_adder:
+ adder_second_recipe_name = second_recipe_original_adder
+
+ # Prompt error:
+ raise BuildInterruptingException(
+ "Conflict detected: '{}'"
+ " inducing dependencies {}, and '{}'"
+ " inducing conflicting dependencies {}".format(
+ adder_first_recipe_name,
+ (recipe.name,),
+ adder_second_recipe_name,
+ triggered_conflicts[0]
+ ))
+
+ # Actually add it to our list:
+ deps.add(added_tuple)
+ deps_were_added_by[added_tuple] = adding_recipe
+
+ # Schedule dependencies to be added
+ to_be_added += [
+ (dep, adder_first_recipe_name or name)
+ for dep in recipe_dependencies
+ if dep not in deps
+ ]
+ # If we came here, then there were no obvious conflicts.
+ return None
+
+
+def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):
+ # Get set of recipe/dependency names, clean up and add bootstrap deps:
+ names = set(names)
+ if bs is not None and bs.recipe_depends:
+ names = names.union(set(bs.recipe_depends))
+ names = fix_deplist([
+ ([name] if not isinstance(name, (list, tuple)) else name)
+ for name in names
+ ])
+ if blacklist is None:
+ blacklist = set()
+ blacklist = {bitem.lower() for bitem in blacklist}
+
+ # Remove all values that are in the blacklist:
+ names_before_blacklist = list(names)
+ names = []
+ for name in names_before_blacklist:
+ cleaned_up_tuple = tuple([
+ item for item in name if item not in blacklist
+ ])
+ if cleaned_up_tuple:
+ names.append(cleaned_up_tuple)
+
+ # Do check for obvious conflicts (that would trigger in any order, and
+ # without comitting to any specific choice in a multi-choice tuple of
+ # dependencies):
+ obvious_conflict_checker(ctx, names, blacklist=blacklist)
+ # If we get here, no obvious conflicts!
+
+ # get all possible order graphs, as names may include tuples/lists
+ # of alternative dependencies
+ possible_orders = []
+ for name_set in product(*names):
+ new_possible_orders = [RecipeOrder(ctx)]
+ for name in name_set:
+ new_possible_orders = recursively_collect_orders(
+ name, ctx, name_set, orders=new_possible_orders,
+ blacklist=blacklist
+ )
+ possible_orders.extend(new_possible_orders)
+
+ # turn each order graph into a linear list if possible
+ orders = []
+ for possible_order in possible_orders:
+ try:
+ order = find_order(possible_order)
+ except ValueError: # a circular dependency was found
+ info('Circular dependency found in graph {}, skipping it.'.format(
+ possible_order))
+ continue
+ orders.append(list(order))
+
+ # prefer python3 and SDL2 if available
+ orders.sort(key=lambda order: -('python3' in order) - ('sdl2' in order))
+
+ if not orders:
+ raise BuildInterruptingException(
+ 'Didn\'t find any valid dependency graphs. '
+ 'This means that some of your '
+ 'requirements pull in conflicting dependencies.')
+
+ # It would be better to check against possible orders other
+ # than the first one, but in practice clashes will be rare,
+ # and can be resolved by specifying more parameters
+ chosen_order = orders[0]
+ if len(orders) > 1:
+ info('Found multiple valid dependency orders:')
+ for order in orders:
+ info(' {}'.format(order))
+ info('Using the first of these: {}'.format(chosen_order))
+ else:
+ info('Found a single valid recipe set: {}'.format(chosen_order))
+
+ if bs is None:
+ bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)
+ if bs is None:
+ # Note: don't remove this without thought, causes infinite loop
+ raise BuildInterruptingException(
+ "Could not find any compatible bootstrap!"
+ )
+ recipes, python_modules, bs = get_recipe_order_and_bootstrap(
+ ctx, chosen_order, bs=bs, blacklist=blacklist
+ )
+ else:
+ # check if each requirement has a recipe
+ recipes = []
+ python_modules = []
+ for name in chosen_order:
+ try:
+ recipe = Recipe.get_recipe(name, ctx)
+ python_modules += recipe.python_depends
+ except ValueError:
+ python_modules.append(name)
+ else:
+ recipes.append(name)
+
+ python_modules = list(set(python_modules))
+ return recipes, python_modules, bs
diff --git a/p4a/pythonforandroidold/includes/arm64-v8a/machine/cpu-features.h b/p4a/pythonforandroidold/includes/arm64-v8a/machine/cpu-features.h
new file mode 100644
index 0000000..ca50906
--- /dev/null
+++ b/p4a/pythonforandroidold/includes/arm64-v8a/machine/cpu-features.h
@@ -0,0 +1,7 @@
+#ifndef _ARM64_CPU_FEATURES
+#define _ARM64_CPU_FEATURES
+
+#define __ARM_ARCH__ 8
+#define __ARM_HAVE_HALFWORD_MULTIPLY 1
+
+#endif // _ARM64_CPU_FEATURES
diff --git a/p4a/pythonforandroidold/logger.py b/p4a/pythonforandroidold/logger.py
new file mode 100644
index 0000000..b25b94c
--- /dev/null
+++ b/p4a/pythonforandroidold/logger.py
@@ -0,0 +1,244 @@
+import logging
+import os
+import re
+import sh
+from sys import stdout, stderr
+from math import log10
+from collections import defaultdict
+from colorama import Style as Colo_Style, Fore as Colo_Fore
+import six
+
+# This codecs change fixes a bug with log output, but crashes under python3
+if not six.PY3:
+ import codecs
+ stdout = codecs.getwriter('utf8')(stdout)
+ stderr = codecs.getwriter('utf8')(stderr)
+
+if six.PY2:
+ unistr = unicode # noqa F821
+else:
+ unistr = str
+
+# monkey patch to show full output
+sh.ErrorReturnCode.truncate_cap = 999999
+
+
+class LevelDifferentiatingFormatter(logging.Formatter):
+ def format(self, record):
+ if record.levelno > 30:
+ record.msg = '{}{}[ERROR]{}{}: '.format(
+ Err_Style.BRIGHT, Err_Fore.RED, Err_Fore.RESET,
+ Err_Style.RESET_ALL) + record.msg
+ elif record.levelno > 20:
+ record.msg = '{}{}[WARNING]{}{}: '.format(
+ Err_Style.BRIGHT, Err_Fore.RED, Err_Fore.RESET,
+ Err_Style.RESET_ALL) + record.msg
+ elif record.levelno > 10:
+ record.msg = '{}[INFO]{}: '.format(
+ Err_Style.BRIGHT, Err_Style.RESET_ALL) + record.msg
+ else:
+ record.msg = '{}{}[DEBUG]{}{}: '.format(
+ Err_Style.BRIGHT, Err_Fore.LIGHTBLACK_EX, Err_Fore.RESET,
+ Err_Style.RESET_ALL) + record.msg
+ return super(LevelDifferentiatingFormatter, self).format(record)
+
+
+logger = logging.getLogger('p4a')
+# Necessary as importlib reloads this,
+# which would add a second handler and reset the level
+if not hasattr(logger, 'touched'):
+ logger.setLevel(logging.INFO)
+ logger.touched = True
+ ch = logging.StreamHandler(stderr)
+ formatter = LevelDifferentiatingFormatter('%(message)s')
+ ch.setFormatter(formatter)
+ logger.addHandler(ch)
+info = logger.info
+debug = logger.debug
+warning = logger.warning
+error = logger.error
+
+
+class colorama_shim(object):
+
+ def __init__(self, real):
+ self._dict = defaultdict(str)
+ self._real = real
+ self._enabled = False
+
+ def __getattr__(self, key):
+ return getattr(self._real, key) if self._enabled else self._dict[key]
+
+ def enable(self, enable):
+ self._enabled = enable
+
+
+Out_Style = colorama_shim(Colo_Style)
+Out_Fore = colorama_shim(Colo_Fore)
+Err_Style = colorama_shim(Colo_Style)
+Err_Fore = colorama_shim(Colo_Fore)
+
+
+def setup_color(color):
+ enable_out = (False if color == 'never' else
+ True if color == 'always' else
+ stdout.isatty())
+ Out_Style.enable(enable_out)
+ Out_Fore.enable(enable_out)
+
+ enable_err = (False if color == 'never' else
+ True if color == 'always' else
+ stderr.isatty())
+ Err_Style.enable(enable_err)
+ Err_Fore.enable(enable_err)
+
+
+def info_main(*args):
+ logger.info(''.join([Err_Style.BRIGHT, Err_Fore.GREEN] + list(args) +
+ [Err_Style.RESET_ALL, Err_Fore.RESET]))
+
+
+def info_notify(s):
+ info('{}{}{}{}'.format(Err_Style.BRIGHT, Err_Fore.LIGHTBLUE_EX, s,
+ Err_Style.RESET_ALL))
+
+
+def shorten_string(string, max_width):
+ ''' make limited length string in form:
+ "the string is very lo...(and 15 more)"
+ '''
+ string_len = len(string)
+ if string_len <= max_width:
+ return string
+ visible = max_width - 16 - int(log10(string_len))
+ # expected suffix len "...(and XXXXX more)"
+ if not isinstance(string, unistr):
+ visstring = unistr(string[:visible], errors='ignore')
+ else:
+ visstring = string[:visible]
+ return u''.join((visstring, u'...(and ',
+ unistr(string_len - visible), u' more)'))
+
+
+def get_console_width():
+ try:
+ cols = int(os.environ['COLUMNS'])
+ except (KeyError, ValueError):
+ pass
+ else:
+ if cols >= 25:
+ return cols
+
+ try:
+ cols = max(25, int(os.popen('stty size', 'r').read().split()[1]))
+ except Exception:
+ pass
+ else:
+ return cols
+
+ return 100
+
+
+def shprint(command, *args, **kwargs):
+ '''Runs the command (which should be an sh.Command instance), while
+ logging the output.'''
+ kwargs["_iter"] = True
+ kwargs["_out_bufsize"] = 1
+ kwargs["_err_to_out"] = True
+ kwargs["_bg"] = True
+ is_critical = kwargs.pop('_critical', False)
+ tail_n = kwargs.pop('_tail', None)
+ full_debug = False
+ if "P4A_FULL_DEBUG" in os.environ:
+ tail_n = 0
+ full_debug = True
+ filter_in = kwargs.pop('_filter', None)
+ filter_out = kwargs.pop('_filterout', None)
+ if len(logger.handlers) > 1:
+ logger.removeHandler(logger.handlers[1])
+ columns = get_console_width()
+ command_path = str(command).split('/')
+ command_string = command_path[-1]
+
+ string = ' '.join(['{}->{} running'.format(Out_Fore.LIGHTBLACK_EX,
+ Out_Style.RESET_ALL),
+ command_string] + list(args))
+
+ # If logging is not in DEBUG mode, trim the command if necessary
+ if logger.level > logging.DEBUG:
+ logger.info('{}{}'.format(shorten_string(string, columns - 12),
+ Err_Style.RESET_ALL))
+ else:
+ logger.debug('{}{}'.format(string, Err_Style.RESET_ALL))
+
+ need_closing_newline = False
+ try:
+ msg_hdr = ' working: '
+ msg_width = columns - len(msg_hdr) - 1
+ output = command(*args, **kwargs)
+ for line in output:
+ if isinstance(line, bytes):
+ line = line.decode('utf-8', errors='replace')
+ if logger.level > logging.DEBUG:
+ if full_debug:
+ stdout.write(line)
+ stdout.flush()
+ continue
+ msg = line.replace(
+ '\n', ' ').replace(
+ '\t', ' ').replace(
+ '\b', ' ').rstrip()
+ if msg:
+ if "CI" not in os.environ:
+ stdout.write(u'{}\r{}{:<{width}}'.format(
+ Err_Style.RESET_ALL, msg_hdr,
+ shorten_string(msg, msg_width), width=msg_width))
+ stdout.flush()
+ need_closing_newline = True
+ else:
+ logger.debug(''.join(['\t', line.rstrip()]))
+ if need_closing_newline:
+ stdout.write('{}\r{:>{width}}\r'.format(
+ Err_Style.RESET_ALL, ' ', width=(columns - 1)))
+ stdout.flush()
+ except sh.ErrorReturnCode as err:
+ if need_closing_newline:
+ stdout.write('{}\r{:>{width}}\r'.format(
+ Err_Style.RESET_ALL, ' ', width=(columns - 1)))
+ stdout.flush()
+ if tail_n is not None or filter_in or filter_out:
+ def printtail(out, name, forecolor, tail_n=0,
+ re_filter_in=None, re_filter_out=None):
+ lines = out.splitlines()
+ if re_filter_in is not None:
+ lines = [l for l in lines if re_filter_in.search(l)]
+ if re_filter_out is not None:
+ lines = [l for l in lines if not re_filter_out.search(l)]
+ if tail_n == 0 or len(lines) <= tail_n:
+ info('{}:\n{}\t{}{}'.format(
+ name, forecolor, '\t\n'.join(lines), Out_Fore.RESET))
+ else:
+ info('{} (last {} lines of {}):\n{}\t{}{}'.format(
+ name, tail_n, len(lines),
+ forecolor, '\t\n'.join([s for s in lines[-tail_n:]]),
+ Out_Fore.RESET))
+ printtail(err.stdout.decode('utf-8'), 'STDOUT', Out_Fore.YELLOW, tail_n,
+ re.compile(filter_in) if filter_in else None,
+ re.compile(filter_out) if filter_out else None)
+ printtail(err.stderr.decode('utf-8'), 'STDERR', Err_Fore.RED)
+ if is_critical:
+ env = kwargs.get("env")
+ if env is not None:
+ info("{}ENV:{}\n{}\n".format(
+ Err_Fore.YELLOW, Err_Fore.RESET, "\n".join(
+ "set {}={}".format(n, v) for n, v in env.items())))
+ info("{}COMMAND:{}\ncd {} && {} {}\n".format(
+ Err_Fore.YELLOW, Err_Fore.RESET, os.getcwd(), command,
+ ' '.join(args)))
+ warning("{}ERROR: {} failed!{}".format(
+ Err_Fore.RED, command, Err_Fore.RESET))
+ exit(1)
+ else:
+ raise
+
+ return output
diff --git a/p4a/pythonforandroidold/patching.py b/p4a/pythonforandroidold/patching.py
new file mode 100644
index 0000000..2a47733
--- /dev/null
+++ b/p4a/pythonforandroidold/patching.py
@@ -0,0 +1,71 @@
+from os import uname
+
+
+def check_all(*callables):
+ def check(**kwargs):
+ return all(c(**kwargs) for c in callables)
+ return check
+
+
+def check_any(*callables):
+ def check(**kwargs):
+ return any(c(**kwargs) for c in callables)
+ return check
+
+
+def is_platform(platform):
+ def is_x(**kwargs):
+ return uname()[0] == platform
+ return is_x
+
+
+is_linux = is_platform('Linux')
+is_darwin = is_platform('Darwin')
+
+
+def is_arch(xarch):
+ def is_x(arch, **kwargs):
+ return arch.arch == xarch
+ return is_x
+
+
+def is_api_gt(apiver):
+ def is_x(recipe, **kwargs):
+ return recipe.ctx.android_api > apiver
+ return is_x
+
+
+def is_api_gte(apiver):
+ def is_x(recipe, **kwargs):
+ return recipe.ctx.android_api >= apiver
+ return is_x
+
+
+def is_api_lt(apiver):
+ def is_x(recipe, **kwargs):
+ return recipe.ctx.android_api < apiver
+ return is_x
+
+
+def is_api_lte(apiver):
+ def is_x(recipe, **kwargs):
+ return recipe.ctx.android_api <= apiver
+ return is_x
+
+
+def is_api(apiver):
+ def is_x(recipe, **kwargs):
+ return recipe.ctx.android_api == apiver
+ return is_x
+
+
+def will_build(recipe_name):
+ def will(recipe, **kwargs):
+ return recipe_name in recipe.ctx.recipe_build_order
+ return will
+
+
+def is_ndk(ndk):
+ def is_x(recipe, **kwargs):
+ return recipe.ctx.ndk == ndk
+ return is_x
diff --git a/p4a/pythonforandroid/python.py b/p4a/pythonforandroidold/python.py
similarity index 100%
rename from p4a/pythonforandroid/python.py
rename to p4a/pythonforandroidold/python.py
diff --git a/p4a/pythonforandroidold/recipe.py b/p4a/pythonforandroidold/recipe.py
new file mode 100644
index 0000000..071aa22
--- /dev/null
+++ b/p4a/pythonforandroidold/recipe.py
@@ -0,0 +1,1169 @@
+from os.path import basename, dirname, exists, isdir, isfile, join, realpath, split
+import importlib
+import glob
+from shutil import rmtree
+from six import PY2, with_metaclass
+
+import hashlib
+from re import match
+
+import sh
+import shutil
+import fnmatch
+from os import listdir, unlink, environ, mkdir, curdir, walk
+from sys import stdout
+import time
+try:
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import urlparse
+from pythonforandroid.logger import (logger, info, warning, debug, shprint, info_main)
+from pythonforandroid.util import (urlretrieve, current_directory, ensure_dir,
+ BuildInterruptingException)
+
+# this import is necessary to keep imp.load_source from complaining :)
+if PY2:
+ import imp
+ import_recipe = imp.load_source
+else:
+ import importlib.util
+ if hasattr(importlib.util, 'module_from_spec'):
+ def import_recipe(module, filename):
+ spec = importlib.util.spec_from_file_location(module, filename)
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod)
+ return mod
+ else:
+ from importlib.machinery import SourceFileLoader
+
+ def import_recipe(module, filename):
+ return SourceFileLoader(module, filename).load_module()
+
+
+class RecipeMeta(type):
+ def __new__(cls, name, bases, dct):
+ if name != 'Recipe':
+ if 'url' in dct:
+ dct['_url'] = dct.pop('url')
+ if 'version' in dct:
+ dct['_version'] = dct.pop('version')
+
+ return super(RecipeMeta, cls).__new__(cls, name, bases, dct)
+
+
+class Recipe(with_metaclass(RecipeMeta)):
+ _url = None
+ '''The address from which the recipe may be downloaded. This is not
+ essential, it may be omitted if the source is available some other
+ way, such as via the :class:`IncludedFilesBehaviour` mixin.
+
+ If the url includes the version, you may (and probably should)
+ replace this with ``{version}``, which will automatically be
+ replaced by the :attr:`version` string during download.
+
+ .. note:: Methods marked (internal) are used internally and you
+ probably don't need to call them, but they are available
+ if you want.
+ '''
+
+ _version = None
+ '''A string giving the version of the software the recipe describes,
+ e.g. ``2.0.3`` or ``master``.'''
+
+ md5sum = None
+ '''The md5sum of the source from the :attr:`url`. Non-essential, but
+ you should try to include this, it is used to check that the download
+ finished correctly.
+ '''
+
+ depends = []
+ '''A list containing the names of any recipes that this recipe depends on.
+ '''
+
+ conflicts = []
+ '''A list containing the names of any recipes that are known to be
+ incompatible with this one.'''
+
+ opt_depends = []
+ '''A list of optional dependencies, that must be built before this
+ recipe if they are built at all, but whose presence is not essential.'''
+
+ patches = []
+ '''A list of patches to apply to the source. Values can be either a string
+ referring to the patch file relative to the recipe dir, or a tuple of the
+ string patch file and a callable, which will receive the kwargs `arch` and
+ `recipe`, which should return True if the patch should be applied.'''
+
+ python_depends = []
+ '''A list of pure-Python packages that this package requires. These
+ packages will NOT be available at build time, but will be added to the
+ list of pure-Python packages to install via pip. If you need these packages
+ at build time, you must create a recipe.'''
+
+ archs = ['armeabi'] # Not currently implemented properly
+
+ @property
+ def version(self):
+ key = 'VERSION_' + self.name
+ return environ.get(key, self._version)
+
+ @property
+ def url(self):
+ key = 'URL_' + self.name
+ return environ.get(key, self._url)
+
+ @property
+ def versioned_url(self):
+ '''A property returning the url of the recipe with ``{version}``
+ replaced by the :attr:`url`. If accessing the url, you should use this
+ property, *not* access the url directly.'''
+ if self.url is None:
+ return None
+ return self.url.format(version=self.version)
+
+ def download_file(self, url, target, cwd=None):
+ """
+ (internal) Download an ``url`` to a ``target``.
+ """
+ if not url:
+ return
+ info('Downloading {} from {}'.format(self.name, url))
+
+ if cwd:
+ target = join(cwd, target)
+
+ parsed_url = urlparse(url)
+ if parsed_url.scheme in ('http', 'https'):
+ def report_hook(index, blksize, size):
+ if size <= 0:
+ progression = '{0} bytes'.format(index * blksize)
+ else:
+ progression = '{0:.2f}%'.format(
+ index * blksize * 100. / float(size))
+ if "CI" not in environ:
+ stdout.write('- Download {}\r'.format(progression))
+ stdout.flush()
+
+ if exists(target):
+ unlink(target)
+
+ # Download item with multiple attempts (for bad connections):
+ attempts = 0
+ while True:
+ try:
+ urlretrieve(url, target, report_hook)
+ except OSError as e:
+ attempts += 1
+ if attempts >= 5:
+ raise e
+ stdout.write('Download failed retrying in a second...')
+ time.sleep(1)
+ continue
+ break
+ return target
+ elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):
+ if isdir(target):
+ with current_directory(target):
+ shprint(sh.git, 'fetch', '--tags')
+ if self.version:
+ shprint(sh.git, 'checkout', self.version)
+ shprint(sh.git, 'pull')
+ shprint(sh.git, 'pull', '--recurse-submodules')
+ shprint(sh.git, 'submodule', 'update', '--recursive')
+ else:
+ if url.startswith('git+'):
+ url = url[4:]
+ shprint(sh.git, 'clone', '--recursive', url, target)
+ if self.version:
+ with current_directory(target):
+ shprint(sh.git, 'checkout', self.version)
+ shprint(sh.git, 'submodule', 'update', '--recursive')
+ return target
+
+ def apply_patch(self, filename, arch, build_dir=None):
+ """
+ Apply a patch from the current recipe directory into the current
+ build directory.
+
+ .. versionchanged:: 0.6.0
+ Add ability to apply patch from any dir via kwarg `build_dir`'''
+ """
+ info("Applying patch {}".format(filename))
+ build_dir = build_dir if build_dir else self.get_build_dir(arch)
+ filename = join(self.get_recipe_dir(), filename)
+ shprint(sh.patch, "-t", "-d", build_dir, "-p1",
+ "-i", filename, _tail=10)
+
+ def copy_file(self, filename, dest):
+ info("Copy {} to {}".format(filename, dest))
+ filename = join(self.get_recipe_dir(), filename)
+ dest = join(self.build_dir, dest)
+ shutil.copy(filename, dest)
+
+ def append_file(self, filename, dest):
+ info("Append {} to {}".format(filename, dest))
+ filename = join(self.get_recipe_dir(), filename)
+ dest = join(self.build_dir, dest)
+ with open(filename, "rb") as fd:
+ data = fd.read()
+ with open(dest, "ab") as fd:
+ fd.write(data)
+
+ @property
+ def name(self):
+ '''The name of the recipe, the same as the folder containing it.'''
+ modname = self.__class__.__module__
+ return modname.split(".", 2)[-1]
+
+ @property
+ def filtered_archs(self):
+ '''Return archs of self.ctx that are valid build archs
+ for the Recipe.'''
+ result = []
+ for arch in self.ctx.archs:
+ if not self.archs or (arch.arch in self.archs):
+ result.append(arch)
+ return result
+
+ def check_recipe_choices(self):
+ '''Checks what recipes are being built to see which of the alternative
+ and optional dependencies are being used,
+ and returns a list of these.'''
+ recipes = []
+ built_recipes = self.ctx.recipe_build_order
+ for recipe in self.depends:
+ if isinstance(recipe, (tuple, list)):
+ for alternative in recipe:
+ if alternative in built_recipes:
+ recipes.append(alternative)
+ break
+ for recipe in self.opt_depends:
+ if recipe in built_recipes:
+ recipes.append(recipe)
+ return sorted(recipes)
+
+ def get_opt_depends_in_list(self, recipes):
+ '''Given a list of recipe names, returns those that are also in
+ self.opt_depends.
+ '''
+ return [recipe for recipe in recipes if recipe in self.opt_depends]
+
+ def get_build_container_dir(self, arch):
+ '''Given the arch name, returns the directory where it will be
+ built.
+
+ This returns a different directory depending on what
+ alternative or optional dependencies are being built.
+ '''
+ dir_name = self.get_dir_name()
+ return join(self.ctx.build_dir, 'other_builds',
+ dir_name, '{}__ndk_target_{}'.format(arch, self.ctx.ndk_api))
+
+ def get_dir_name(self):
+ choices = self.check_recipe_choices()
+ dir_name = '-'.join([self.name] + choices)
+ return dir_name
+
+ def get_build_dir(self, arch):
+ '''Given the arch name, returns the directory where the
+ downloaded/copied package will be built.'''
+
+ return join(self.get_build_container_dir(arch), self.name)
+
+ def get_recipe_dir(self):
+ """
+ Returns the local recipe directory or defaults to the core recipe
+ directory.
+ """
+ if self.ctx.local_recipes is not None:
+ local_recipe_dir = join(self.ctx.local_recipes, self.name)
+ if exists(local_recipe_dir):
+ return local_recipe_dir
+ return join(self.ctx.root_dir, 'recipes', self.name)
+
+ # Public Recipe API to be subclassed if needed
+
+ def download_if_necessary(self):
+ info_main('Downloading {}'.format(self.name))
+ user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower()))
+ if user_dir is not None:
+ info('P4A_{}_DIR is set, skipping download for {}'.format(
+ self.name, self.name))
+ return
+ self.download()
+
+ def download(self):
+ if self.url is None:
+ info('Skipping {} download as no URL is set'.format(self.name))
+ return
+
+ url = self.versioned_url
+ ma = match(u'^(.+)#md5=([0-9a-f]{32})$', url)
+ if ma: # fragmented URL?
+ if self.md5sum:
+ raise ValueError(
+ ('Received md5sum from both the {} recipe '
+ 'and its url').format(self.name))
+ url = ma.group(1)
+ expected_md5 = ma.group(2)
+ else:
+ expected_md5 = self.md5sum
+
+ shprint(sh.mkdir, '-p', join(self.ctx.packages_path, self.name))
+
+ with current_directory(join(self.ctx.packages_path, self.name)):
+ filename = shprint(sh.basename, url).stdout[:-1].decode('utf-8')
+
+ do_download = True
+ marker_filename = '.mark-{}'.format(filename)
+ if exists(filename) and isfile(filename):
+ if not exists(marker_filename):
+ shprint(sh.rm, filename)
+ elif expected_md5:
+ current_md5 = md5sum(filename)
+ if current_md5 != expected_md5:
+ debug('* Generated md5sum: {}'.format(current_md5))
+ debug('* Expected md5sum: {}'.format(expected_md5))
+ raise ValueError(
+ ('Generated md5sum does not match expected md5sum '
+ 'for {} recipe').format(self.name))
+ do_download = False
+ else:
+ do_download = False
+
+ # If we got this far, we will download
+ if do_download:
+ debug('Downloading {} from {}'.format(self.name, url))
+
+ shprint(sh.rm, '-f', marker_filename)
+ self.download_file(self.versioned_url, filename)
+ shprint(sh.touch, marker_filename)
+
+ if exists(filename) and isfile(filename) and expected_md5:
+ current_md5 = md5sum(filename)
+ if expected_md5 is not None:
+ if current_md5 != expected_md5:
+ debug('* Generated md5sum: {}'.format(current_md5))
+ debug('* Expected md5sum: {}'.format(expected_md5))
+ raise ValueError(
+ ('Generated md5sum does not match expected md5sum '
+ 'for {} recipe').format(self.name))
+ else:
+ info('{} download already cached, skipping'.format(self.name))
+
+ def unpack(self, arch):
+ info_main('Unpacking {} for {}'.format(self.name, arch))
+
+ build_dir = self.get_build_container_dir(arch)
+
+ user_dir = environ.get('P4A_{}_DIR'.format(self.name.lower()))
+ if user_dir is not None:
+ info('P4A_{}_DIR exists, symlinking instead'.format(
+ self.name.lower()))
+ if exists(self.get_build_dir(arch)):
+ return
+ shprint(sh.rm, '-rf', build_dir)
+ shprint(sh.mkdir, '-p', build_dir)
+ shprint(sh.rmdir, build_dir)
+ ensure_dir(build_dir)
+ shprint(sh.cp, '-a', user_dir, self.get_build_dir(arch))
+ return
+
+ if self.url is None:
+ info('Skipping {} unpack as no URL is set'.format(self.name))
+ return
+
+ filename = shprint(
+ sh.basename, self.versioned_url).stdout[:-1].decode('utf-8')
+ ma = match(u'^(.+)#md5=([0-9a-f]{32})$', filename)
+ if ma: # fragmented URL?
+ filename = ma.group(1)
+
+ with current_directory(build_dir):
+ directory_name = self.get_build_dir(arch)
+
+ if not exists(directory_name) or not isdir(directory_name):
+ extraction_filename = join(
+ self.ctx.packages_path, self.name, filename)
+ if isfile(extraction_filename):
+ if extraction_filename.endswith('.zip'):
+ try:
+ sh.unzip(extraction_filename)
+ except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2):
+ # return code 1 means unzipping had
+ # warnings but did complete,
+ # apparently happens sometimes with
+ # github zips
+ pass
+ import zipfile
+ fileh = zipfile.ZipFile(extraction_filename, 'r')
+ root_directory = fileh.filelist[0].filename.split('/')[0]
+ if root_directory != basename(directory_name):
+ shprint(sh.mv, root_directory, directory_name)
+ elif extraction_filename.endswith(
+ ('.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz')):
+ sh.tar('xf', extraction_filename)
+ root_directory = sh.tar('tf', extraction_filename).stdout.decode(
+ 'utf-8').split('\n')[0].split('/')[0]
+ if root_directory != directory_name:
+ shprint(sh.mv, root_directory, directory_name)
+ else:
+ raise Exception(
+ 'Could not extract {} download, it must be .zip, '
+ '.tar.gz or .tar.bz2 or .tar.xz'.format(extraction_filename))
+ elif isdir(extraction_filename):
+ mkdir(directory_name)
+ for entry in listdir(extraction_filename):
+ if entry not in ('.git',):
+ shprint(sh.cp, '-Rv',
+ join(extraction_filename, entry),
+ directory_name)
+ else:
+ raise Exception(
+ 'Given path is neither a file nor a directory: {}'
+ .format(extraction_filename))
+
+ else:
+ info('{} is already unpacked, skipping'.format(self.name))
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True, clang=False):
+ """Return the env specialized for the recipe
+ """
+ if arch is None:
+ arch = self.filtered_archs[0]
+ return arch.get_env(with_flags_in_cc=with_flags_in_cc, clang=clang)
+
+ def prebuild_arch(self, arch):
+ '''Run any pre-build tasks for the Recipe. By default, this checks if
+ any prebuild_archname methods exist for the archname of the current
+ architecture, and runs them if so.'''
+ prebuild = "prebuild_{}".format(arch.arch.replace('-', '_'))
+ if hasattr(self, prebuild):
+ getattr(self, prebuild)()
+ else:
+ info('{} has no {}, skipping'.format(self.name, prebuild))
+
+ def is_patched(self, arch):
+ build_dir = self.get_build_dir(arch.arch)
+ return exists(join(build_dir, '.patched'))
+
+ def apply_patches(self, arch, build_dir=None):
+ '''Apply any patches for the Recipe.
+
+ .. versionchanged:: 0.6.0
+ Add ability to apply patches from any dir via kwarg `build_dir`'''
+ if self.patches:
+ info_main('Applying patches for {}[{}]'
+ .format(self.name, arch.arch))
+
+ if self.is_patched(arch):
+ info_main('{} already patched, skipping'.format(self.name))
+ return
+
+ build_dir = build_dir if build_dir else self.get_build_dir(arch.arch)
+ for patch in self.patches:
+ if isinstance(patch, (tuple, list)):
+ patch, patch_check = patch
+ if not patch_check(arch=arch, recipe=self):
+ continue
+
+ self.apply_patch(
+ patch.format(version=self.version, arch=arch.arch),
+ arch.arch, build_dir=build_dir)
+
+ shprint(sh.touch, join(build_dir, '.patched'))
+
+ def should_build(self, arch):
+ '''Should perform any necessary test and return True only if it needs
+ building again.
+
+ '''
+ return True
+
+ def build_arch(self, arch):
+ '''Run any build tasks for the Recipe. By default, this checks if
+ any build_archname methods exist for the archname of the current
+ architecture, and runs them if so.'''
+ build = "build_{}".format(arch.arch)
+ if hasattr(self, build):
+ getattr(self, build)()
+
+ def postbuild_arch(self, arch):
+ '''Run any post-build tasks for the Recipe. By default, this checks if
+ any postbuild_archname methods exist for the archname of the
+ current architecture, and runs them if so.
+ '''
+ postbuild = "postbuild_{}".format(arch.arch)
+ if hasattr(self, postbuild):
+ getattr(self, postbuild)()
+
+ def prepare_build_dir(self, arch):
+ '''Copies the recipe data into a build dir for the given arch. By
+ default, this unpacks a downloaded recipe. You should override
+ it (or use a Recipe subclass with different behaviour) if you
+ want to do something else.
+ '''
+ self.unpack(arch)
+
+ def clean_build(self, arch=None):
+ '''Deletes all the build information of the recipe.
+
+ If arch is not None, only this arch dir is deleted. Otherwise
+ (the default) all builds for all archs are deleted.
+
+ By default, this just deletes the main build dir. If the
+ recipe has e.g. object files biglinked, or .so files stored
+ elsewhere, you should override this method.
+
+ This method is intended for testing purposes, it may have
+ strange results. Rebuild everything if this seems to happen.
+
+ '''
+ if arch is None:
+ base_dir = join(self.ctx.build_dir, 'other_builds', self.name)
+ else:
+ base_dir = self.get_build_container_dir(arch)
+ dirs = glob.glob(base_dir + '-*')
+ if exists(base_dir):
+ dirs.append(base_dir)
+ if not dirs:
+ warning('Attempted to clean build for {} but found no existing '
+ 'build dirs'.format(self.name))
+
+ for directory in dirs:
+ if exists(directory):
+ info('Deleting {}'.format(directory))
+ shutil.rmtree(directory)
+
+ # Delete any Python distributions to ensure the recipe build
+ # doesn't persist in site-packages
+ shutil.rmtree(self.ctx.python_installs_dir)
+
+ def install_libs(self, arch, *libs):
+ libs_dir = self.ctx.get_libs_dir(arch.arch)
+ if not libs:
+ warning('install_libs called with no libraries to install!')
+ return
+ args = libs + (libs_dir,)
+ shprint(sh.cp, *args)
+
+ def has_libs(self, arch, *libs):
+ return all(map(lambda l: self.ctx.has_lib(arch.arch, l), libs))
+
+ @classmethod
+ def recipe_dirs(cls, ctx):
+ recipe_dirs = []
+ if ctx.local_recipes is not None:
+ recipe_dirs.append(realpath(ctx.local_recipes))
+ if ctx.storage_dir:
+ recipe_dirs.append(join(ctx.storage_dir, 'recipes'))
+ recipe_dirs.append(join(ctx.root_dir, "recipes"))
+ return recipe_dirs
+
+ @classmethod
+ def list_recipes(cls, ctx):
+ forbidden_dirs = ('__pycache__', )
+ for recipes_dir in cls.recipe_dirs(ctx):
+ if recipes_dir and exists(recipes_dir):
+ for name in listdir(recipes_dir):
+ if name in forbidden_dirs:
+ continue
+ fn = join(recipes_dir, name)
+ if isdir(fn):
+ yield name
+
+ @classmethod
+ def get_recipe(cls, name, ctx):
+ '''Returns the Recipe with the given name, if it exists.'''
+ name = name.lower()
+ if not hasattr(cls, "recipes"):
+ cls.recipes = {}
+ if name in cls.recipes:
+ return cls.recipes[name]
+
+ recipe_file = None
+ for recipes_dir in cls.recipe_dirs(ctx):
+ if not exists(recipes_dir):
+ continue
+ # Find matching folder (may differ in case):
+ for subfolder in listdir(recipes_dir):
+ if subfolder.lower() == name:
+ recipe_file = join(recipes_dir, subfolder, '__init__.py')
+ if exists(recipe_file):
+ name = subfolder # adapt to actual spelling
+ break
+ recipe_file = None
+ if recipe_file is not None:
+ break
+
+ if not recipe_file:
+ raise ValueError('Recipe does not exist: {}'.format(name))
+
+ mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)
+ if len(logger.handlers) > 1:
+ logger.removeHandler(logger.handlers[1])
+ recipe = mod.recipe
+ recipe.ctx = ctx
+ cls.recipes[name.lower()] = recipe
+ return recipe
+
+
+class IncludedFilesBehaviour(object):
+ '''Recipe mixin class that will automatically unpack files included in
+ the recipe directory.'''
+ src_filename = None
+
+ def prepare_build_dir(self, arch):
+ if self.src_filename is None:
+ raise BuildInterruptingException(
+ 'IncludedFilesBehaviour failed: no src_filename specified')
+ shprint(sh.rm, '-rf', self.get_build_dir(arch))
+ shprint(sh.cp, '-a', join(self.get_recipe_dir(), self.src_filename),
+ self.get_build_dir(arch))
+
+
+class BootstrapNDKRecipe(Recipe):
+ '''A recipe class for recipes built in an Android project jni dir with
+ an Android.mk. These are not cached separatly, but built in the
+ bootstrap's own building directory.
+
+ To build an NDK project which is not part of the bootstrap, see
+ :class:`~pythonforandroid.recipe.NDKRecipe`.
+
+ To link with python, call the method :meth:`get_recipe_env`
+ with the kwarg *with_python=True*.
+ '''
+
+ dir_name = None # The name of the recipe build folder in the jni dir
+
+ def get_build_container_dir(self, arch):
+ return self.get_jni_dir()
+
+ def get_build_dir(self, arch):
+ if self.dir_name is None:
+ raise ValueError('{} recipe doesn\'t define a dir_name, but '
+ 'this is necessary'.format(self.name))
+ return join(self.get_build_container_dir(arch), self.dir_name)
+
+ def get_jni_dir(self):
+ return join(self.ctx.bootstrap.build_dir, 'jni')
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=False):
+ env = super(BootstrapNDKRecipe, self).get_recipe_env(
+ arch, with_flags_in_cc)
+ if not with_python:
+ return env
+
+ env['PYTHON_INCLUDE_ROOT'] = self.ctx.python_recipe.include_root(arch.arch)
+ env['PYTHON_LINK_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
+ env['EXTRA_LDLIBS'] = ' -lpython{}'.format(
+ self.ctx.python_recipe.major_minor_version_string)
+ #if 'python3' in self.ctx.python_recipe.name:
+ # env['EXTRA_LDLIBS'] += 'm'
+ return env
+
+
+class NDKRecipe(Recipe):
+ '''A recipe class for any NDK project not included in the bootstrap.'''
+
+ generated_libraries = []
+
+ def should_build(self, arch):
+ lib_dir = self.get_lib_dir(arch)
+
+ for lib in self.generated_libraries:
+ if not exists(join(lib_dir, lib)):
+ return True
+
+ return False
+
+ def get_lib_dir(self, arch):
+ return join(self.get_build_dir(arch.arch), 'obj', 'local', arch.arch)
+
+ def get_jni_dir(self, arch):
+ return join(self.get_build_dir(arch.arch), 'jni')
+
+ def build_arch(self, arch, *extra_args):
+ super(NDKRecipe, self).build_arch(arch)
+
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ shprint(
+ sh.ndk_build,
+ 'V=1',
+ 'APP_PLATFORM=android-' + str(self.ctx.ndk_api),
+ 'APP_ABI=' + arch.arch,
+ *extra_args, _env=env
+ )
+
+
+class PythonRecipe(Recipe):
+ site_packages_name = None
+ '''The name of the module's folder when installed in the Python
+ site-packages (e.g. for pyjnius it is 'jnius')'''
+
+ call_hostpython_via_targetpython = True
+ '''If True, tries to install the module using the hostpython binary
+ copied to the target (normally arm) python build dir. However, this
+ will fail if the module tries to import e.g. _io.so. Set this to False
+ to call hostpython from its own build dir, installing the module in
+ the right place via arguments to setup.py. However, this may not set
+ the environment correctly and so False is not the default.'''
+
+ install_in_hostpython = False
+ '''If True, additionally installs the module in the hostpython build
+ dir. This will make it available to other recipes if
+ call_hostpython_via_targetpython is False.
+ '''
+
+ install_in_targetpython = True
+ '''If True, installs the module in the targetpython installation dir.
+ This is almost always what you want to do.'''
+
+ setup_extra_args = []
+ '''List of extra arugments to pass to setup.py'''
+
+ def __init__(self, *args, **kwargs):
+ super(PythonRecipe, self).__init__(*args, **kwargs)
+ depends = self.depends
+ depends.append(('python2', 'python2legacy', 'python3', 'python3crystax'))
+ depends = list(set(depends))
+ self.depends = depends
+
+ def clean_build(self, arch=None):
+ super(PythonRecipe, self).clean_build(arch=arch)
+ name = self.folder_name
+ python_install_dirs = glob.glob(join(self.ctx.python_installs_dir, '*'))
+ for python_install in python_install_dirs:
+ site_packages_dir = glob.glob(join(python_install, 'lib', 'python*',
+ 'site-packages'))
+ if site_packages_dir:
+ build_dir = join(site_packages_dir[0], name)
+ if exists(build_dir):
+ info('Deleted {}'.format(build_dir))
+ rmtree(build_dir)
+
+ @property
+ def real_hostpython_location(self):
+ host_name = 'host{}'.format(self.ctx.python_recipe.name)
+ host_build = Recipe.get_recipe(host_name, self.ctx).get_build_dir()
+ if host_name in ['hostpython2', 'hostpython3']:
+ return join(host_build, 'native-build', 'python')
+ elif host_name in ['hostpython3crystax', 'hostpython2legacy']:
+ return join(host_build, 'hostpython')
+ else:
+ python_recipe = self.ctx.python_recipe
+ return 'python{}'.format(python_recipe.version)
+
+ @property
+ def hostpython_location(self):
+ if not self.call_hostpython_via_targetpython:
+ return self.real_hostpython_location
+ return self.ctx.hostpython
+
+ @property
+ def folder_name(self):
+ '''The name of the build folders containing this recipe.'''
+ name = self.site_packages_name
+ if name is None:
+ name = self.name
+ return name
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super(PythonRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+
+ env['PYTHONNOUSERSITE'] = '1'
+
+ # Set the LANG, this isn't usually important but is a better default
+ # as it occasionally matters how Python e.g. reads files
+ env['LANG'] = "en_GB.UTF-8"
+
+ if not self.call_hostpython_via_targetpython:
+ # sets python headers/linkages...depending on python's recipe
+ python_name = self.ctx.python_recipe.name
+ python_version = self.ctx.python_recipe.version
+ python_short_version = '.'.join(python_version.split('.')[:2])
+ if not self.ctx.python_recipe.from_crystax:
+ env['CFLAGS'] += ' -I{}'.format(
+ self.ctx.python_recipe.include_root(arch.arch))
+ env['LDFLAGS'] += ' -L{} -lpython{}'.format(
+ self.ctx.python_recipe.link_root(arch.arch),
+ self.ctx.python_recipe.major_minor_version_string)
+ if python_name == 'python3':
+ env['LDFLAGS'] += 'm'
+ elif python_name == 'python2legacy':
+ env['PYTHON_ROOT'] = join(
+ self.ctx.python_recipe.get_build_dir(
+ arch.arch), 'python-install')
+ else:
+ ndk_dir_python = join(self.ctx.ndk_dir, 'sources',
+ 'python', python_version)
+ env['CFLAGS'] += ' -I{} '.format(
+ join(ndk_dir_python, 'include',
+ 'python'))
+ env['LDFLAGS'] += ' -L{}'.format(
+ join(ndk_dir_python, 'libs', arch.arch))
+ env['LDFLAGS'] += ' -lpython{}'.format(python_short_version)
+
+ hppath = []
+ hppath.append(join(dirname(self.hostpython_location), 'Lib'))
+ hppath.append(join(hppath[0], 'site-packages'))
+ builddir = join(dirname(self.hostpython_location), 'build')
+ if exists(builddir):
+ hppath += [join(builddir, d) for d in listdir(builddir)
+ if isdir(join(builddir, d))]
+ if len(hppath) > 0:
+ if 'PYTHONPATH' in env:
+ env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']])
+ else:
+ env['PYTHONPATH'] = ':'.join(hppath)
+ return env
+
+ def should_build(self, arch):
+ name = self.folder_name
+ if self.ctx.has_package(name):
+ info('Python package already exists in site-packages')
+ return False
+ info('{} apparently isn\'t already in site-packages'.format(name))
+ return True
+
+ def build_arch(self, arch):
+ '''Install the Python module by calling setup.py install with
+ the target Python dir.'''
+ super(PythonRecipe, self).build_arch(arch)
+ self.install_python_package(arch)
+
+ def install_python_package(self, arch, name=None, env=None, is_dir=True):
+ '''Automate the installation of a Python package (or a cython
+ package where the cython components are pre-built).'''
+ # arch = self.filtered_archs[0] # old kivy-ios way
+ if name is None:
+ name = self.name
+ if env is None:
+ env = self.get_recipe_env(arch)
+
+ info('Installing {} into site-packages'.format(self.name))
+
+ with current_directory(self.get_build_dir(arch.arch)):
+ hostpython = sh.Command(self.hostpython_location)
+
+ if self.ctx.python_recipe.name != 'python2legacy':
+ hpenv = env.copy()
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(self.ctx.get_python_install_dir()),
+ '--install-lib=.',
+ _env=hpenv, *self.setup_extra_args)
+ elif self.call_hostpython_via_targetpython:
+ shprint(hostpython, 'setup.py', 'install', '-O2', _env=env,
+ *self.setup_extra_args)
+ else: # python2legacy
+ hppath = join(dirname(self.hostpython_location), 'Lib', 'site-packages')
+ hpenv = env.copy()
+ if 'PYTHONPATH' in hpenv:
+ hpenv['PYTHONPATH'] = ':'.join([hppath] + hpenv['PYTHONPATH'].split(':'))
+ else:
+ hpenv['PYTHONPATH'] = hppath
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(self.ctx.get_python_install_dir()),
+ '--install-lib=lib/python2.7/site-packages',
+ _env=hpenv, *self.setup_extra_args)
+
+ # If asked, also install in the hostpython build dir
+ if self.install_in_hostpython:
+ self.install_hostpython_package(arch)
+
+ def get_hostrecipe_env(self, arch):
+ env = environ.copy()
+ env['PYTHONPATH'] = join(dirname(self.real_hostpython_location), 'Lib', 'site-packages')
+ return env
+
+ def install_hostpython_package(self, arch):
+ env = self.get_hostrecipe_env(arch)
+ real_hostpython = sh.Command(self.real_hostpython_location)
+ shprint(real_hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(dirname(self.real_hostpython_location)),
+ '--install-lib=Lib/site-packages',
+ _env=env, *self.setup_extra_args)
+
+
+class CompiledComponentsPythonRecipe(PythonRecipe):
+ pre_build_ext = False
+
+ build_cmd = 'build_ext'
+
+ def build_arch(self, arch):
+ '''Build any cython components, then install the Python module by
+ calling setup.py install with the target Python dir.
+ '''
+ Recipe.build_arch(self, arch)
+ self.build_compiled_components(arch)
+ self.install_python_package(arch)
+
+ def build_compiled_components(self, arch):
+ info('Building compiled components in {}'.format(self.name))
+
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ hostpython = sh.Command(self.hostpython_location)
+ if self.install_in_hostpython:
+ shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)
+ shprint(hostpython, 'setup.py', self.build_cmd, '-v',
+ _env=env, *self.setup_extra_args)
+ build_dir = glob.glob('build/lib.*')[0]
+ shprint(sh.find, build_dir, '-name', '"*.o"', '-exec',
+ env['STRIP'], '{}', ';', _env=env)
+
+ def install_hostpython_package(self, arch):
+ env = self.get_hostrecipe_env(arch)
+ self.rebuild_compiled_components(arch, env)
+ super(CompiledComponentsPythonRecipe, self).install_hostpython_package(arch)
+
+ def rebuild_compiled_components(self, arch, env):
+ info('Rebuilding compiled components in {}'.format(self.name))
+
+ hostpython = sh.Command(self.real_hostpython_location)
+ shprint(hostpython, 'setup.py', 'clean', '--all', _env=env)
+ shprint(hostpython, 'setup.py', self.build_cmd, '-v', _env=env,
+ *self.setup_extra_args)
+
+
+class CppCompiledComponentsPythonRecipe(CompiledComponentsPythonRecipe):
+ """ Extensions that require the cxx-stl """
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch):
+ env = super(CppCompiledComponentsPythonRecipe, self).get_recipe_env(arch)
+ keys = dict(
+ ctx=self.ctx,
+ arch=arch,
+ arch_noeabi=arch.arch.replace('eabi', '')
+ )
+ env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
+ env['CFLAGS'] += (
+ " -I{ctx.ndk_dir}/platforms/android-{ctx.android_api}/arch-{arch_noeabi}/usr/include" +
+ " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/include" +
+ " -I{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/include").format(**keys)
+ env['CXXFLAGS'] = env['CFLAGS'] + ' -frtti -fexceptions'
+ env['LDFLAGS'] += (
+ " -L{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}" +
+ " -lgnustl_shared").format(**keys)
+
+ return env
+
+ def build_compiled_components(self, arch):
+ super(CppCompiledComponentsPythonRecipe, self).build_compiled_components(arch)
+
+ # Copy libgnustl_shared.so
+ with current_directory(self.get_build_dir(arch.arch)):
+ sh.cp(
+ "{ctx.ndk_dir}/sources/cxx-stl/gnu-libstdc++/{ctx.toolchain_version}/libs/{arch.arch}/libgnustl_shared.so".format(ctx=self.ctx, arch=arch),
+ self.ctx.get_libs_dir(arch.arch)
+ )
+
+
+class CythonRecipe(PythonRecipe):
+ pre_build_ext = False
+ cythonize = True
+ cython_args = []
+ call_hostpython_via_targetpython = False
+
+ def __init__(self, *args, **kwargs):
+ super(CythonRecipe, self).__init__(*args, **kwargs)
+ depends = self.depends
+ depends.append(('python2', 'python2legacy', 'python3', 'python3crystax'))
+ depends = list(set(depends))
+ self.depends = depends
+
+ def build_arch(self, arch):
+ '''Build any cython components, then install the Python module by
+ calling setup.py install with the target Python dir.
+ '''
+ Recipe.build_arch(self, arch)
+ self.build_cython_components(arch)
+ self.install_python_package(arch)
+
+ def build_cython_components(self, arch):
+ info('Cythonizing anything necessary in {}'.format(self.name))
+
+ env = self.get_recipe_env(arch)
+
+ with current_directory(self.get_build_dir(arch.arch)):
+ hostpython = sh.Command(self.ctx.hostpython)
+ shprint(hostpython, '-c', 'import sys; print(sys.path)', _env=env)
+ debug('cwd is {}'.format(realpath(curdir)))
+ info('Trying first build of {} to get cython files: this is '
+ 'expected to fail'.format(self.name))
+
+ manually_cythonise = False
+ try:
+ shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env,
+ *self.setup_extra_args)
+ except sh.ErrorReturnCode_1:
+ print()
+ info('{} first build failed (as expected)'.format(self.name))
+ manually_cythonise = True
+
+ if manually_cythonise:
+ self.cythonize_build(env=env)
+ shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env,
+ _tail=20, _critical=True, *self.setup_extra_args)
+ else:
+ info('First build appeared to complete correctly, skipping manual'
+ 'cythonising.')
+
+ self.strip_object_files(arch, env)
+
+ def strip_object_files(self, arch, env, build_dir=None):
+ if build_dir is None:
+ build_dir = self.get_build_dir(arch.arch)
+ with current_directory(build_dir):
+ info('Stripping object files')
+ if self.ctx.python_recipe.name == 'python2legacy':
+ info('Stripping object files')
+ build_lib = glob.glob('./build/lib*')
+ shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
+ env['STRIP'], '{}', ';', _env=env)
+ else:
+ shprint(sh.find, '.', '-iname', '*.so', '-exec',
+ '/usr/bin/echo', '{}', ';', _env=env)
+ shprint(sh.find, '.', '-iname', '*.so', '-exec',
+ env['STRIP'].split(' ')[0], '--strip-unneeded',
+ # '/usr/bin/strip', '--strip-unneeded',
+ '{}', ';', _env=env)
+
+ def cythonize_file(self, env, build_dir, filename):
+ short_filename = filename
+ if filename.startswith(build_dir):
+ short_filename = filename[len(build_dir) + 1:]
+ info(u"Cythonize {}".format(short_filename))
+ cyenv = env.copy()
+ if 'CYTHONPATH' in cyenv:
+ cyenv['PYTHONPATH'] = cyenv['CYTHONPATH']
+ elif 'PYTHONPATH' in cyenv:
+ del cyenv['PYTHONPATH']
+ if 'PYTHONNOUSERSITE' in cyenv:
+ cyenv.pop('PYTHONNOUSERSITE')
+ cython = 'cython' if self.ctx.python_recipe.from_crystax else self.ctx.cython
+ cython_command = sh.Command(cython)
+ shprint(cython_command, filename, *self.cython_args, _env=cyenv)
+
+ def cythonize_build(self, env, build_dir="."):
+ if not self.cythonize:
+ info('Running cython cancelled per recipe setting')
+ return
+ info('Running cython where appropriate')
+ for root, dirnames, filenames in walk("."):
+ for filename in fnmatch.filter(filenames, "*.pyx"):
+ self.cythonize_file(env, build_dir, join(root, filename))
+
+ def get_recipe_env(self, arch, with_flags_in_cc=True):
+ env = super(CythonRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format(
+ self.ctx.get_libs_dir(arch.arch) +
+ ' -L{} '.format(self.ctx.libs_dir) +
+ ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local',
+ arch.arch)))
+ if self.ctx.python_recipe.from_crystax:
+ env['LDFLAGS'] = (env['LDFLAGS'] +
+ ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)))
+
+ if self.ctx.python_recipe.name == 'python2legacy':
+ env['LDSHARED'] = join(self.ctx.root_dir, 'tools', 'liblink.sh')
+ else:
+ env['LDSHARED'] = env['CC'] + ' -shared'
+ # shprint(sh.whereis, env['LDSHARED'], _env=env)
+ env['LIBLINK'] = 'NOTNONE'
+ env['NDKPLATFORM'] = self.ctx.ndk_platform
+ if self.ctx.copy_libs:
+ env['COPYLIBS'] = '1'
+
+ # Every recipe uses its own liblink path, object files are
+ # collected and biglinked later
+ liblink_path = join(self.get_build_container_dir(arch.arch),
+ 'objects_{}'.format(self.name))
+ env['LIBLINK_PATH'] = liblink_path
+ ensure_dir(liblink_path)
+
+ # Add crystax-specific site packages:
+ if self.ctx.python_recipe.from_crystax:
+ command = sh.Command('python{}'.format(self.ctx.python_recipe.version))
+ site_packages_dirs = command(
+ '-c', 'import site; print("\\n".join(site.getsitepackages()))')
+ site_packages_dirs = site_packages_dirs.stdout.decode('utf-8').split('\n')
+ if 'PYTHONPATH' in env:
+ env['PYTHONPATH'] = env['PYTHONPATH'] +\
+ ':{}'.format(':'.join(site_packages_dirs))
+ else:
+ env['PYTHONPATH'] = ':'.join(site_packages_dirs)
+ while env['PYTHONPATH'].find("::") > 0:
+ env['PYTHONPATH'] = env['PYTHONPATH'].replace("::", ":")
+ if env['PYTHONPATH'].endswith(":"):
+ env['PYTHONPATH'] = env['PYTHONPATH'][:-1]
+ if env['PYTHONPATH'].startswith(":"):
+ env['PYTHONPATH'] = env['PYTHONPATH'][1:]
+
+ return env
+
+
+class TargetPythonRecipe(Recipe):
+ '''Class for target python recipes. Sets ctx.python_recipe to point to
+ itself, so as to know later what kind of Python was built or used.'''
+
+ from_crystax = False
+ '''True if the python is used from CrystaX, False otherwise (i.e. if
+ it is built by p4a).'''
+
+ def __init__(self, *args, **kwargs):
+ self._ctx = None
+ super(TargetPythonRecipe, self).__init__(*args, **kwargs)
+
+ def prebuild_arch(self, arch):
+ super(TargetPythonRecipe, self).prebuild_arch(arch)
+ if self.from_crystax and self.ctx.ndk != 'crystax':
+ raise BuildInterruptingException(
+ 'The {} recipe can only be built when '
+ 'using the CrystaX NDK. Exiting.'.format(self.name))
+ self.ctx.python_recipe = self
+
+ def include_root(self, arch):
+ '''The root directory from which to include headers.'''
+ raise NotImplementedError('Not implemented in TargetPythonRecipe')
+
+ def link_root(self):
+ raise NotImplementedError('Not implemented in TargetPythonRecipe')
+
+ @property
+ def major_minor_version_string(self):
+ from distutils.version import LooseVersion
+ return '.'.join([str(v) for v in LooseVersion(self.version).version[:2]])
+
+ def create_python_bundle(self, dirn, arch):
+ """
+ Create a packaged python bundle in the target directory, by
+ copying all the modules and standard library to the right
+ place.
+ """
+ raise NotImplementedError('{} does not implement create_python_bundle'.format(self))
+
+ def reduce_object_file_names(self, dirn):
+ """Recursively renames all files named XXX.cpython-...-linux-gnu.so"
+ to "XXX.so", i.e. removing the erroneous architecture name
+ coming from the local system.
+ """
+ py_so_files = shprint(sh.find, dirn, '-iname', '*.so')
+ filens = py_so_files.stdout.decode('utf-8').split('\n')[:-1]
+ for filen in filens:
+ file_dirname, file_basename = split(filen)
+ parts = file_basename.split('.')
+ if len(parts) <= 2:
+ continue
+ shprint(sh.mv, filen, join(file_dirname, parts[0] + '.so'))
+
+
+def md5sum(filen):
+ '''Calculate the md5sum of a file.
+ '''
+ with open(filen, 'rb') as fileh:
+ md5 = hashlib.md5(fileh.read())
+
+ return md5.hexdigest()
diff --git a/p4a/pythonforandroidold/recipes/Pillow/__init__.py b/p4a/pythonforandroidold/recipes/Pillow/__init__.py
new file mode 100644
index 0000000..14c9d2b
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/Pillow/__init__.py
@@ -0,0 +1,59 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from os.path import join
+
+
+class PillowRecipe(CompiledComponentsPythonRecipe):
+
+ version = '5.2.0'
+ url = 'https://github.com/python-pillow/Pillow/archive/{version}.tar.gz'
+ site_packages_name = 'Pillow'
+ depends = ['png', 'jpeg', 'freetype', 'setuptools']
+ patches = [join('patches', 'fix-docstring.patch'),
+ join('patches', 'fix-setup.patch')]
+
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super(PillowRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+
+ env['ANDROID_ROOT'] = join(self.ctx.ndk_platform, 'usr')
+ ndk_lib_dir = join(self.ctx.ndk_platform, 'usr', 'lib')
+ ndk_include_dir = join(self.ctx.ndk_dir, 'sysroot', 'usr', 'include')
+
+ png = self.get_recipe('png', self.ctx)
+ png_lib_dir = png.get_lib_dir(arch)
+ png_jni_dir = png.get_jni_dir(arch)
+
+ jpeg = self.get_recipe('jpeg', self.ctx)
+ jpeg_inc_dir = jpeg_lib_dir = jpeg.get_build_dir(arch.arch)
+
+ freetype = self.get_recipe('freetype', self.ctx)
+ free_lib_dir = join(freetype.get_build_dir(arch.arch), 'objs', '.libs')
+ free_inc_dir = join(freetype.get_build_dir(arch.arch), 'include')
+
+ # harfbuzz is a direct dependency of freetype and we need the proper
+ # flags to successfully build the Pillow recipe, so we add them here.
+ harfbuzz = self.get_recipe('harfbuzz', self.ctx)
+ harf_lib_dir = join(harfbuzz.get_build_dir(arch.arch), 'src', '.libs')
+ harf_inc_dir = harfbuzz.get_build_dir(arch.arch)
+
+ env['JPEG_ROOT'] = '{}|{}'.format(jpeg_lib_dir, jpeg_inc_dir)
+ env['FREETYPE_ROOT'] = '{}|{}'.format(free_lib_dir, free_inc_dir)
+ env['ZLIB_ROOT'] = '{}|{}'.format(ndk_lib_dir, ndk_include_dir)
+
+ cflags = ' -I{}'.format(png_jni_dir)
+ cflags += ' -I{} -I{}'.format(harf_inc_dir, join(harf_inc_dir, 'src'))
+ cflags += ' -I{}'.format(free_inc_dir)
+ cflags += ' -I{}'.format(jpeg_inc_dir)
+ cflags += ' -I{}'.format(ndk_include_dir)
+
+ env['LIBS'] = ' -lpng -lfreetype -lharfbuzz -ljpeg -lturbojpeg'
+
+ env['LDFLAGS'] += ' -L{} -L{} -L{} -L{}'.format(
+ png_lib_dir, harf_lib_dir, jpeg_lib_dir, ndk_lib_dir)
+ if cflags not in env['CFLAGS']:
+ env['CFLAGS'] += cflags
+ return env
+
+
+recipe = PillowRecipe()
diff --git a/p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch b/p4a/pythonforandroidold/recipes/Pillow/patches/fix-docstring.patch
similarity index 100%
rename from p4a/pythonforandroid/recipes/Pillow/patches/fix-docstring.patch
rename to p4a/pythonforandroidold/recipes/Pillow/patches/fix-docstring.patch
diff --git a/p4a/pythonforandroidold/recipes/Pillow/patches/fix-setup.patch b/p4a/pythonforandroidold/recipes/Pillow/patches/fix-setup.patch
new file mode 100644
index 0000000..3b0ccef
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/Pillow/patches/fix-setup.patch
@@ -0,0 +1,148 @@
+diff --git a/setup.py b/setup.py
+index 761d552..4ddc598 100755
+--- a/setup.py
++++ b/setup.py
+@@ -136,12 +136,12 @@ except (ImportError, OSError):
+
+ NAME = 'Pillow'
+ PILLOW_VERSION = get_version()
+-JPEG_ROOT = None
++JPEG_ROOT = tuple(os.environ['JPEG_ROOT'].split('|')) if 'JPEG_ROOT' in os.environ else None
+ JPEG2K_ROOT = None
+-ZLIB_ROOT = None
++ZLIB_ROOT = tuple(os.environ['ZLIB_ROOT'].split('|')) if 'ZLIB_ROOT' in os.environ else None
+ IMAGEQUANT_ROOT = None
+ TIFF_ROOT = None
+-FREETYPE_ROOT = None
++FREETYPE_ROOT = tuple(os.environ['FREETYPE_ROOT'].split('|')) if 'FREETYPE_ROOT' in os.environ else None
+ LCMS_ROOT = None
+
+
+@@ -194,7 +194,7 @@ class pil_build_ext(build_ext):
+ ]
+
+ def initialize_options(self):
+- self.disable_platform_guessing = None
++ self.disable_platform_guessing = True
+ build_ext.initialize_options(self)
+ for x in self.feature:
+ setattr(self, 'disable_%s' % x, None)
+@@ -466,61 +466,6 @@ class pil_build_ext(build_ext):
+ feature.jpeg = "libjpeg" # alternative name
+
+ feature.openjpeg_version = None
+- if feature.want('jpeg2000'):
+- _dbg('Looking for jpeg2000')
+- best_version = None
+- best_path = None
+-
+- # Find the best version
+- for directory in self.compiler.include_dirs:
+- _dbg('Checking for openjpeg-#.# in %s', directory)
+- try:
+- listdir = os.listdir(directory)
+- except Exception:
+- # WindowsError, FileNotFoundError
+- continue
+- for name in listdir:
+- if name.startswith('openjpeg-') and \
+- os.path.isfile(os.path.join(directory, name,
+- 'openjpeg.h')):
+- _dbg('Found openjpeg.h in %s/%s', (directory, name))
+- version = tuple(int(x) for x in name[9:].split('.'))
+- if best_version is None or version > best_version:
+- best_version = version
+- best_path = os.path.join(directory, name)
+- _dbg('Best openjpeg version %s so far in %s',
+- (best_version, best_path))
+-
+- if best_version and _find_library_file(self, 'openjp2'):
+- # Add the directory to the include path so we can include
+- # rather than having to cope with the versioned
+- # include path
+- # FIXME (melvyn-sopacua):
+- # At this point it's possible that best_path is already in
+- # self.compiler.include_dirs. Should investigate how that is
+- # possible.
+- _add_directory(self.compiler.include_dirs, best_path, 0)
+- feature.jpeg2000 = 'openjp2'
+- feature.openjpeg_version = '.'.join(str(x) for x in best_version)
+-
+- if feature.want('imagequant'):
+- _dbg('Looking for imagequant')
+- if _find_include_file(self, 'libimagequant.h'):
+- if _find_library_file(self, "imagequant"):
+- feature.imagequant = "imagequant"
+- elif _find_library_file(self, "libimagequant"):
+- feature.imagequant = "libimagequant"
+-
+- if feature.want('tiff'):
+- _dbg('Looking for tiff')
+- if _find_include_file(self, 'tiff.h'):
+- if _find_library_file(self, "tiff"):
+- feature.tiff = "tiff"
+- if sys.platform == "win32" and _find_library_file(self, "libtiff"):
+- feature.tiff = "libtiff"
+- if (sys.platform == "darwin" and
+- _find_library_file(self, "libtiff")):
+- feature.tiff = "libtiff"
+
+ if feature.want('freetype'):
+ _dbg('Looking for freetype')
+@@ -546,36 +491,6 @@ class pil_build_ext(build_ext):
+ if subdir:
+ _add_directory(self.compiler.include_dirs, subdir, 0)
+
+- if feature.want('lcms'):
+- _dbg('Looking for lcms')
+- if _find_include_file(self, "lcms2.h"):
+- if _find_library_file(self, "lcms2"):
+- feature.lcms = "lcms2"
+- elif _find_library_file(self, "lcms2_static"):
+- # alternate Windows name.
+- feature.lcms = "lcms2_static"
+-
+- if feature.want('webp'):
+- _dbg('Looking for webp')
+- if (_find_include_file(self, "webp/encode.h") and
+- _find_include_file(self, "webp/decode.h")):
+- # In Google's precompiled zip it is call "libwebp":
+- if _find_library_file(self, "webp"):
+- feature.webp = "webp"
+- elif _find_library_file(self, "libwebp"):
+- feature.webp = "libwebp"
+-
+- if feature.want('webpmux'):
+- _dbg('Looking for webpmux')
+- if (_find_include_file(self, "webp/mux.h") and
+- _find_include_file(self, "webp/demux.h")):
+- if (_find_library_file(self, "webpmux") and
+- _find_library_file(self, "webpdemux")):
+- feature.webpmux = "webpmux"
+- if (_find_library_file(self, "libwebpmux") and
+- _find_library_file(self, "libwebpdemux")):
+- feature.webpmux = "libwebpmux"
+-
+ for f in feature:
+ if not getattr(feature, f) and feature.require(f):
+ if f in ('jpeg', 'zlib'):
+@@ -612,8 +527,6 @@ class pil_build_ext(build_ext):
+ defs.append(("HAVE_LIBTIFF", None))
+ if sys.platform == "win32":
+ libs.extend(["kernel32", "user32", "gdi32"])
+- if struct.unpack("h", "\0\1".encode('ascii'))[0] == 1:
+- defs.append(("WORDS_BIGENDIAN", None))
+
+ if sys.platform == "win32" and not (PLATFORM_PYPY or PLATFORM_MINGW):
+ defs.append(("PILLOW_VERSION", '"\\"%s\\""' % PILLOW_VERSION))
+@@ -658,10 +571,6 @@ class pil_build_ext(build_ext):
+ define_macros=defs))
+
+ tk_libs = ['psapi'] if sys.platform == 'win32' else []
+- exts.append(Extension("PIL._imagingtk",
+- ["src/_imagingtk.c", "src/Tk/tkImaging.c"],
+- include_dirs=['src/Tk'],
+- libraries=tk_libs))
+
+ exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"]))
+ exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]))
diff --git a/p4a/pythonforandroidold/recipes/__init__.py b/p4a/pythonforandroidold/recipes/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/p4a/pythonforandroidold/recipes/android/__init__.py b/p4a/pythonforandroidold/recipes/android/__init__.py
new file mode 100644
index 0000000..4a06ca8
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/__init__.py
@@ -0,0 +1,95 @@
+from __future__ import unicode_literals
+from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
+from pythonforandroid.util import current_directory
+from pythonforandroid.patching import will_build
+from pythonforandroid import logger
+
+from os.path import join
+
+
+class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
+ # name = 'android'
+ version = None
+ url = None
+
+ src_filename = 'src'
+
+ depends = [('pygame', 'sdl2', 'genericndkbuild'),
+ 'pyjnius']
+
+ config_env = {}
+
+ def get_recipe_env(self, arch):
+ env = super(AndroidRecipe, self).get_recipe_env(arch)
+ env.update(self.config_env)
+ return env
+
+ def prebuild_arch(self, arch):
+ super(AndroidRecipe, self).prebuild_arch(arch)
+ ctx_bootstrap = self.ctx.bootstrap.name
+
+ # define macros for Cython, C, Python
+ tpxi = 'DEF {} = {}\n'
+ th = '#define {} {}\n'
+ tpy = '{} = {}\n'
+
+ # make sure bootstrap name is in unicode
+ if isinstance(ctx_bootstrap, bytes):
+ ctx_bootstrap = ctx_bootstrap.decode('utf-8')
+ bootstrap = bootstrap_name = ctx_bootstrap
+
+ is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3', 'sdl2_gradle')
+ is_pygame = bootstrap_name in ('pygame',)
+ is_webview = bootstrap_name in ('webview',)
+
+ if is_sdl2 or is_webview:
+ if is_sdl2:
+ bootstrap = 'sdl2'
+ java_ns = u'org.kivy.android'
+ jni_ns = u'org/kivy/android'
+ elif is_pygame:
+ java_ns = u'org.renpy.android'
+ jni_ns = u'org/renpy/android'
+ else:
+ logger.error((
+ 'unsupported bootstrap for android recipe: {}'
+ ''.format(bootstrap_name)
+ ))
+ exit(1)
+
+ config = {
+ 'BOOTSTRAP': bootstrap,
+ 'IS_SDL2': int(is_sdl2),
+ 'IS_PYGAME': int(is_pygame),
+ 'PY2': int(will_build('python2')(self)),
+ 'JAVA_NAMESPACE': java_ns,
+ 'JNI_NAMESPACE': jni_ns,
+ }
+
+ # create config files for Cython, C and Python
+ with (
+ current_directory(self.get_build_dir(arch.arch))), (
+ open(join('android', 'config.pxi'), 'w')) as fpxi, (
+ open(join('android', 'config.h'), 'w')) as fh, (
+ open(join('android', 'config.py'), 'w')) as fpy:
+
+ for key, value in config.items():
+ fpxi.write(tpxi.format(key, repr(value)))
+ fpy.write(tpy.format(key, repr(value)))
+
+ fh.write(th.format(
+ key,
+ value if isinstance(value, int) else '"{}"'.format(value)
+ ))
+ self.config_env[key] = str(value)
+
+ if is_sdl2:
+ fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
+ fh.write(
+ '#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n'
+ )
+ elif is_pygame:
+ fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
+
+
+recipe = AndroidRecipe()
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/__init__.py b/p4a/pythonforandroidold/recipes/android/src/android/__init__.py
new file mode 100644
index 0000000..cb95734
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/__init__.py
@@ -0,0 +1,8 @@
+'''
+Android module
+==============
+
+'''
+
+# legacy import
+from android._android import * # noqa: F401, F403
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/_android.pyx b/p4a/pythonforandroidold/recipes/android/src/android/_android.pyx
new file mode 100644
index 0000000..d332eed
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/_android.pyx
@@ -0,0 +1,385 @@
+# Android-specific python services.
+
+include "config.pxi"
+
+IF BOOTSTRAP == 'pygame':
+ cdef extern int SDL_ANDROID_CheckPause()
+ cdef extern void SDL_ANDROID_WaitForResume() nogil
+ cdef extern void SDL_ANDROID_MapKey(int scancode, int keysym)
+
+ def check_pause():
+ return SDL_ANDROID_CheckPause()
+
+ def wait_for_resume():
+ android_accelerometer_enable(False)
+ SDL_ANDROID_WaitForResume()
+ android_accelerometer_enable(accelerometer_enabled)
+
+ def map_key(scancode, keysym):
+ SDL_ANDROID_MapKey(scancode, keysym)
+
+# Android keycodes.
+KEYCODE_UNKNOWN = 0
+KEYCODE_SOFT_LEFT = 1
+KEYCODE_SOFT_RIGHT = 2
+KEYCODE_HOME = 3
+KEYCODE_BACK = 4
+KEYCODE_CALL = 5
+KEYCODE_ENDCALL = 6
+KEYCODE_0 = 7
+KEYCODE_1 = 8
+KEYCODE_2 = 9
+KEYCODE_3 = 10
+KEYCODE_4 = 11
+KEYCODE_5 = 12
+KEYCODE_6 = 13
+KEYCODE_7 = 14
+KEYCODE_8 = 15
+KEYCODE_9 = 16
+KEYCODE_STAR = 17
+KEYCODE_POUND = 18
+KEYCODE_DPAD_UP = 19
+KEYCODE_DPAD_DOWN = 20
+KEYCODE_DPAD_LEFT = 21
+KEYCODE_DPAD_RIGHT = 22
+KEYCODE_DPAD_CENTER = 23
+KEYCODE_VOLUME_UP = 24
+KEYCODE_VOLUME_DOWN = 25
+KEYCODE_POWER = 26
+KEYCODE_CAMERA = 27
+KEYCODE_CLEAR = 28
+KEYCODE_A = 29
+KEYCODE_B = 30
+KEYCODE_C = 31
+KEYCODE_D = 32
+KEYCODE_E = 33
+KEYCODE_F = 34
+KEYCODE_G = 35
+KEYCODE_H = 36
+KEYCODE_I = 37
+KEYCODE_J = 38
+KEYCODE_K = 39
+KEYCODE_L = 40
+KEYCODE_M = 41
+KEYCODE_N = 42
+KEYCODE_O = 43
+KEYCODE_P = 44
+KEYCODE_Q = 45
+KEYCODE_R = 46
+KEYCODE_S = 47
+KEYCODE_T = 48
+KEYCODE_U = 49
+KEYCODE_V = 50
+KEYCODE_W = 51
+KEYCODE_X = 52
+KEYCODE_Y = 53
+KEYCODE_Z = 54
+KEYCODE_COMMA = 55
+KEYCODE_PERIOD = 56
+KEYCODE_ALT_LEFT = 57
+KEYCODE_ALT_RIGHT = 58
+KEYCODE_SHIFT_LEFT = 59
+KEYCODE_SHIFT_RIGHT = 60
+KEYCODE_TAB = 61
+KEYCODE_SPACE = 62
+KEYCODE_SYM = 63
+KEYCODE_EXPLORER = 64
+KEYCODE_ENVELOPE = 65
+KEYCODE_ENTER = 66
+KEYCODE_DEL = 67
+KEYCODE_GRAVE = 68
+KEYCODE_MINUS = 69
+KEYCODE_EQUALS = 70
+KEYCODE_LEFT_BRACKET = 71
+KEYCODE_RIGHT_BRACKET = 72
+KEYCODE_BACKSLASH = 73
+KEYCODE_SEMICOLON = 74
+KEYCODE_APOSTROPHE = 75
+KEYCODE_SLASH = 76
+KEYCODE_AT = 77
+KEYCODE_NUM = 78
+KEYCODE_HEADSETHOOK = 79
+KEYCODE_FOCUS = 80
+KEYCODE_PLUS = 81
+KEYCODE_MENU = 82
+KEYCODE_NOTIFICATION = 83
+KEYCODE_SEARCH = 84
+KEYCODE_MEDIA_PLAY_PAUSE= 85
+KEYCODE_MEDIA_STOP = 86
+KEYCODE_MEDIA_NEXT = 87
+KEYCODE_MEDIA_PREVIOUS = 88
+KEYCODE_MEDIA_REWIND = 89
+KEYCODE_MEDIA_FAST_FORWARD = 90
+KEYCODE_MUTE = 91
+
+# Vibration support.
+cdef extern void android_vibrate(double)
+
+def vibrate(s):
+ android_vibrate(s)
+
+# Accelerometer support.
+cdef extern void android_accelerometer_enable(int)
+cdef extern void android_accelerometer_reading(float *)
+
+accelerometer_enabled = False
+
+def accelerometer_enable(p):
+ global accelerometer_enabled
+
+ android_accelerometer_enable(p)
+
+ accelerometer_enabled = p
+
+def accelerometer_reading():
+ cdef float rv[3]
+ android_accelerometer_reading(rv)
+
+ return (rv[0], rv[1], rv[2])
+
+# Wifi reading support
+cdef extern void android_wifi_scanner_enable()
+cdef extern char * android_wifi_scan()
+
+def wifi_scanner_enable():
+ android_wifi_scanner_enable()
+
+def wifi_scan():
+ cdef char * reading
+ reading = android_wifi_scan()
+
+ reading_list = []
+
+ for line in filter(lambda l: l, reading.split('\n')):
+ [ssid, mac, level] = line.split('\t')
+ reading_list.append((ssid.strip(), mac.upper().strip(), int(level)))
+
+ return reading_list
+
+# DisplayMetrics information.
+cdef extern int android_get_dpi()
+
+def get_dpi():
+ return android_get_dpi()
+
+
+# Soft keyboard.
+cdef extern void android_show_keyboard(int)
+cdef extern void android_hide_keyboard()
+
+
+from jnius import autoclass, PythonJavaClass, java_method, cast
+
+# API versions
+api_version = autoclass('android.os.Build$VERSION').SDK_INT
+version_codes = autoclass('android.os.Build$VERSION_CODES')
+
+
+python_act = autoclass(JAVA_NAMESPACE + u'.PythonActivity')
+Rect = autoclass(u'android.graphics.Rect')
+mActivity = python_act.mActivity
+if mActivity:
+ # PyGame backend already has the listener so adding
+ # one here leads to a crash/too much cpu usage.
+ # SDL2 now does not need the listener so there is
+ # no point adding a processor intensive layout listenere here.
+ height = 0
+ def get_keyboard_height():
+ rctx = Rect()
+ mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rctx)
+ # NOTE top should always be zero
+ rctx.top = 0
+ height = mActivity.getWindowManager().getDefaultDisplay().getHeight() - (rctx.bottom - rctx.top)
+ return height
+else:
+ def get_keyboard_height():
+ return 0
+
+# Flags for input_type, for requesting a particular type of keyboard
+#android FLAGS
+TYPE_CLASS_DATETIME = 4
+TYPE_CLASS_NUMBER = 2
+TYPE_NUMBER_VARIATION_NORMAL = 0
+TYPE_NUMBER_VARIATION_PASSWORD = 16
+TYPE_CLASS_TEXT = 1
+TYPE_TEXT_FLAG_AUTO_COMPLETE = 65536
+TYPE_TEXT_FLAG_AUTO_CORRECT = 32768
+TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288
+TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32
+TYPE_TEXT_VARIATION_NORMAL = 0
+TYPE_TEXT_VARIATION_PASSWORD = 128
+TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 112
+TYPE_TEXT_VARIATION_URI = 16
+TYPE_CLASS_PHONE = 3
+
+IF BOOTSTRAP == 'sdl2':
+ def remove_presplash():
+ # Remove android presplash in SDL2 bootstrap.
+ mActivity.removeLoadingScreen()
+
+def show_keyboard(target, input_type):
+ if input_type == 'text':
+ _input_type = TYPE_CLASS_TEXT
+ elif input_type == 'number':
+ _input_type = TYPE_CLASS_NUMBER
+ elif input_type == 'url':
+ _input_type = \
+ TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI
+ elif input_type == 'mail':
+ _input_type = \
+ TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ elif input_type == 'datetime':
+ _input_type = TYPE_CLASS_DATETIME
+ elif input_type == 'tel':
+ _input_type = TYPE_CLASS_PHONE
+ elif input_type == 'address':
+ _input_type = TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+
+ if hasattr(target, 'password') and target.password:
+ if _input_type == TYPE_CLASS_TEXT:
+ _input_type |= TYPE_TEXT_VARIATION_PASSWORD
+ elif _input_type == TYPE_CLASS_NUMBER:
+ _input_type |= TYPE_NUMBER_VARIATION_PASSWORD
+
+ if hasattr(target, 'keyboard_suggestions') and not target.keyboard_suggestions:
+ if _input_type == TYPE_CLASS_TEXT:
+ _input_type = TYPE_CLASS_TEXT | \
+ TYPE_TEXT_FLAG_NO_SUGGESTIONS
+
+ android_show_keyboard(_input_type)
+
+def hide_keyboard():
+ android_hide_keyboard()
+
+# Build info.
+cdef extern char* BUILD_MANUFACTURER
+cdef extern char* BUILD_MODEL
+cdef extern char* BUILD_PRODUCT
+cdef extern char* BUILD_VERSION_RELEASE
+
+cdef extern void android_get_buildinfo()
+
+class BuildInfo:
+ MANUFACTURER = None
+ MODEL = None
+ PRODUCT = None
+ VERSION_RELEASE = None
+
+def get_buildinfo():
+ android_get_buildinfo()
+ binfo = BuildInfo()
+ binfo.MANUFACTURER = BUILD_MANUFACTURER
+ binfo.MODEL = BUILD_MODEL
+ binfo.PRODUCT = BUILD_PRODUCT
+ binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
+ return binfo
+
+IF IS_PYGAME:
+ # Activate input - required to receive input events.
+ cdef extern void android_activate_input()
+
+ def init():
+ android_activate_input()
+
+ # Action send
+ cdef extern void android_action_send(char*, char*, char*, char*, char*)
+ def action_send(mimetype, filename=None, subject=None, text=None,
+ chooser_title=None):
+ cdef char *j_mimetype = mimetype
+ cdef char *j_filename = NULL
+ cdef char *j_subject = NULL
+ cdef char *j_text = NULL
+ cdef char *j_chooser_title = NULL
+ if filename is not None:
+ j_filename = filename
+ if subject is not None:
+ j_subject = subject
+ if text is not None:
+ j_text = text
+ if chooser_title is not None:
+ j_chooser_title = chooser_title
+ android_action_send(j_mimetype, j_filename, j_subject, j_text,
+ j_chooser_title)
+
+ cdef extern int android_checkstop()
+ cdef extern void android_ackstop()
+
+ def check_stop():
+ return android_checkstop()
+
+ def ack_stop():
+ android_ackstop()
+
+# -------------------------------------------------------------------
+# URL Opening.
+def open_url(url):
+ Intent = autoclass('android.content.Intent')
+ Uri = autoclass('android.net.Uri')
+ browserIntent = Intent()
+ browserIntent.setAction(Intent.ACTION_VIEW)
+ browserIntent.setData(Uri.parse(url))
+ currentActivity = cast('android.app.Activity', mActivity)
+ currentActivity.startActivity(browserIntent)
+ return True
+
+# Web browser support.
+class AndroidBrowser(object):
+ def open(self, url, new=0, autoraise=True):
+ return open_url(url)
+ def open_new(self, url):
+ return open_url(url)
+ def open_new_tab(self, url):
+ return open_url(url)
+
+import webbrowser
+webbrowser.register('android', AndroidBrowser)
+
+cdef extern void android_start_service(char *, char *, char *)
+def start_service(title=None, description=None, arg=None):
+ cdef char *j_title = NULL
+ cdef char *j_description = NULL
+ if title is not None:
+ j_title = title
+ if description is not None:
+ j_description = description
+ if arg is not None:
+ j_arg = arg
+ android_start_service(j_title, j_description, j_arg)
+
+cdef extern void android_stop_service()
+def stop_service():
+ android_stop_service()
+
+class AndroidService(object):
+ '''Android service class.
+ Run ``service/main.py`` from application directory as a service.
+
+ :Parameters:
+ `title`: str, default to 'Python service'
+ Notification title.
+
+ `description`: str, default to 'Kivy Python service started'
+ Notification text.
+ '''
+
+ def __init__(self, title='Python service',
+ description='Kivy Python service started'):
+ self.title = title
+ self.description = description
+
+ def start(self, arg=''):
+ '''Start the service.
+
+ :Parameters:
+ `arg`: str, default to ''
+ Argument to pass to a service,
+ through environment variable ``PYTHON_SERVICE_ARGUMENT``.
+ '''
+ start_service(self.title, self.description, arg)
+
+ def stop(self):
+ '''Stop the service.
+ '''
+ stop_service()
+
+
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/_android_billing.pyx b/p4a/pythonforandroidold/recipes/android/src/android/_android_billing.pyx
new file mode 100644
index 0000000..bd6bb2e
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/_android_billing.pyx
@@ -0,0 +1,81 @@
+# -------------------------------------------------------------------
+# Billing
+cdef extern void android_billing_service_start()
+cdef extern void android_billing_service_stop()
+cdef extern void android_billing_buy(char *sku)
+cdef extern char *android_billing_get_purchased_items()
+cdef extern char *android_billing_get_pending_message()
+
+class BillingService(object):
+
+ BILLING_ACTION_SUPPORTED = 'billingsupported'
+ BILLING_ACTION_ITEMSCHANGED = 'itemschanged'
+
+ BILLING_TYPE_INAPP = 'inapp'
+ BILLING_TYPE_SUBSCRIPTION = 'subs'
+
+ def __init__(self, callback):
+ super(BillingService, self).__init__()
+ self.callback = callback
+ self.purchased_items = None
+ android_billing_service_start()
+
+ def _stop(self):
+ android_billing_service_stop()
+
+ def buy(self, sku):
+ cdef char *j_sku = sku
+ android_billing_buy(j_sku)
+
+ def get_purchased_items(self):
+ cdef char *items = NULL
+ cdef bytes pitem
+ items = android_billing_get_purchased_items()
+ if items == NULL:
+ return []
+ pitems = items
+ ret = {}
+ for item in pitems.split('\n'):
+ if not item:
+ continue
+ sku, qt = item.split(',')
+ ret[sku] = {'qt': int(qt)}
+ return ret
+
+ def check(self, *largs):
+ cdef char *message
+ cdef bytes pymessage
+
+ while True:
+ message = android_billing_get_pending_message()
+ if message == NULL:
+ break
+ pymessage = message
+ self._handle_message(pymessage)
+
+ if self.purchased_items is None:
+ self._check_new_items()
+
+ def _handle_message(self, message):
+ action, data = message.split('|', 1)
+ #print "HANDLE MESSAGE-----", (action, data)
+
+ if action == 'billingSupported':
+ tp, value = data.split('|')
+ value = True if value == '1' else False
+ self.callback(BillingService.BILLING_ACTION_SUPPORTED, tp, value)
+
+ elif action == 'requestPurchaseResponse':
+ self._check_new_items()
+
+ elif action == 'purchaseStateChange':
+ self._check_new_items()
+
+ elif action == 'restoreTransaction':
+ self._check_new_items()
+
+ def _check_new_items(self):
+ items = self.get_purchased_items()
+ if self.purchased_items != items:
+ self.purchased_items = items
+ self.callback(BillingService.BILLING_ACTION_ITEMSCHANGED, self.purchased_items)
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/_android_billing_jni.c b/p4a/pythonforandroidold/recipes/android/src/android/_android_billing_jni.c
new file mode 100644
index 0000000..d438df3
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/_android_billing_jni.c
@@ -0,0 +1,120 @@
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
+#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
+#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
+
+void android_billing_service_start() {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "billingServiceStart", "()V");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+ (*env)->CallStaticVoidMethod(env, cls, mid);
+ POP_FRAME;
+}
+
+void android_billing_service_stop() {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "billingServiceStop", "()V");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+ (*env)->CallStaticVoidMethod(env, cls, mid);
+ POP_FRAME;
+}
+
+void android_billing_buy(char *sku) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "billingBuy", "(Ljava/lang/String;)V");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ (*env)->NewStringUTF(env, sku)
+ );
+
+ POP_FRAME;
+}
+
+char *android_billing_get_purchased_items() {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+ jobject jreading;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "billingGetPurchasedItems", "()Ljava/lang/String;");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+ jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
+ const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
+ POP_FRAME;
+
+ return reading;
+}
+
+char *android_billing_get_pending_message() {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+ jobject jreading;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "billingGetPendingMessage", "()Ljava/lang/String;");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+ jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
+ const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
+ POP_FRAME;
+
+ return reading;
+}
+
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/_android_jni.c b/p4a/pythonforandroidold/recipes/android/src/android/_android_jni.c
new file mode 100644
index 0000000..8eee770
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/_android_jni.c
@@ -0,0 +1,358 @@
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
+#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
+#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
+
+void android_vibrate(double seconds) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "vibrate", "(D)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ (jdouble) seconds);
+}
+
+void android_accelerometer_enable(int enable) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "accelerometerEnable", "(Z)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ (jboolean) enable);
+}
+
+void android_wifi_scanner_enable(void){
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "enableWifiScanner", "()V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(env, cls, mid);
+}
+
+
+char * android_wifi_scan() {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+ jobject jreading;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "scanWifi", "()Ljava/lang/String;");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+ jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
+ const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
+ POP_FRAME;
+
+ return reading;
+}
+
+void android_accelerometer_reading(float *values) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+ jobject jvalues;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "accelerometerReading", "()[F");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+
+ jvalues = (*env)->CallStaticObjectMethod(env, cls, mid);
+ (*env)->GetFloatArrayRegion(env, jvalues, 0, 3, values);
+
+ POP_FRAME;
+}
+
+int android_get_dpi(void) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "getDPI", "()I");
+ aassert(mid);
+ }
+
+ return (*env)->CallStaticIntMethod(env, cls, mid);
+}
+
+void android_show_keyboard(int input_type) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "showKeyboard", "(I)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(env, cls, mid, (jint) input_type);
+}
+
+void android_hide_keyboard(void) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "hideKeyboard", "()V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(env, cls, mid);
+}
+
+char* BUILD_MANUFACTURER = NULL;
+char* BUILD_MODEL = NULL;
+char* BUILD_PRODUCT = NULL;
+char* BUILD_VERSION_RELEASE = NULL;
+
+void android_get_buildinfo() {
+ static JNIEnv *env = NULL;
+
+ if (env == NULL) {
+ jclass *cls = NULL;
+ jfieldID fid;
+ jstring sval;
+
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+
+ cls = (*env)->FindClass(env, "android/os/Build");
+
+ fid = (*env)->GetStaticFieldID(env, cls, "MANUFACTURER", "Ljava/lang/String;");
+ sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
+ BUILD_MANUFACTURER = (*env)->GetStringUTFChars(env, sval, 0);
+
+ fid = (*env)->GetStaticFieldID(env, cls, "MODEL", "Ljava/lang/String;");
+ sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
+ BUILD_MODEL = (*env)->GetStringUTFChars(env, sval, 0);
+
+ fid = (*env)->GetStaticFieldID(env, cls, "PRODUCT", "Ljava/lang/String;");
+ sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
+ BUILD_PRODUCT = (*env)->GetStringUTFChars(env, sval, 0);
+
+ cls = (*env)->FindClass(env, "android/os/Build$VERSION");
+
+ fid = (*env)->GetStaticFieldID(env, cls, "RELEASE", "Ljava/lang/String;");
+ sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
+ BUILD_VERSION_RELEASE = (*env)->GetStringUTFChars(env, sval, 0);
+ }
+}
+
+#if IS_PYGAME
+void android_activate_input(void) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "activateInput", "()V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(env, cls, mid);
+}
+
+int android_checkstop(void) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "checkStop", "()I");
+ aassert(mid);
+ }
+
+ return (*env)->CallStaticIntMethod(env, cls, mid);
+}
+
+void android_ackstop(void) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "ackStop", "()I");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticIntMethod(env, cls, mid);
+}
+
+void android_action_send(char *mimeType, char *filename, char *subject, char *text, char *chooser_title) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/Action");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "send",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ aassert(mid);
+ }
+
+ jstring j_mimeType = (*env)->NewStringUTF(env, mimeType);
+ jstring j_filename = NULL;
+ jstring j_subject = NULL;
+ jstring j_text = NULL;
+ jstring j_chooser_title = NULL;
+ if ( filename != NULL )
+ j_filename = (*env)->NewStringUTF(env, filename);
+ if ( subject != NULL )
+ j_subject = (*env)->NewStringUTF(env, subject);
+ if ( text != NULL )
+ j_text = (*env)->NewStringUTF(env, text);
+ if ( chooser_title != NULL )
+ j_chooser_title = (*env)->NewStringUTF(env, text);
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ j_mimeType, j_filename, j_subject, j_text,
+ j_chooser_title);
+}
+
+void android_open_url(char *url) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "openUrl", "(Ljava/lang/String;)V");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ (*env)->NewStringUTF(env, url)
+ );
+
+ POP_FRAME;
+}
+#endif // IS_PYGAME
+
+void android_start_service(char *title, char *description, char *arg) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "start_service",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ aassert(mid);
+ }
+
+ jstring j_title = NULL;
+ jstring j_description = NULL;
+ jstring j_arg = NULL;
+ if ( title != 0 )
+ j_title = (*env)->NewStringUTF(env, title);
+ if ( description != 0 )
+ j_description = (*env)->NewStringUTF(env, description);
+ if ( arg != 0 )
+ j_arg = (*env)->NewStringUTF(env, arg);
+
+ (*env)->CallStaticVoidMethod(env, cls, mid, j_title, j_description, j_arg);
+}
+
+void android_stop_service() {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "stop_service", "()V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(env, cls, mid);
+}
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/_android_sound.pyx b/p4a/pythonforandroidold/recipes/android/src/android/_android_sound.pyx
new file mode 100644
index 0000000..9a486eb
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/_android_sound.pyx
@@ -0,0 +1,125 @@
+cdef extern void android_sound_queue(int, char *, char *, long long, long long)
+cdef extern void android_sound_play(int, char *, char *, long long, long long)
+cdef extern void android_sound_stop(int)
+cdef extern void android_sound_seek(int, float)
+cdef extern void android_sound_dequeue(int)
+cdef extern void android_sound_playing_name(int, char *, int)
+cdef extern void android_sound_pause(int)
+cdef extern void android_sound_unpause(int)
+
+cdef extern void android_sound_set_volume(int, float)
+cdef extern void android_sound_set_secondary_volume(int, float)
+cdef extern void android_sound_set_pan(int, float)
+
+cdef extern int android_sound_queue_depth(int)
+cdef extern int android_sound_get_pos(int)
+cdef extern int android_sound_get_length(int)
+
+channels = set()
+volumes = { }
+
+def queue(channel, file, name, fadein=0, tight=False):
+
+ channels.add(channel)
+
+ real_fn = file.name
+ base = getattr(file, "base", -1)
+ length = getattr(file, "length", -1)
+
+ android_sound_queue(channel, name, real_fn, base, length)
+
+def play(channel, file, name, paused=False, fadein=0, tight=False):
+
+ channels.add(channel)
+
+ real_fn = file.name
+ base = getattr(file, "base", -1)
+ length = getattr(file, "length", -1)
+
+ android_sound_play(channel, name, real_fn, base, length)
+
+def seek(channel, position):
+ android_sound_seek(channel, position)
+
+def stop(channel):
+ android_sound_stop(channel)
+
+def dequeue(channel, even_tight=False):
+ android_sound_dequeue(channel)
+
+def queue_depth(channel):
+ return android_sound_queue_depth(channel)
+
+def playing_name(channel):
+ cdef char buf[1024]
+
+ android_sound_playing_name(channel, buf, 1024)
+
+ rv = buf
+ if not len(rv):
+ return None
+ return rv
+
+def pause(channel):
+ android_sound_pause(channel)
+ return
+
+def unpause(channel):
+ android_sound_unpause(channel)
+ return
+
+def unpause_all():
+ for i in channels:
+ unpause(i)
+
+def pause_all():
+ for i in channels:
+ pause(i)
+
+def fadeout(channel, ms):
+ stop(channel)
+
+def busy(channel):
+ return playing_name(channel) != None
+
+def get_pos(channel):
+ return android_sound_get_pos(channel)
+
+def get_length(channel):
+ return android_sound_get_length(channel)
+
+def set_volume(channel, volume):
+ android_sound_set_volume(channel, volume)
+ volumes[channel] = volume
+
+def set_secondary_volume(channel, volume):
+ android_sound_set_secondary_volume(channel, volume)
+
+def set_pan(channel, pan):
+ android_sound_set_pan(channel, pan)
+
+def set_end_event(channel, event):
+ return
+
+def get_volume(channel):
+ return volumes.get(channel, 1.0)
+
+def init(freq, stereo, samples, status=False):
+ return
+
+def quit():
+ for i in channels:
+ stop(i)
+
+def periodic():
+ return
+
+def alloc_event(surf):
+ return
+
+def refresh_event():
+ return
+
+def check_version(version):
+ return
+
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/_android_sound_jni.c b/p4a/pythonforandroidold/recipes/android/src/android/_android_sound_jni.c
new file mode 100644
index 0000000..ee6c60b
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/_android_sound_jni.c
@@ -0,0 +1,308 @@
+#include
+#include
+#include
+#include
+
+JNIEnv *SDL_ANDROID_GetJNIEnv();
+
+#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_sound_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
+#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
+#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
+
+
+void android_sound_queue(int channel, char *filename, char *real_fn, long long base, long long length) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "queue", "(ILjava/lang/String;Ljava/lang/String;JJ)V");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel,
+ (*env)->NewStringUTF(env, filename),
+ (*env)->NewStringUTF(env, real_fn),
+ (jlong) base,
+ (jlong) length);
+
+ POP_FRAME;
+}
+
+void android_sound_play(int channel, char *filename, char *real_fn, long long base, long long length) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "play", "(ILjava/lang/String;Ljava/lang/String;JJ)V");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel,
+ (*env)->NewStringUTF(env, filename),
+ (*env)->NewStringUTF(env, real_fn),
+ (jlong) base,
+ (jlong) length);
+
+ POP_FRAME;
+}
+
+void android_sound_seek(int channel, float position){
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "seek", "(IF)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel,
+ (jfloat) position);
+}
+
+void android_sound_stop(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "stop", "(I)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel);
+}
+
+void android_sound_dequeue(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "dequeue", "(I)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel);
+}
+
+int android_sound_queue_depth(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "queue_depth", "(I)I");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticIntMethod(
+ env, cls, mid,
+ channel);
+}
+
+void android_sound_playing_name(int channel, char *buf, int buflen) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ jobject s = NULL;
+ char *jbuf;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "playing_name", "(I)Ljava/lang/String;");
+ aassert(mid);
+ }
+
+ PUSH_FRAME;
+
+ s = (*env)->CallStaticObjectMethod(
+ env, cls, mid,
+ channel);
+
+ jbuf = (*env)->GetStringUTFChars(env, s, NULL);
+ strncpy(buf, jbuf, buflen);
+ (*env)->ReleaseStringUTFChars(env, s, jbuf);
+
+ POP_FRAME;
+}
+
+void android_sound_set_volume(int channel, float value) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "set_volume", "(IF)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel,
+ (jfloat) value);
+}
+
+void android_sound_set_secondary_volume(int channel, float value) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "set_secondary_volume", "(IF)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel,
+ (jfloat) value);
+}
+
+void android_sound_set_pan(int channel, float value) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "set_pan", "(IF)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel,
+ (jfloat) value);
+}
+
+void android_sound_pause(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "pause", "(I)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel);
+}
+
+void android_sound_unpause(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "unpause", "(I)V");
+ aassert(mid);
+ }
+
+ (*env)->CallStaticVoidMethod(
+ env, cls, mid,
+ channel);
+}
+
+int android_sound_get_pos(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "get_pos", "(I)I");
+ aassert(mid);
+ }
+
+ return (*env)->CallStaticIntMethod(
+ env, cls, mid,
+ channel);
+}
+
+int android_sound_get_length(int channel) {
+ static JNIEnv *env = NULL;
+ static jclass *cls = NULL;
+ static jmethodID mid = NULL;
+
+ if (env == NULL) {
+ env = SDL_ANDROID_GetJNIEnv();
+ aassert(env);
+ cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
+ aassert(cls);
+ mid = (*env)->GetStaticMethodID(env, cls, "get_length", "(I)I");
+ aassert(mid);
+ }
+
+ return (*env)->CallStaticIntMethod(
+ env, cls, mid,
+ channel);
+}
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/activity.py b/p4a/pythonforandroidold/recipes/android/src/android/activity.py
new file mode 100644
index 0000000..cafbbda
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/activity.py
@@ -0,0 +1,63 @@
+from jnius import PythonJavaClass, autoclass, java_method
+from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+
+_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
+
+_callbacks = {
+ 'on_new_intent': [],
+ 'on_activity_result': [],
+}
+
+
+class NewIntentListener(PythonJavaClass):
+ __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
+ __javacontext__ = 'app'
+
+ def __init__(self, callback, **kwargs):
+ super(NewIntentListener, self).__init__(**kwargs)
+ self.callback = callback
+
+ @java_method('(Landroid/content/Intent;)V')
+ def onNewIntent(self, intent):
+ self.callback(intent)
+
+
+class ActivityResultListener(PythonJavaClass):
+ __javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener']
+ __javacontext__ = 'app'
+
+ def __init__(self, callback):
+ super(ActivityResultListener, self).__init__()
+ self.callback = callback
+
+ @java_method('(IILandroid/content/Intent;)V')
+ def onActivityResult(self, requestCode, resultCode, intent):
+ self.callback(requestCode, resultCode, intent)
+
+
+def bind(**kwargs):
+ for event, callback in kwargs.items():
+ if event not in _callbacks:
+ raise Exception('Unknown {!r} event'.format(event))
+ elif event == 'on_new_intent':
+ listener = NewIntentListener(callback)
+ _activity.registerNewIntentListener(listener)
+ _callbacks[event].append(listener)
+ elif event == 'on_activity_result':
+ listener = ActivityResultListener(callback)
+ _activity.registerActivityResultListener(listener)
+ _callbacks[event].append(listener)
+
+
+def unbind(**kwargs):
+ for event, callback in kwargs.items():
+ if event not in _callbacks:
+ raise Exception('Unknown {!r} event'.format(event))
+ else:
+ for listener in _callbacks[event][:]:
+ if listener.callback == callback:
+ _callbacks[event].remove(listener)
+ if event == 'on_new_intent':
+ _activity.unregisterNewIntentListener(listener)
+ elif event == 'on_activity_result':
+ _activity.unregisterActivityResultListener(listener)
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/billing.py b/p4a/pythonforandroidold/recipes/android/src/android/billing.py
new file mode 100644
index 0000000..0ea1008
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/billing.py
@@ -0,0 +1,5 @@
+'''
+Android Billing API
+===================
+
+'''
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/broadcast.py b/p4a/pythonforandroidold/recipes/android/src/android/broadcast.py
new file mode 100644
index 0000000..cb34cd9
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/broadcast.py
@@ -0,0 +1,78 @@
+# -------------------------------------------------------------------
+# Broadcast receiver bridge
+
+from jnius import autoclass, PythonJavaClass, java_method
+from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
+
+
+class BroadcastReceiver(object):
+
+ class Callback(PythonJavaClass):
+ __javainterfaces__ = [JNI_NAMESPACE + '/GenericBroadcastReceiverCallback']
+ __javacontext__ = 'app'
+
+ def __init__(self, callback, *args, **kwargs):
+ self.callback = callback
+ PythonJavaClass.__init__(self, *args, **kwargs)
+
+ @java_method('(Landroid/content/Context;Landroid/content/Intent;)V')
+ def onReceive(self, context, intent):
+ self.callback(context, intent)
+
+ def __init__(self, callback, actions=None, categories=None):
+ super(BroadcastReceiver, self).__init__()
+ self.callback = callback
+
+ if not actions and not categories:
+ raise Exception('You need to define at least actions or categories')
+
+ def _expand_partial_name(partial_name):
+ if '.' in partial_name:
+ return partial_name # Its actually a full dotted name
+ else:
+ name = 'ACTION_{}'.format(partial_name.upper())
+ if not hasattr(Intent, name):
+ raise Exception('The intent {} doesnt exist'.format(name))
+ return getattr(Intent, name)
+
+ # resolve actions/categories first
+ Intent = autoclass('android.content.Intent')
+ resolved_actions = [_expand_partial_name(x) for x in actions or []]
+ resolved_categories = [_expand_partial_name(x) for x in categories or []]
+
+ # resolve android API
+ GenericBroadcastReceiver = autoclass(JAVA_NAMESPACE + '.GenericBroadcastReceiver')
+ IntentFilter = autoclass('android.content.IntentFilter')
+ HandlerThread = autoclass('android.os.HandlerThread')
+
+ # create a thread for handling events from the receiver
+ self.handlerthread = HandlerThread('handlerthread')
+
+ # create a listener
+ self.listener = BroadcastReceiver.Callback(self.callback)
+ self.receiver = GenericBroadcastReceiver(self.listener)
+ self.receiver_filter = IntentFilter()
+ for x in resolved_actions:
+ self.receiver_filter.addAction(x)
+ for x in resolved_categories:
+ self.receiver_filter.addCategory(x)
+
+ def start(self):
+ Handler = autoclass('android.os.Handler')
+ self.handlerthread.start()
+ self.handler = Handler(self.handlerthread.getLooper())
+ self.context.registerReceiver(
+ self.receiver, self.receiver_filter, None, self.handler)
+
+ def stop(self):
+ self.context.unregisterReceiver(self.receiver)
+ self.handlerthread.quit()
+
+ @property
+ def context(self):
+ from os import environ
+ if 'PYTHON_SERVICE_ARGUMENT' in environ:
+ PythonService = autoclass(JAVA_NAMESPACE + '.PythonService')
+ return PythonService.mService
+ PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+ return PythonActivity.mActivity
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/loadingscreen.py b/p4a/pythonforandroidold/recipes/android/src/android/loadingscreen.py
new file mode 100644
index 0000000..1dc1b67
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/loadingscreen.py
@@ -0,0 +1,7 @@
+
+from jnius import autoclass
+
+
+def hide_loading_screen():
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ python_activity.removeLoadingScreen()
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/mixer.py b/p4a/pythonforandroidold/recipes/android/src/android/mixer.py
new file mode 100644
index 0000000..334f696
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/mixer.py
@@ -0,0 +1,322 @@
+# This module is, as much a possible, a clone of the pygame
+# mixer api.
+
+import android._android_sound as sound
+import time
+import threading
+import os
+
+condition = threading.Condition()
+
+
+def periodic():
+ for i in range(0, num_channels):
+ if i in channels:
+ channels[i].periodic()
+
+
+num_channels = 8
+reserved_channels = 0
+
+
+def init(frequency=22050, size=-16, channels=2, buffer=4096):
+ return None
+
+
+def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
+ return None
+
+
+def quit():
+ stop()
+ return None
+
+
+def stop():
+ for i in range(0, num_channels):
+ sound.stop(i)
+
+
+def pause():
+ for i in range(0, num_channels):
+ sound.pause(i)
+
+
+def unpause():
+ for i in range(0, num_channels):
+ sound.unpause(i)
+
+
+def get_busy():
+ for i in range(0, num_channels):
+ if sound.busy(i):
+ return True
+
+ return False
+
+
+def fadeout(time):
+ # Fadeout doesn't work - it just immediately stops playback.
+ stop()
+
+
+# A map from channel number to Channel object.
+channels = {}
+
+
+def set_num_channels(count):
+ global num_channels
+ num_channels = count
+
+
+def get_num_channels(count):
+ return num_channels
+
+
+def set_reserved(count):
+ global reserved_channels
+ reserved_channels = count
+
+
+def find_channel(force=False):
+
+ busy = []
+
+ for i in range(reserved_channels, num_channels):
+ c = Channel(i)
+
+ if not c.get_busy():
+ return c
+
+ busy.append(c)
+
+ if not force:
+ return None
+
+ return min(busy, key=lambda x: x.play_time)
+
+
+class ChannelImpl(object):
+
+ def __init__(self, id):
+ self.id = id
+ self.loop = None
+ self.queued = None
+
+ self.play_time = time.time()
+
+ def periodic(self):
+ qd = sound.queue_depth(self.id)
+
+ if qd < 2:
+ self.queued = None
+
+ if self.loop is not None and sound.queue_depth(self.id) < 2:
+ self.queue(self.loop, loops=1)
+
+ def play(self, s, loops=0, maxtime=0, fade_ms=0):
+ if loops:
+ self.loop = s
+
+ sound.play(self.id, s.file, s.serial)
+
+ self.play_time = time.time()
+
+ with condition:
+ condition.notify()
+
+ def seek(self, position):
+ sound.seek(self.id, position)
+
+ def stop(self):
+ self.loop = None
+ sound.stop(self.id)
+
+ def pause(self):
+ sound.pause(self.id)
+
+ def unpause(self):
+ sound.pause(self.id)
+
+ def fadeout(self, time):
+ # No fadeout
+ self.stop()
+
+ def set_volume(self, left, right=None):
+ sound.set_volume(self.id, left)
+
+ def get_volume(self):
+ return sound.get_volume(self.id)
+
+ def get_busy(self):
+ return sound.busy(self.id)
+
+ def get_sound(self):
+ is_busy = sound.busy(self.id)
+ if not is_busy:
+ return
+ serial = sound.playing_name(self.id)
+ if not serial:
+ return
+ return sounds.get(serial, None)
+
+ def queue(self, s):
+ self.loop = None
+ self.queued = s
+
+ sound.queue(self.id, s.what, s.serial)
+
+ with condition:
+ condition.notify()
+
+ def get_queue(self):
+ return self.queued
+
+ def get_pos(self):
+ return sound.get_pos(self.id)/1000.
+
+ def get_length(self):
+ return sound.get_length(self.id)/1000.
+
+
+def Channel(n):
+ """
+ Gets the channel with the given number.
+ """
+
+ rv = channels.get(n, None)
+ if rv is None:
+ rv = ChannelImpl(n)
+ channels[n] = rv
+
+ return rv
+
+
+sound_serial = 0
+sounds = {}
+
+
+class Sound(object):
+
+ def __init__(self, what):
+
+ # Doesn't support buffers.
+
+ global sound_serial
+
+ self._channel = None
+ self._volume = 1.
+ self.serial = str(sound_serial)
+ sound_serial += 1
+
+ if isinstance(what, file): # noqa F821
+ self.file = what
+ else:
+ self.file = file(os.path.abspath(what), "rb") # noqa F821
+
+ sounds[self.serial] = self
+
+ def play(self, loops=0, maxtime=0, fade_ms=0):
+ # avoid new play if the sound is already playing
+ # -> same behavior as standard pygame.
+ if self._channel is not None:
+ if self._channel.get_sound() is self:
+ return
+ self._channel = channel = find_channel(True)
+ channel.set_volume(self._volume)
+ channel.play(self, loops=loops)
+ return channel
+
+ def stop(self):
+ for i in range(0, num_channels):
+ if Channel(i).get_sound() is self:
+ Channel(i).stop()
+
+ def fadeout(self, time):
+ self.stop()
+
+ def set_volume(self, left, right=None):
+ self._volume = left
+ if self._channel:
+ if self._channel.get_sound() is self:
+ self._channel.set_volume(self._volume)
+
+ def get_volume(self):
+ return self._volume
+
+ def get_num_channels(self):
+ rv = 0
+
+ for i in range(0, num_channels):
+ if Channel(i).get_sound() is self:
+ rv += 1
+
+ return rv
+
+ def get_length(self):
+ return 1.0
+
+
+music_channel = Channel(256)
+music_sound = None
+
+
+class music(object):
+
+ @staticmethod
+ def load(filename):
+
+ music_channel.stop()
+
+ global music_sound
+ music_sound = Sound(filename)
+
+ @staticmethod
+ def play(loops=0, start=0.0):
+ # No start.
+
+ music_channel.play(music_sound, loops=loops)
+
+ @staticmethod
+ def rewind():
+ music_channel.play(music_sound)
+
+ @staticmethod
+ def seek(position):
+ music_channel.seek(position)
+
+ @staticmethod
+ def stop():
+ music_channel.stop()
+
+ @staticmethod
+ def pause():
+ music_channel.pause()
+
+ @staticmethod
+ def unpause():
+ music_channel.unpause()
+
+ @staticmethod
+ def fadeout(time):
+ music_channel.fadeout(time)
+
+ @staticmethod
+ def set_volume(value):
+ music_channel.set_volume(value)
+
+ @staticmethod
+ def get_volume():
+ return music_channel.get_volume()
+
+ @staticmethod
+ def get_busy():
+ return music_channel.get_busy()
+
+ @staticmethod
+ def get_pos():
+ return music_channel.get_pos()
+
+ @staticmethod
+ def queue(filename):
+ return music_channel.queue(Sound(filename))
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/permissions.py b/p4a/pythonforandroidold/recipes/android/src/android/permissions.py
new file mode 100644
index 0000000..6c2d384
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/permissions.py
@@ -0,0 +1,438 @@
+
+try:
+ from jnius import autoclass
+except ImportError:
+ # To allow importing by build/manifest-creating code without
+ # pyjnius being present:
+ def autoclass(item):
+ raise RuntimeError("pyjnius not available")
+
+
+class Permission:
+ ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
+ ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
+ ACCESS_LOCATION_EXTRA_COMMANDS = (
+ "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
+ )
+ ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE"
+ ACCESS_NOTIFICATION_POLICY = (
+ "android.permission.ACCESS_NOTIFICATION_POLICY"
+ )
+ ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE"
+ ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"
+ ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"
+ BATTERY_STATS = "android.permission.BATTERY_STATS"
+ BIND_ACCESSIBILITY_SERVICE = (
+ "android.permission.BIND_ACCESSIBILITY_SERVICE"
+ )
+ BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE"
+ BIND_CARRIER_MESSAGING_SERVICE = ( # note: deprecated in api 23+
+ "android.permission.BIND_CARRIER_MESSAGING_SERVICE"
+ )
+ BIND_CARRIER_SERVICES = ( # replaces BIND_CARRIER_MESSAGING_SERVICE
+ "android.permission.BIND_CARRIER_SERVICES"
+ )
+ BIND_CHOOSER_TARGET_SERVICE = (
+ "android.permission.BIND_CHOOSER_TARGET_SERVICE"
+ )
+ BIND_CONDITION_PROVIDER_SERVICE = (
+ "android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+ )
+ BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"
+ BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE"
+ BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"
+ BIND_INPUT_METHOD = (
+ "android.permission.BIND_INPUT_METHOD"
+ )
+ BIND_MIDI_DEVICE_SERVICE = (
+ "android.permission.BIND_MIDI_DEVICE_SERVICE"
+ )
+ BIND_NFC_SERVICE = (
+ "android.permission.BIND_NFC_SERVICE"
+ )
+ BIND_NOTIFICATION_LISTENER_SERVICE = (
+ "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ )
+ BIND_PRINT_SERVICE = (
+ "android.permission.BIND_PRINT_SERVICE"
+ )
+ BIND_QUICK_SETTINGS_TILE = (
+ "android.permission.BIND_QUICK_SETTINGS_TILE"
+ )
+ BIND_REMOTEVIEWS = (
+ "android.permission.BIND_REMOTEVIEWS"
+ )
+ BIND_SCREENING_SERVICE = (
+ "android.permission.BIND_SCREENING_SERVICE"
+ )
+ BIND_TELECOM_CONNECTION_SERVICE = (
+ "android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ )
+ BIND_TEXT_SERVICE = (
+ "android.permission.BIND_TEXT_SERVICE"
+ )
+ BIND_TV_INPUT = (
+ "android.permission.BIND_TV_INPUT"
+ )
+ BIND_VISUAL_VOICEMAIL_SERVICE = (
+ "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+ )
+ BIND_VOICE_INTERACTION = (
+ "android.permission.BIND_VOICE_INTERACTION"
+ )
+ BIND_VPN_SERVICE = (
+ "android.permission.BIND_VPN_SERVICE"
+ )
+ BIND_VR_LISTENER_SERVICE = (
+ "android.permission.BIND_VR_LISTENER_SERVICE"
+ )
+ BIND_WALLPAPER = (
+ "android.permission.BIND_WALLPAPER"
+ )
+ BLUETOOTH = (
+ "android.permission.BLUETOOTH"
+ )
+ BLUETOOTH_ADMIN = (
+ "android.permission.BLUETOOTH_ADMIN"
+ )
+ BODY_SENSORS = (
+ "android.permission.BODY_SENSORS"
+ )
+ BROADCAST_PACKAGE_REMOVED = (
+ "android.permission.BROADCAST_PACKAGE_REMOVED"
+ )
+ BROADCAST_STICKY = (
+ "android.permission.BROADCAST_STICKY"
+ )
+ CALL_PHONE = (
+ "android.permission.CALL_PHONE"
+ )
+ CALL_PRIVILEGED = (
+ "android.permission.CALL_PRIVILEGED"
+ )
+ CAMERA = (
+ "android.permission.CAMERA"
+ )
+ CAPTURE_AUDIO_OUTPUT = (
+ "android.permission.CAPTURE_AUDIO_OUTPUT"
+ )
+ CAPTURE_SECURE_VIDEO_OUTPUT = (
+ "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+ )
+ CAPTURE_VIDEO_OUTPUT = (
+ "android.permission.CAPTURE_VIDEO_OUTPUT"
+ )
+ CHANGE_COMPONENT_ENABLED_STATE = (
+ "android.permission.CHANGE_COMPONENT_ENABLED_STATE"
+ )
+ CHANGE_CONFIGURATION = (
+ "android.permission.CHANGE_CONFIGURATION"
+ )
+ CHANGE_NETWORK_STATE = (
+ "android.permission.CHANGE_NETWORK_STATE"
+ )
+ CHANGE_WIFI_MULTICAST_STATE = (
+ "android.permission.CHANGE_WIFI_MULTICAST_STATE"
+ )
+ CHANGE_WIFI_STATE = (
+ "android.permission.CHANGE_WIFI_STATE"
+ )
+ CLEAR_APP_CACHE = (
+ "android.permission.CLEAR_APP_CACHE"
+ )
+ CONTROL_LOCATION_UPDATES = (
+ "android.permission.CONTROL_LOCATION_UPDATES"
+ )
+ DELETE_CACHE_FILES = (
+ "android.permission.DELETE_CACHE_FILES"
+ )
+ DELETE_PACKAGES = (
+ "android.permission.DELETE_PACKAGES"
+ )
+ DIAGNOSTIC = (
+ "android.permission.DIAGNOSTIC"
+ )
+ DISABLE_KEYGUARD = (
+ "android.permission.DISABLE_KEYGUARD"
+ )
+ DUMP = (
+ "android.permission.DUMP"
+ )
+ EXPAND_STATUS_BAR = (
+ "android.permission.EXPAND_STATUS_BAR"
+ )
+ FACTORY_TEST = (
+ "android.permission.FACTORY_TEST"
+ )
+ FOREGROUND_SERVICE = (
+ "android.permission.FOREGROUND_SERVICE"
+ )
+ GET_ACCOUNTS = (
+ "android.permission.GET_ACCOUNTS"
+ )
+ GET_ACCOUNTS_PRIVILEGED = (
+ "android.permission.GET_ACCOUNTS_PRIVILEGED"
+ )
+ GET_PACKAGE_SIZE = (
+ "android.permission.GET_PACKAGE_SIZE"
+ )
+ GET_TASKS = (
+ "android.permission.GET_TASKS"
+ )
+ GLOBAL_SEARCH = (
+ "android.permission.GLOBAL_SEARCH"
+ )
+ INSTALL_LOCATION_PROVIDER = (
+ "android.permission.INSTALL_LOCATION_PROVIDER"
+ )
+ INSTALL_PACKAGES = (
+ "android.permission.INSTALL_PACKAGES"
+ )
+ INSTALL_SHORTCUT = (
+ "com.android.launcher.permission.INSTALL_SHORTCUT"
+ )
+ INSTANT_APP_FOREGROUND_SERVICE = (
+ "android.permission.INSTANT_APP_FOREGROUND_SERVICE"
+ )
+ INTERNET = (
+ "android.permission.INTERNET"
+ )
+ KILL_BACKGROUND_PROCESSES = (
+ "android.permission.KILL_BACKGROUND_PROCESSES"
+ )
+ LOCATION_HARDWARE = (
+ "android.permission.LOCATION_HARDWARE"
+ )
+ MANAGE_DOCUMENTS = (
+ "android.permission.MANAGE_DOCUMENTS"
+ )
+ MANAGE_OWN_CALLS = (
+ "android.permission.MANAGE_OWN_CALLS"
+ )
+ MASTER_CLEAR = (
+ "android.permission.MASTER_CLEAR"
+ )
+ MEDIA_CONTENT_CONTROL = (
+ "android.permission.MEDIA_CONTENT_CONTROL"
+ )
+ MODIFY_AUDIO_SETTINGS = (
+ "android.permission.MODIFY_AUDIO_SETTINGS"
+ )
+ MODIFY_PHONE_STATE = (
+ "android.permission.MODIFY_PHONE_STATE"
+ )
+ MOUNT_FORMAT_FILESYSTEMS = (
+ "android.permission.MOUNT_FORMAT_FILESYSTEMS"
+ )
+ MOUNT_UNMOUNT_FILESYSTEMS = (
+ "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+ )
+ NFC = (
+ "android.permission.NFC"
+ )
+ NFC_TRANSACTION_EVENT = (
+ "android.permission.NFC_TRANSACTION_EVENT"
+ )
+ PACKAGE_USAGE_STATS = (
+ "android.permission.PACKAGE_USAGE_STATS"
+ )
+ PERSISTENT_ACTIVITY = (
+ "android.permission.PERSISTENT_ACTIVITY"
+ )
+ PROCESS_OUTGOING_CALLS = (
+ "android.permission.PROCESS_OUTGOING_CALLS"
+ )
+ READ_CALENDAR = (
+ "android.permission.READ_CALENDAR"
+ )
+ READ_CALL_LOG = (
+ "android.permission.READ_CALL_LOG"
+ )
+ READ_CONTACTS = (
+ "android.permission.READ_CONTACTS"
+ )
+ READ_EXTERNAL_STORAGE = (
+ "android.permission.READ_EXTERNAL_STORAGE"
+ )
+ READ_FRAME_BUFFER = (
+ "android.permission.READ_FRAME_BUFFER"
+ )
+ READ_INPUT_STATE = (
+ "android.permission.READ_INPUT_STATE"
+ )
+ READ_LOGS = (
+ "android.permission.READ_LOGS"
+ )
+ READ_PHONE_NUMBERS = (
+ "android.permission.READ_PHONE_NUMBERS"
+ )
+ READ_PHONE_STATE = (
+ "android.permission.READ_PHONE_STATE"
+ )
+ READ_SMS = (
+ "android.permission.READ_SMS"
+ )
+ READ_SYNC_SETTINGS = (
+ "android.permission.READ_SYNC_SETTINGS"
+ )
+ READ_SYNC_STATS = (
+ "android.permission.READ_SYNC_STATS"
+ )
+ READ_VOICEMAIL = (
+ "com.android.voicemail.permission.READ_VOICEMAIL"
+ )
+ REBOOT = (
+ "android.permission.REBOOT"
+ )
+ RECEIVE_BOOT_COMPLETED = (
+ "android.permission.RECEIVE_BOOT_COMPLETED"
+ )
+ RECEIVE_MMS = (
+ "android.permission.RECEIVE_MMS"
+ )
+ RECEIVE_SMS = (
+ "android.permission.RECEIVE_SMS"
+ )
+ RECEIVE_WAP_PUSH = (
+ "android.permission.RECEIVE_WAP_PUSH"
+ )
+ RECORD_AUDIO = (
+ "android.permission.RECORD_AUDIO"
+ )
+ REORDER_TASKS = (
+ "android.permission.REORDER_TASKS"
+ )
+ REQUEST_COMPANION_RUN_IN_BACKGROUND = (
+ "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
+ )
+ REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = (
+ "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"
+ )
+ REQUEST_DELETE_PACKAGES = (
+ "android.permission.REQUEST_DELETE_PACKAGES"
+ )
+ REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = (
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
+ )
+ REQUEST_INSTALL_PACKAGES = (
+ "android.permission.REQUEST_INSTALL_PACKAGES"
+ )
+ RESTART_PACKAGES = (
+ "android.permission.RESTART_PACKAGES"
+ )
+ SEND_RESPOND_VIA_MESSAGE = (
+ "android.permission.SEND_RESPOND_VIA_MESSAGE"
+ )
+ SEND_SMS = (
+ "android.permission.SEND_SMS"
+ )
+ SET_ALARM = (
+ "com.android.alarm.permission.SET_ALARM"
+ )
+ SET_ALWAYS_FINISH = (
+ "android.permission.SET_ALWAYS_FINISH"
+ )
+ SET_ANIMATION_SCALE = (
+ "android.permission.SET_ANIMATION_SCALE"
+ )
+ SET_DEBUG_APP = (
+ "android.permission.SET_DEBUG_APP"
+ )
+ SET_PREFERRED_APPLICATIONS = (
+ "android.permission.SET_PREFERRED_APPLICATIONS"
+ )
+ SET_PROCESS_LIMIT = (
+ "android.permission.SET_PROCESS_LIMIT"
+ )
+ SET_TIME = (
+ "android.permission.SET_TIME"
+ )
+ SET_TIME_ZONE = (
+ "android.permission.SET_TIME_ZONE"
+ )
+ SET_WALLPAPER = (
+ "android.permission.SET_WALLPAPER"
+ )
+ SET_WALLPAPER_HINTS = (
+ "android.permission.SET_WALLPAPER_HINTS"
+ )
+ SIGNAL_PERSISTENT_PROCESSES = (
+ "android.permission.SIGNAL_PERSISTENT_PROCESSES"
+ )
+ STATUS_BAR = (
+ "android.permission.STATUS_BAR"
+ )
+ SYSTEM_ALERT_WINDOW = (
+ "android.permission.SYSTEM_ALERT_WINDOW"
+ )
+ TRANSMIT_IR = (
+ "android.permission.TRANSMIT_IR"
+ )
+ UNINSTALL_SHORTCUT = (
+ "com.android.launcher.permission.UNINSTALL_SHORTCUT"
+ )
+ UPDATE_DEVICE_STATS = (
+ "android.permission.UPDATE_DEVICE_STATS"
+ )
+ USE_BIOMETRIC = (
+ "android.permission.USE_BIOMETRIC"
+ )
+ USE_FINGERPRINT = (
+ "android.permission.USE_FINGERPRINT"
+ )
+ USE_SIP = (
+ "android.permission.USE_SIP"
+ )
+ VIBRATE = (
+ "android.permission.VIBRATE"
+ )
+ WAKE_LOCK = (
+ "android.permission.WAKE_LOCK"
+ )
+ WRITE_APN_SETTINGS = (
+ "android.permission.WRITE_APN_SETTINGS"
+ )
+ WRITE_CALENDAR = (
+ "android.permission.WRITE_CALENDAR"
+ )
+ WRITE_CALL_LOG = (
+ "android.permission.WRITE_CALL_LOG"
+ )
+ WRITE_CONTACTS = (
+ "android.permission.WRITE_CONTACTS"
+ )
+ WRITE_EXTERNAL_STORAGE = (
+ "android.permission.WRITE_EXTERNAL_STORAGE"
+ )
+ WRITE_GSERVICES = (
+ "android.permission.WRITE_GSERVICES"
+ )
+ WRITE_SECURE_SETTINGS = (
+ "android.permission.WRITE_SECURE_SETTINGS"
+ )
+ WRITE_SETTINGS = (
+ "android.permission.WRITE_SETTINGS"
+ )
+ WRITE_SYNC_SETTINGS = (
+ "android.permission.WRITE_SYNC_SETTINGS"
+ )
+ WRITE_VOICEMAIL = (
+ "com.android.voicemail.permission.WRITE_VOICEMAIL"
+ )
+
+
+def request_permissions(permissions):
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ python_activity.requestPermissions(permissions)
+
+
+def request_permission(permission):
+ request_permissions([permission])
+
+
+def check_permission(permission):
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ result = bool(python_activity.checkCurrentPermission(
+ permission + ""
+ ))
+ return result
diff --git a/p4a/pythonforandroidold/recipes/android/src/android/runnable.py b/p4a/pythonforandroidold/recipes/android/src/android/runnable.py
new file mode 100644
index 0000000..8d2d116
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/android/runnable.py
@@ -0,0 +1,49 @@
+'''
+Runnable
+========
+
+'''
+
+from jnius import PythonJavaClass, java_method, autoclass
+from android.config import JAVA_NAMESPACE
+
+# reference to the activity
+_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
+
+
+class Runnable(PythonJavaClass):
+ '''Wrapper around Java Runnable class. This class can be used to schedule a
+ call of a Python function into the PythonActivity thread.
+ '''
+
+ __javainterfaces__ = ['java/lang/Runnable']
+ __runnables__ = []
+
+ def __init__(self, func):
+ super(Runnable, self).__init__()
+ self.func = func
+
+ def __call__(self, *args, **kwargs):
+ self.args = args
+ self.kwargs = kwargs
+ Runnable.__runnables__.append(self)
+ _PythonActivity.mActivity.runOnUiThread(self)
+
+ @java_method('()V')
+ def run(self):
+ try:
+ self.func(*self.args, **self.kwargs)
+ except: # noqa E722
+ import traceback
+ traceback.print_exc()
+
+ Runnable.__runnables__.remove(self)
+
+
+def run_on_ui_thread(f):
+ '''Decorator to create automatically a :class:`Runnable` object with the
+ function. The function will be delayed and call into the Activity thread.
+ '''
+ def f2(*args, **kwargs):
+ Runnable(f)(*args, **kwargs)
+ return f2
diff --git a/p4a/pythonforandroidold/recipes/android/src/setup.py b/p4a/pythonforandroidold/recipes/android/src/setup.py
new file mode 100755
index 0000000..2e95a86
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/android/src/setup.py
@@ -0,0 +1,34 @@
+from distutils.core import setup, Extension
+import os
+
+library_dirs = ['libs/' + os.environ['ARCH']]
+lib_dict = {
+ 'pygame': ['sdl'],
+ 'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
+}
+sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], [])
+
+renpy_sound = Extension('android._android_sound',
+ ['android/_android_sound.c', 'android/_android_sound_jni.c', ],
+ libraries=sdl_libs + ['log'],
+ library_dirs=library_dirs)
+
+modules = [Extension('android._android',
+ ['android/_android.c', 'android/_android_jni.c'],
+ libraries=sdl_libs + ['log'],
+ library_dirs=library_dirs),
+ Extension('android._android_billing',
+ ['android/_android_billing.c', 'android/_android_billing_jni.c'],
+ libraries=['log'],
+ library_dirs=library_dirs)]
+
+if int(os.environ['IS_PYGAME']):
+ modules.append(renpy_sound)
+
+
+setup(name='android',
+ version='1.0',
+ packages=['android'],
+ package_dir={'android': 'android'},
+ ext_modules=modules
+ )
diff --git a/p4a/pythonforandroidold/recipes/apsw/__init__.py b/p4a/pythonforandroidold/recipes/apsw/__init__.py
new file mode 100644
index 0000000..6098e4b
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/apsw/__init__.py
@@ -0,0 +1,34 @@
+from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.toolchain import current_directory, shprint
+import sh
+
+
+class ApswRecipe(PythonRecipe):
+ version = '3.15.0-r1'
+ url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz'
+ depends = ['sqlite3', ('python2', 'python3'), 'setuptools']
+ call_hostpython_via_targetpython = False
+ site_packages_name = 'apsw'
+
+ def build_arch(self, arch):
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ # Build python bindings
+ hostpython = sh.Command(self.hostpython_location)
+ shprint(hostpython,
+ 'setup.py',
+ 'build_ext',
+ '--enable=fts4', _env=env)
+ # Install python bindings
+ super(ApswRecipe, self).build_arch(arch)
+
+ def get_recipe_env(self, arch):
+ env = super(ApswRecipe, self).get_recipe_env(arch)
+ sqlite_recipe = self.get_recipe('sqlite3', self.ctx)
+ env['CFLAGS'] += ' -I' + sqlite_recipe.get_build_dir(arch.arch)
+ env['LDFLAGS'] += ' -L' + sqlite_recipe.get_lib_dir(arch)
+ env['LIBS'] = env.get('LIBS', '') + ' -lsqlite3'
+ return env
+
+
+recipe = ApswRecipe()
diff --git a/p4a/pythonforandroidold/recipes/atom/__init__.py b/p4a/pythonforandroidold/recipes/atom/__init__.py
new file mode 100644
index 0000000..51923d5
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/atom/__init__.py
@@ -0,0 +1,11 @@
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class AtomRecipe(CppCompiledComponentsPythonRecipe):
+ site_packages_name = 'atom'
+ version = '0.3.10'
+ url = 'https://github.com/nucleic/atom/archive/master.zip'
+ depends = ['setuptools']
+
+
+recipe = AtomRecipe()
diff --git a/p4a/pythonforandroidold/recipes/audiostream/__init__.py b/p4a/pythonforandroidold/recipes/audiostream/__init__.py
new file mode 100644
index 0000000..4197abd
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/audiostream/__init__.py
@@ -0,0 +1,32 @@
+
+from pythonforandroid.recipe import CythonRecipe
+from os.path import join
+
+
+class AudiostreamRecipe(CythonRecipe):
+ version = 'master'
+ url = 'https://github.com/kivy/audiostream/archive/{version}.zip'
+ name = 'audiostream'
+ depends = [('python2', 'python3'), ('sdl', 'sdl2'), 'pyjnius']
+
+ def get_recipe_env(self, arch):
+ env = super(AudiostreamRecipe, self).get_recipe_env(arch)
+ if 'sdl' in self.ctx.recipe_build_order:
+ sdl_include = 'sdl'
+ sdl_mixer_include = 'sdl_mixer'
+ elif 'sdl2' in self.ctx.recipe_build_order:
+ sdl_include = 'SDL2'
+ sdl_mixer_include = 'SDL2_mixer'
+ env['USE_SDL2'] = 'True'
+ env['SDL2_INCLUDE_DIR'] = join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include')
+
+ env['CFLAGS'] += ' -I{jni_path}/{sdl_include}/include -I{jni_path}/{sdl_mixer_include}'.format(
+ jni_path=join(self.ctx.bootstrap.build_dir, 'jni'),
+ sdl_include=sdl_include,
+ sdl_mixer_include=sdl_mixer_include)
+ env['NDKPLATFORM'] = self.ctx.ndk_platform
+ env['LIBLINK'] = 'NOTNONE' # Hacky fix. Needed by audiostream setup.py
+ return env
+
+
+recipe = AudiostreamRecipe()
diff --git a/p4a/pythonforandroidold/recipes/babel/__init__.py b/p4a/pythonforandroidold/recipes/babel/__init__.py
new file mode 100644
index 0000000..fc17f8e
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/babel/__init__.py
@@ -0,0 +1,15 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class BabelRecipe(PythonRecipe):
+ name = 'babel'
+ version = '2.2.0'
+ url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz'
+
+ depends = ['setuptools', 'pytz']
+
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+
+recipe = BabelRecipe()
diff --git a/p4a/pythonforandroidold/recipes/boost/__init__.py b/p4a/pythonforandroidold/recipes/boost/__init__.py
new file mode 100644
index 0000000..53d9388
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/boost/__init__.py
@@ -0,0 +1,104 @@
+from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
+from os.path import join, exists
+from os import environ
+import sh
+
+"""
+This recipe creates a custom toolchain and bootstraps Boost from source to build Boost.Build
+including python bindings
+"""
+
+
+class BoostRecipe(Recipe):
+ # Todo: make recipe compatible with all p4a architectures
+ '''
+ .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a
+
+ .. versionchanged:: 0.6.0
+ Rewrote recipe to support clang's build. The following changes has
+ been made:
+
+ - Bumped version number to 1.68.0
+ - Better version handling for url
+ - Added python 3 compatibility
+ - Default compiler for ndk's toolchain set to clang
+ - Python version will be detected via user-config.jam
+ - Changed stl's lib from ``gnustl_shared`` to ``c++_shared``
+ '''
+ version = '1.68.0'
+ url = 'http://downloads.sourceforge.net/project/boost/' \
+ 'boost/{version}/boost_{version_underscore}.tar.bz2'
+ depends = [('python2', 'python3')]
+ patches = ['disable-so-version.patch',
+ 'use-android-libs.patch',
+ 'fix-android-issues.patch']
+
+ @property
+ def versioned_url(self):
+ if self.url is None:
+ return None
+ return self.url.format(
+ version=self.version,
+ version_underscore=self.version.replace('.', '_'))
+
+ def should_build(self, arch):
+ return not exists(join(self.get_build_dir(arch.arch), 'b2'))
+
+ def prebuild_arch(self, arch):
+ super(BoostRecipe, self).prebuild_arch(arch)
+ env = self.get_recipe_env(arch)
+ with current_directory(self.get_build_dir(arch.arch)):
+ if not exists(env['CROSSHOME']):
+ # Make custom toolchain
+ bash = sh.Command('bash')
+ shprint(bash, join(self.ctx.ndk_dir, 'build/tools/make-standalone-toolchain.sh'),
+ '--arch=' + env['ARCH'],
+ '--platform=android-' + str(self.ctx.android_api),
+ '--toolchain=' + env['CROSSHOST'] + '-' + self.ctx.toolchain_version + ':-llvm',
+ '--use-llvm',
+ '--stl=libc++',
+ '--install-dir=' + env['CROSSHOME']
+ )
+ # Set custom configuration
+ shutil.copyfile(join(self.get_recipe_dir(), 'user-config.jam'),
+ join(env['BOOST_BUILD_PATH'], 'user-config.jam'))
+
+ def build_arch(self, arch):
+ super(BoostRecipe, self).build_arch(arch)
+ env = self.get_recipe_env(arch)
+ env['PYTHON_HOST'] = self.ctx.hostpython
+ with current_directory(self.get_build_dir(arch.arch)):
+ # Compile Boost.Build engine with this custom toolchain
+ bash = sh.Command('bash')
+ shprint(bash, 'bootstrap.sh') # Do not pass env
+ # Install app stl
+ shutil.copyfile(
+ join(self.ctx.ndk_dir, 'sources/cxx-stl/llvm-libc++/libs/'
+ 'armeabi-v7a/libc++_shared.so'),
+ join(self.ctx.get_libs_dir(arch.arch), 'libc++_shared.so'))
+
+ def select_build_arch(self, arch):
+ return arch.arch.replace('eabi-v7a', '').replace('eabi', '')
+
+ def get_recipe_env(self, arch):
+ # We don't use the normal env because we
+ # are building with a standalone toolchain
+ env = environ.copy()
+
+ env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch) # find user-config.jam
+ env['BOOST_ROOT'] = env['BOOST_BUILD_PATH'] # find boost source
+
+ env['PYTHON_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
+ env['PYTHON_INCLUDE'] = self.ctx.python_recipe.include_root(arch.arch)
+ env['PYTHON_MAJOR_MINOR'] = self.ctx.python_recipe.version[:3]
+ env['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.major_minor_version_string
+ if 'python3' in self.ctx.python_recipe.name:
+ env['PYTHON_LINK_VERSION'] += 'm'
+
+ env['ARCH'] = self.select_build_arch(arch)
+ env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi'
+ env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain')
+ return env
+
+
+recipe = BoostRecipe()
diff --git a/p4a/pythonforandroidold/recipes/boost/disable-so-version.patch b/p4a/pythonforandroidold/recipes/boost/disable-so-version.patch
new file mode 100644
index 0000000..6911f89
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/boost/disable-so-version.patch
@@ -0,0 +1,12 @@
+--- boost/boostcpp.jam 2015-12-14 03:30:09.000000000 +0100
++++ boost-patch/boostcpp.jam 2016-02-08 16:38:40.510859612 +0100
+@@ -155,8 +155,9 @@
+ if $(type) = SHARED_LIB &&
+ ! [ $(property-set).get ] in windows cygwin darwin aix &&
+ ! [ $(property-set).get ] in pgi
+ {
++ return $(result) ; # disable version suffix for android
+ result = $(result).$(BOOST_VERSION) ;
+ }
+
+ return $(result) ;
diff --git a/p4a/pythonforandroidold/recipes/boost/fix-android-issues.patch b/p4a/pythonforandroidold/recipes/boost/fix-android-issues.patch
new file mode 100644
index 0000000..5413480
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/boost/fix-android-issues.patch
@@ -0,0 +1,68 @@
+diff -u -r boost_1_68_0.orig/boost/config/user.hpp boost_1_68_0/boost/config/user.hpp
+--- boost_1_68_0.orig/boost/config/user.hpp 2018-08-01 22:50:46.000000000 +0200
++++ boost_1_68_0/boost/config/user.hpp 2018-08-27 15:43:38.000000000 +0200
+@@ -13,6 +13,12 @@
+ // configuration policy:
+ //
+
++// Android defines
++// There is problem with std::atomic on android (and some other platforms).
++// See this link for more info:
++// https://code.google.com/p/android/issues/detail?id=42735#makechanges
++#define BOOST_ASIO_DISABLE_STD_ATOMIC 1
++
+ // define this to locate a compiler config file:
+ // #define BOOST_COMPILER_CONFIG
+
+diff -u -r boost_1_68_0.orig/boost/asio/detail/config.hpp boost_1_68_0/boost/asio/detail/config.hpp
+--- boost_1_68_0.orig/boost/asio/detail/config.hpp 2018-08-01 22:50:46.000000000 +0200
++++ boost_1_68_0/boost/asio/detail/config.hpp 2018-09-19 12:39:56.000000000 +0200
+@@ -804,7 +804,11 @@
+ # if defined(__clang__)
+ # if (__cplusplus >= 201402)
+ # if __has_include()
+-# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
++# if __clang_major__ >= 7
++# undef BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW
++# else
++# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
++# endif // __clang_major__ >= 7
+ # endif // __has_include()
+ # endif // (__cplusplus >= 201402)
+ # endif // defined(__clang__)
+diff -u -r boost_1_68_0.orig/boost/system/error_code.hpp boost_1_68_0/boost/system/error_code.hpp
+--- boost_1_68_0.orig/boost/system/error_code.hpp 2018-08-01 22:50:53.000000000 +0200
++++ boost_1_68_0/boost/system/error_code.hpp 2018-08-27 15:44:29.000000000 +0200
+@@ -17,6 +17,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+diff -u -r boost_1_68_0.orig/libs/filesystem/src/operations.cpp boost_1_68_0/libs/filesystem/src/operations.cpp
+--- boost_1_68_0.orig/libs/filesystem/src/operations.cpp 2018-08-01 22:50:47.000000000 +0200
++++ boost_1_68_0/libs/filesystem/src/operations.cpp 2018-08-27 15:47:15.000000000 +0200
+@@ -232,6 +232,21 @@
+
+ # if defined(BOOST_POSIX_API)
+
++# if defined(__ANDROID__)
++# define truncate libboost_truncate_wrapper
++// truncate() is present in Android libc only starting from ABI 21, so here's a simple wrapper
++static int libboost_truncate_wrapper(const char *path, off_t length)
++{
++ int fd = open(path, O_WRONLY);
++ if (fd == -1) {
++ return -1;
++ }
++ int status = ftruncate(fd, length);
++ close(fd);
++ return status;
++}
++# endif
++
+ typedef int err_t;
+
+ // POSIX uses a 0 return to indicate success
diff --git a/p4a/pythonforandroidold/recipes/boost/use-android-libs.patch b/p4a/pythonforandroidold/recipes/boost/use-android-libs.patch
new file mode 100644
index 0000000..650722d
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/boost/use-android-libs.patch
@@ -0,0 +1,10 @@
+--- boost/tools/build/src/tools/python.jam 2015-10-16 20:55:36.000000000 +0200
++++ boost-patch/tools/build/src/tools/python.jam 2016-02-09 13:16:09.519261546 +0100
+@@ -646,6 +646,7 @@
+
+ case aix : return pthread dl ;
+
++ case * : return ; # use Android builtin libs
+ case * : return pthread dl
+ gcc:util linux:util ;
+ }
diff --git a/p4a/pythonforandroidold/recipes/boost/user-config.jam b/p4a/pythonforandroidold/recipes/boost/user-config.jam
new file mode 100644
index 0000000..e50b50a
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/boost/user-config.jam
@@ -0,0 +1,61 @@
+import os ;
+
+local ARCH = [ os.environ ARCH ] ;
+local CROSSHOME = [ os.environ CROSSHOME ] ;
+local PYTHON_HOST = [ os.environ PYTHON_HOST ] ;
+local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
+local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;
+local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;
+local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;
+
+using clang : $(ARCH) : $(CROSSHOME)/bin/arm-linux-androideabi-clang++ :
+$(CROSSHOME)/bin/arm-linux-androideabi-ar
+$(CROSSHOME)/sysroot
+$(ARCH)
+-fexceptions
+-frtti
+-fpic
+-ffunction-sections
+-funwind-tables
+-march=armv7-a
+-msoft-float
+-mfpu=neon
+-mthumb
+-march=armv7-a
+-Wl,--fix-cortex-a8
+-Os
+-fomit-frame-pointer
+-fno-strict-aliasing
+-DANDROID
+-D__ANDROID__
+-DANDROID_TOOLCHAIN=clang
+-DANDROID_ABI=armv7-a
+-DANDROID_STL=c++_shared
+-DBOOST_ALL_NO_LIB
+#-DNDEBUG
+-O2
+-g
+-fvisibility=hidden
+-fvisibility-inlines-hidden
+-fdata-sections
+-D__arm__
+-D_REENTRANT
+-D_GLIBCXX__PTHREADS
+-Wno-long-long
+-Wno-missing-field-initializers
+-Wno-unused-variable
+-Wl,-z,relro
+-Wl,-z,now
+-lc++_shared
+-L$(PYTHON_ROOT)
+-lpython$(PYTHON_LINK_VERSION)
+-Wl,-O1
+-Wl,-Bsymbolic-functions
+;
+
+using python : $(PYTHON_MAJOR_MINOR)
+ : $(PYTHON_host)
+ : $(PYTHON_ROOT) $(PYTHON_INCLUDE)
+ : $(PYTHON_ROOT)/libpython$(PYTHON_LINK_VERSION).so
+ : #BOOST_ALL_DYN_LINK
+;
\ No newline at end of file
diff --git a/p4a/pythonforandroidold/recipes/brokenrecipe/__init__.py b/p4a/pythonforandroidold/recipes/brokenrecipe/__init__.py
new file mode 100644
index 0000000..48e266b
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/brokenrecipe/__init__.py
@@ -0,0 +1,9 @@
+from pythonforandroid.toolchain import Recipe
+
+
+class BrokenRecipe(Recipe):
+ def __init__(self):
+ print('This is a broken recipe, not a real one!')
+
+
+recipe = BrokenRecipe()
diff --git a/p4a/pythonforandroidold/recipes/cdecimal/__init__.py b/p4a/pythonforandroidold/recipes/cdecimal/__init__.py
new file mode 100644
index 0000000..94929c7
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cdecimal/__init__.py
@@ -0,0 +1,25 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from pythonforandroid.patching import is_darwin
+
+
+class CdecimalRecipe(CompiledComponentsPythonRecipe):
+ name = 'cdecimal'
+ version = '2.3'
+ url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-{version}.tar.gz'
+
+ depends = []
+
+ patches = ['locale.patch',
+ 'cross-compile.patch']
+
+ def prebuild_arch(self, arch):
+ super(CdecimalRecipe, self).prebuild_arch(arch)
+ if not is_darwin():
+ if '64' in arch.arch:
+ machine = 'ansi64'
+ else:
+ machine = 'ansi32'
+ self.setup_extra_args = ['--with-machine=' + machine]
+
+
+recipe = CdecimalRecipe()
diff --git a/p4a/pythonforandroidold/recipes/cdecimal/cross-compile.patch b/p4a/pythonforandroidold/recipes/cdecimal/cross-compile.patch
new file mode 100644
index 0000000..cc15f33
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cdecimal/cross-compile.patch
@@ -0,0 +1,12 @@
+diff -Naur cdecimal/setup.py b/setup.py
+--- cdecimal/setup.py 2015-12-14 13:48:23.085997956 -0600
++++ b/setup.py 2015-12-14 13:48:11.413805121 -0600
+@@ -229,7 +229,7 @@
+ def configure(machine, cc, py_size_t):
+ os.chmod("./configure", 0x1ed) # pip removes execute permissions.
+ if machine: # string has been validated.
+- os.system("./configure MACHINE=%s" % machine)
++ os.system("./configure --host=%s MACHINE=%s" % (os.environ['TOOLCHAIN_PREFIX'], machine))
+ elif 'sunos' in SYSTEM and py_size_t == 8:
+ # cc is from sysconfig.
+ os.system("./configure CC='%s -m64'" % cc)
diff --git a/p4a/pythonforandroidold/recipes/cdecimal/locale.patch b/p4a/pythonforandroidold/recipes/cdecimal/locale.patch
new file mode 100644
index 0000000..4b8df6b
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cdecimal/locale.patch
@@ -0,0 +1,172 @@
+diff -Naur a/io.c b/io.c
+--- a/io.c 2012-02-01 14:29:49.000000000 -0600
++++ b/io.c 2015-12-09 17:04:00.060579230 -0600
+@@ -34,7 +34,7 @@
+ #include
+ #include
+ #include
+-#include
++#include "locale.h"
+ #include "bits.h"
+ #include "constants.h"
+ #include "memory.h"
+@@ -792,15 +792,14 @@
+ }
+ else if (*cp == 'N' || *cp == 'n') {
+ /* locale specific conversion */
+- struct lconv *lc;
+ spec->type = *cp++;
+ /* separator has already been specified */
+ if (*spec->sep) return 0;
+ spec->type = (spec->type == 'N') ? 'G' : 'g';
+- lc = localeconv();
+- spec->dot = lc->decimal_point;
+- spec->sep = lc->thousands_sep;
+- spec->grouping = lc->grouping;
++ /* TODO: Android does not have localeconv(); we'll just use C locale values for now */
++ spec->dot = ".";
++ spec->sep = "";
++ spec->grouping = "";
+ }
+
+ /* check correctness */
+diff -Naur a/locale.h b/locale.h
+--- a/locale.h 1969-12-31 18:00:00.000000000 -0600
++++ b/locale.h 2015-12-09 17:04:11.128762784 -0600
+@@ -0,0 +1,136 @@
++/*
++ * Copyright (C) 2008 The Android Open Source Project
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * * Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ * * Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in
++ * the documentation and/or other materials provided with the
++ * distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
++ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
++ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
++ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
++ * SUCH DAMAGE.
++ */
++#ifndef _LOCALE_H_
++#define _LOCALE_H_
++
++#include
++
++__BEGIN_DECLS
++
++enum {
++ LC_CTYPE = 0,
++ LC_NUMERIC = 1,
++ LC_TIME = 2,
++ LC_COLLATE = 3,
++ LC_MONETARY = 4,
++ LC_MESSAGES = 5,
++ LC_ALL = 6,
++ LC_PAPER = 7,
++ LC_NAME = 8,
++ LC_ADDRESS = 9,
++
++ LC_TELEPHONE = 10,
++ LC_MEASUREMENT = 11,
++ LC_IDENTIFICATION = 12
++};
++
++extern char *setlocale(int category, const char *locale);
++
++#if 1 /* MISSING FROM BIONIC - DEFINED TO MAKE libstdc++-v3 happy */
++/*struct lconv { };*/
++
++__BEGIN_NAMESPACE_STD;
++
++/* Structure giving information about numeric and monetary notation. */
++struct lconv
++{
++ /* Numeric (non-monetary) information. */
++
++ char *decimal_point; /* Decimal point character. */
++ char *thousands_sep; /* Thousands separator. */
++ /* Each element is the number of digits in each group;
++ elements with higher indices are farther left.
++ An element with value CHAR_MAX means that no further grouping is done.
++ An element with value 0 means that the previous element is used
++ for all groups farther left. */
++ char *grouping;
++
++ /* Monetary information. */
++
++ /* First three chars are a currency symbol from ISO 4217.
++ Fourth char is the separator. Fifth char is '\0'. */
++ char *int_curr_symbol;
++ char *currency_symbol; /* Local currency symbol. */
++ char *mon_decimal_point; /* Decimal point character. */
++ char *mon_thousands_sep; /* Thousands separator. */
++ char *mon_grouping; /* Like `grouping' element (above). */
++ char *positive_sign; /* Sign for positive values. */
++ char *negative_sign; /* Sign for negative values. */
++ char int_frac_digits; /* Int'l fractional digits. */
++ char frac_digits; /* Local fractional digits. */
++ /* 1 if currency_symbol precedes a positive value, 0 if succeeds. */
++ char p_cs_precedes;
++ /* 1 iff a space separates currency_symbol from a positive value. */
++ char p_sep_by_space;
++ /* 1 if currency_symbol precedes a negative value, 0 if succeeds. */
++ char n_cs_precedes;
++ /* 1 iff a space separates currency_symbol from a negative value. */
++ char n_sep_by_space;
++ /* Positive and negative sign positions:
++ 0 Parentheses surround the quantity and currency_symbol.
++ 1 The sign string precedes the quantity and currency_symbol.
++ 2 The sign string follows the quantity and currency_symbol.
++ 3 The sign string immediately precedes the currency_symbol.
++ 4 The sign string immediately follows the currency_symbol. */
++ char p_sign_posn;
++ char n_sign_posn;
++#ifdef __USE_ISOC99
++ /* 1 if int_curr_symbol precedes a positive value, 0 if succeeds. */
++ char int_p_cs_precedes;
++ /* 1 iff a space separates int_curr_symbol from a positive value. */
++ char int_p_sep_by_space;
++ /* 1 if int_curr_symbol precedes a negative value, 0 if succeeds. */
++ char int_n_cs_precedes;
++ /* 1 iff a space separates int_curr_symbol from a negative value. */
++ char int_n_sep_by_space;
++ /* Positive and negative sign positions:
++ 0 Parentheses surround the quantity and int_curr_symbol.
++ 1 The sign string precedes the quantity and int_curr_symbol.
++ 2 The sign string follows the quantity and int_curr_symbol.
++ 3 The sign string immediately precedes the int_curr_symbol.
++ 4 The sign string immediately follows the int_curr_symbol. */
++ char int_p_sign_posn;
++ char int_n_sign_posn;
++#else
++ char __int_p_cs_precedes;
++ char __int_p_sep_by_space;
++ char __int_n_cs_precedes;
++ char __int_n_sep_by_space;
++ char __int_p_sign_posn;
++ char __int_n_sign_posn;
++#endif
++};
++
++__END_NAMESPACE_STD;
++
++struct lconv *localeconv(void);
++#endif /* MISSING */
++
++__END_DECLS
++
++#endif /* _LOCALE_H_ */
diff --git a/p4a/pythonforandroidold/recipes/cffi/__init__.py b/p4a/pythonforandroidold/recipes/cffi/__init__.py
new file mode 100644
index 0000000..50458e5
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cffi/__init__.py
@@ -0,0 +1,53 @@
+import os
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class CffiRecipe(CompiledComponentsPythonRecipe):
+ """
+ Extra system dependencies: autoconf, automake and libtool.
+ """
+ name = 'cffi'
+ version = '1.11.5'
+ url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
+
+ depends = ['setuptools', 'pycparser', 'libffi']
+
+ patches = ['disable-pkg-config.patch']
+
+ # call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+ def get_hostrecipe_env(self, arch=None):
+ # fixes missing ffi.h on some host systems (e.g. gentoo)
+ env = super(CffiRecipe, self).get_hostrecipe_env(arch)
+ libffi = self.get_recipe('libffi', self.ctx)
+ includes = libffi.get_include_dirs(arch)
+ env['FFI_INC'] = ",".join(includes)
+ return env
+
+ def get_recipe_env(self, arch=None):
+ env = super(CffiRecipe, self).get_recipe_env(arch)
+ libffi = self.get_recipe('libffi', self.ctx)
+ includes = libffi.get_include_dirs(arch)
+ env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)
+ env['CFLAGS'] += ' -I{}'.format(self.ctx.python_recipe.include_root(arch.arch))
+ env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +
+ self.ctx.get_libs_dir(arch.arch))
+ env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))
+ # required for libc and libdl
+ ndk_dir = self.ctx.ndk_platform
+ ndk_lib_dir = os.path.join(ndk_dir, 'usr', 'lib')
+ env['LDFLAGS'] += ' -L{}'.format(ndk_lib_dir)
+ env['LDFLAGS'] += " --sysroot={}".format(self.ctx.ndk_platform)
+ env['PYTHONPATH'] = ':'.join([
+ self.ctx.get_site_packages_dir(),
+ env['BUILDLIB_PATH'],
+ ])
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.python_recipe.link_root(arch.arch))
+ env['LDFLAGS'] += ' -lpython{}'.format(self.ctx.python_recipe.major_minor_version_string)
+ if 'python3' in self.ctx.python_recipe.name:
+ env['LDFLAGS'] += 'm'
+ return env
+
+
+recipe = CffiRecipe()
diff --git a/p4a/pythonforandroidold/recipes/cffi/disable-pkg-config.patch b/p4a/pythonforandroidold/recipes/cffi/disable-pkg-config.patch
new file mode 100644
index 0000000..cf2abd5
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cffi/disable-pkg-config.patch
@@ -0,0 +1,30 @@
+diff --git a/setup.py b/setup.py
+index c1db368..57311c3 100644
+--- a/setup.py
++++ b/setup.py
+@@ -5,8 +5,7 @@ import errno
+
+ sources = ['c/_cffi_backend.c']
+ libraries = ['ffi']
+-include_dirs = ['/usr/include/ffi',
+- '/usr/include/libffi'] # may be changed by pkg-config
++include_dirs = os.environ['FFI_INC'].split(",") if 'FFI_INC' in os.environ else []
+ define_macros = []
+ library_dirs = []
+ extra_compile_args = []
+@@ -67,14 +66,7 @@ def ask_supports_thread():
+ sys.stderr.write("The above error message can be safely ignored\n")
+
+ def use_pkg_config():
+- if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'):
+- use_homebrew_for_libffi()
+-
+- _ask_pkg_config(include_dirs, '--cflags-only-I', '-I', sysroot=True)
+- _ask_pkg_config(extra_compile_args, '--cflags-only-other')
+- _ask_pkg_config(library_dirs, '--libs-only-L', '-L', sysroot=True)
+- _ask_pkg_config(extra_link_args, '--libs-only-other')
+- _ask_pkg_config(libraries, '--libs-only-l', '-l')
++ pass
+
+ def use_homebrew_for_libffi():
+ # We can build by setting:
diff --git a/p4a/pythonforandroid/recipes/cherrypy/__init__.py b/p4a/pythonforandroidold/recipes/cherrypy/__init__.py
similarity index 100%
rename from p4a/pythonforandroid/recipes/cherrypy/__init__.py
rename to p4a/pythonforandroidold/recipes/cherrypy/__init__.py
diff --git a/p4a/pythonforandroidold/recipes/coverage/__init__.py b/p4a/pythonforandroidold/recipes/coverage/__init__.py
new file mode 100644
index 0000000..95f08f1
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/coverage/__init__.py
@@ -0,0 +1,19 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class CoverageRecipe(PythonRecipe):
+
+ version = '4.1'
+
+ url = 'https://pypi.python.org/packages/2d/10/6136c8e10644c16906edf4d9f7c782c0f2e7ed47ff2f41f067384e432088/coverage-{version}.tar.gz'
+
+ depends = [('hostpython2', 'hostpython3'), 'setuptools']
+
+ patches = ['fallback-utf8.patch']
+
+ site_packages_name = 'coverage'
+
+ call_hostpython_via_targetpython = False
+
+
+recipe = CoverageRecipe()
diff --git a/p4a/pythonforandroidold/recipes/coverage/fallback-utf8.patch b/p4a/pythonforandroidold/recipes/coverage/fallback-utf8.patch
new file mode 100644
index 0000000..6d251c4
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/coverage/fallback-utf8.patch
@@ -0,0 +1,12 @@
+--- coverage-4.1/coverage/misc.py 2016-02-13 20:04:35.000000000 +0100
++++ patch/coverage/misc.py 2016-07-11 17:07:22.656603295 +0200
+@@ -166,7 +166,8 @@
+ encoding = (
+ getattr(outfile, "encoding", None) or
+ getattr(sys.__stdout__, "encoding", None) or
+- locale.getpreferredencoding()
++ locale.getpreferredencoding() or
++ 'utf-8'
+ )
+ return encoding
+
diff --git a/p4a/pythonforandroidold/recipes/cryptography/__init__.py b/p4a/pythonforandroidold/recipes/cryptography/__init__.py
new file mode 100644
index 0000000..1b7baba
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cryptography/__init__.py
@@ -0,0 +1,23 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
+
+
+class CryptographyRecipe(CompiledComponentsPythonRecipe):
+ name = 'cryptography'
+ version = '2.6.1'
+ url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz'
+ depends = ['openssl', 'idna', 'asn1crypto', 'six', 'setuptools',
+ 'enum34', 'ipaddress', 'cffi']
+ call_hostpython_via_targetpython = False
+
+ def get_recipe_env(self, arch):
+ env = super(CryptographyRecipe, self).get_recipe_env(arch)
+
+ openssl_recipe = Recipe.get_recipe('openssl', self.ctx)
+ env['CFLAGS'] += openssl_recipe.include_flags(arch)
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = openssl_recipe.link_libs_flags()
+
+ return env
+
+
+recipe = CryptographyRecipe()
diff --git a/p4a/pythonforandroidold/recipes/cymunk/__init__.py b/p4a/pythonforandroidold/recipes/cymunk/__init__.py
new file mode 100644
index 0000000..96d4169
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/cymunk/__init__.py
@@ -0,0 +1,12 @@
+from pythonforandroid.recipe import CythonRecipe
+
+
+class CymunkRecipe(CythonRecipe):
+ version = 'master'
+ url = 'https://github.com/tito/cymunk/archive/{version}.zip'
+ name = 'cymunk'
+
+ depends = [('python2', 'python3crystax', 'python3')]
+
+
+recipe = CymunkRecipe()
diff --git a/p4a/pythonforandroidold/recipes/dateutil/__init__.py b/p4a/pythonforandroidold/recipes/dateutil/__init__.py
new file mode 100644
index 0000000..3367f8d
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/dateutil/__init__.py
@@ -0,0 +1,14 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class DateutilRecipe(PythonRecipe):
+ name = 'dateutil'
+ version = '2.6.0'
+ url = 'https://pypi.python.org/packages/51/fc/39a3fbde6864942e8bb24c93663734b74e281b984d1b8c4f95d64b0c21f6/python-dateutil-2.6.0.tar.gz'
+
+ depends = ["setuptools"]
+ call_hostpython_via_targetpython = False
+ install_in_hostpython = True
+
+
+recipe = DateutilRecipe()
diff --git a/p4a/pythonforandroidold/recipes/decorator/__init__.py b/p4a/pythonforandroidold/recipes/decorator/__init__.py
new file mode 100644
index 0000000..e1001dd
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/decorator/__init__.py
@@ -0,0 +1,13 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class DecoratorPyRecipe(PythonRecipe):
+ version = '4.2.1'
+ url = 'https://pypi.python.org/packages/source/d/decorator/decorator-{version}.tar.gz'
+ url = 'https://github.com/micheles/decorator/archive/{version}.tar.gz'
+ depends = ['setuptools']
+ site_packages_name = 'decorator'
+ call_hostpython_via_targetpython = False
+
+
+recipe = DecoratorPyRecipe()
diff --git a/p4a/pythonforandroidold/recipes/enaml/0001-Update-setup.py.patch b/p4a/pythonforandroidold/recipes/enaml/0001-Update-setup.py.patch
new file mode 100644
index 0000000..c84f892
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/enaml/0001-Update-setup.py.patch
@@ -0,0 +1,25 @@
+From 156a0426f7350bf49bdfae1aad555e13c9494b9a Mon Sep 17 00:00:00 2001
+From: frmdstryr
+Date: Thu, 23 Jun 2016 22:04:32 -0400
+Subject: [PATCH] Update setup.py
+
+---
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/setup.py b/setup.py
+index 3bfd2a2..99817e5 100644
+--- a/setup.py
++++ b/setup.py
+@@ -72,7 +72,7 @@ setup(
+ url='https://github.com/nucleic/enaml',
+ description='Declarative DSL for building rich user interfaces in Python',
+ long_description=open('README.rst').read(),
+- requires=['atom', 'PyQt', 'ply', 'kiwisolver'],
++ requires=['atom', 'ply', 'kiwisolver'],
+ install_requires=['distribute', 'atom >= 0.3.8', 'kiwisolver >= 0.1.2', 'ply >= 3.4'],
+ packages=find_packages(),
+ package_data={
+--
+2.7.4
+
diff --git a/p4a/pythonforandroidold/recipes/enaml/__init__.py b/p4a/pythonforandroidold/recipes/enaml/__init__.py
new file mode 100644
index 0000000..d233520
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/enaml/__init__.py
@@ -0,0 +1,12 @@
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
+
+
+class EnamlRecipe(CppCompiledComponentsPythonRecipe):
+ site_packages_name = 'enaml'
+ version = '0.9.8'
+ url = 'https://github.com/nucleic/enaml/archive/{version}.zip'
+ patches = ['0001-Update-setup.py.patch'] # Remove PyQt dependency
+ depends = ['setuptools', 'atom', 'kiwisolver']
+
+
+recipe = EnamlRecipe()
diff --git a/p4a/pythonforandroid/recipes/enum34/__init__.py b/p4a/pythonforandroidold/recipes/enum34/__init__.py
similarity index 100%
rename from p4a/pythonforandroid/recipes/enum34/__init__.py
rename to p4a/pythonforandroidold/recipes/enum34/__init__.py
diff --git a/p4a/pythonforandroidold/recipes/ethash/__init__.py b/p4a/pythonforandroidold/recipes/ethash/__init__.py
new file mode 100644
index 0000000..b65e10a
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/ethash/__init__.py
@@ -0,0 +1,11 @@
+from pythonforandroid.recipe import PythonRecipe
+
+
+class EthashRecipe(PythonRecipe):
+
+ url = 'https://github.com/ethereum/ethash/archive/master.zip'
+
+ depends = ['setuptools']
+
+
+recipe = EthashRecipe()
diff --git a/p4a/pythonforandroidold/recipes/evdev/__init__.py b/p4a/pythonforandroidold/recipes/evdev/__init__.py
new file mode 100644
index 0000000..afd542e
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/evdev/__init__.py
@@ -0,0 +1,25 @@
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+
+
+class EvdevRecipe(CompiledComponentsPythonRecipe):
+ name = 'evdev'
+ version = 'v0.4.7'
+ url = 'https://github.com/gvalkov/python-evdev/archive/{version}.zip'
+
+ depends = []
+
+ build_cmd = 'build'
+
+ patches = ['evcnt.patch',
+ 'keycnt.patch',
+ 'remove-uinput.patch',
+ 'include-dir.patch',
+ 'evdev-permissions.patch']
+
+ def get_recipe_env(self, arch=None):
+ env = super(EvdevRecipe, self).get_recipe_env(arch)
+ env['NDKPLATFORM'] = self.ctx.ndk_platform
+ return env
+
+
+recipe = EvdevRecipe()
diff --git a/p4a/pythonforandroidold/recipes/evdev/evcnt.patch b/p4a/pythonforandroidold/recipes/evdev/evcnt.patch
new file mode 100644
index 0000000..f140ddd
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/evdev/evcnt.patch
@@ -0,0 +1,21 @@
+diff -Naur orig/evdev/input.c v0.4.7/evdev/input.c
+--- orig/evdev/input.c 2015-06-11 13:56:43.483891914 -0500
++++ v0.4.7/evdev/input.c 2015-06-11 13:57:29.079529095 -0500
+@@ -24,6 +24,8 @@
+ #include
+ #endif
+
++#define EV_CNT (EV_MAX+1)
++
+ #define MAX_NAME_SIZE 256
+
+ extern char* EV_NAME[EV_CNT];
+@@ -190,7 +192,7 @@
+ absinfo.maximum,
+ absinfo.fuzz,
+ absinfo.flat,
+- absinfo.resolution);
++ 0);
+
+ evlong = PyLong_FromLong(ev_code);
+ absitem = Py_BuildValue("(OO)", evlong, py_absinfo);
diff --git a/p4a/pythonforandroidold/recipes/evdev/evdev-permissions.patch b/p4a/pythonforandroidold/recipes/evdev/evdev-permissions.patch
new file mode 100644
index 0000000..0faa6e7
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/evdev/evdev-permissions.patch
@@ -0,0 +1,57 @@
+diff -Naur orig/evdev/util.py v0.4.7/evdev/util.py
+--- orig/evdev/util.py 2015-06-12 16:31:46.532994729 -0500
++++ v0.4.7/evdev/util.py 2015-06-12 16:32:59.489933840 -0500
+@@ -3,15 +3,53 @@
+ import os
+ import stat
+ import glob
++import subprocess
+
+ from evdev import ecodes
+ from evdev.events import event_factory
+
+
++su = False
++
++
++def get_su_binary():
++ global su
++ if su is not False:
++ return su
++
++ su_files = ['/sbin/su', '/system/bin/su', '/system/xbin/su', '/data/local/xbin/su',
++ '/data/local/bin/su', '/system/sd/xbin/su', '/system/bin/failsafe/su',
++ '/data/local/su']
++ su = None
++
++ for fn in su_files:
++ if os.path.exists(fn):
++ try:
++ cmd = [fn, '-c', 'id']
++ output = subprocess.check_output(cmd)
++ except Exception:
++ pass
++ else:
++ if 'uid=0' in output:
++ su = fn
++ break
++
++ return su
++
++
++def fix_permissions(nodes):
++ su = get_su_binary()
++ if su:
++ cmd = 'chmod 666 ' + ' '.join(nodes)
++ print cmd
++ subprocess.check_call(['su', '-c', cmd])
++
++
+ def list_devices(input_device_dir='/dev/input'):
+ '''List readable character devices in ``input_device_dir``.'''
+
+ fns = glob.glob('{}/event*'.format(input_device_dir))
++ fix_permissions(fns)
+ fns = list(filter(is_device, fns))
+
+ return fns
diff --git a/p4a/pythonforandroidold/recipes/evdev/include-dir.patch b/p4a/pythonforandroidold/recipes/evdev/include-dir.patch
new file mode 100644
index 0000000..d6a7c81
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/evdev/include-dir.patch
@@ -0,0 +1,12 @@
+diff -Naur orig/setup.py v0.4.7/setup.py
+--- orig/setup.py 2015-06-11 14:16:31.315765908 -0500
++++ v0.4.7/setup.py 2015-06-11 14:17:05.800263536 -0500
+@@ -64,7 +64,7 @@
+
+ #-----------------------------------------------------------------------------
+ def create_ecodes():
+- header = '/usr/include/linux/input.h'
++ header = os.environ['NDKPLATFORM'] + '/usr/include/linux/input.h'
+
+ if not os.path.isfile(header):
+ msg = '''\
diff --git a/p4a/pythonforandroidold/recipes/evdev/keycnt.patch b/p4a/pythonforandroidold/recipes/evdev/keycnt.patch
new file mode 100644
index 0000000..c0f9c12
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/evdev/keycnt.patch
@@ -0,0 +1,20 @@
+diff -Naur orig/evdev/genecodes.py v0.4.7/evdev/genecodes.py
+--- orig/evdev/genecodes.py 2015-06-12 11:18:39.460538902 -0500
++++ v0.4.7/evdev/genecodes.py 2015-06-12 11:20:49.004337615 -0500
+@@ -17,6 +17,8 @@
+ #include
+ #endif
+
++#define KEY_CNT (KEY_MAX+1)
++
+ /* Automatically generated by evdev.genecodes */
+ /* Generated on %s */
+
+@@ -88,6 +88,7 @@
+ macro = regex.search(line)
+ if macro:
+ yield ' PyModule_AddIntMacro(m, %s);' % macro.group(1)
++ yield ' PyModule_AddIntMacro(m, KEY_CNT);'
+
+ uname = list(os.uname()); del uname[1]
+ uname = ' '.join(uname)
diff --git a/p4a/pythonforandroidold/recipes/evdev/remove-uinput.patch b/p4a/pythonforandroidold/recipes/evdev/remove-uinput.patch
new file mode 100644
index 0000000..82af122
--- /dev/null
+++ b/p4a/pythonforandroidold/recipes/evdev/remove-uinput.patch
@@ -0,0 +1,523 @@
+diff -Naur orig/evdev/device.py v0.4.7/evdev/device.py
+--- orig/evdev/device.py 2015-06-11 14:05:00.452884781 -0500
++++ v0.4.7/evdev/device.py 2015-06-11 14:05:47.606553546 -0500
+@@ -4,7 +4,7 @@
+ from select import select
+ from collections import namedtuple
+
+-from evdev import _input, _uinput, ecodes, util
++from evdev import _input, ecodes, util
+ from evdev.events import InputEvent
+
+
+@@ -203,7 +203,7 @@
+
+ ..
+ '''
+- _uinput.write(self.fd, ecodes.EV_LED, led_num, value)
++ pass
+
+ def __eq__(self, other):
+ '''Two devices are equal if their :data:`info` attributes are equal.'''
+diff -Naur orig/evdev/__init__.py v0.4.7/evdev/__init__.py
+--- orig/evdev/__init__.py 2015-06-11 14:05:00.452884781 -0500
++++ v0.4.7/evdev/__init__.py 2015-06-11 14:05:22.973204070 -0500
+@@ -6,7 +6,6 @@
+
+ from evdev.device import DeviceInfo, InputDevice, AbsInfo
+ from evdev.events import InputEvent, KeyEvent, RelEvent, SynEvent, AbsEvent, event_factory
+-from evdev.uinput import UInput, UInputError
+ from evdev.util import list_devices, categorize, resolve_ecodes
+ from evdev import ecodes
+ from evdev import ff
+diff -Naur orig/evdev/uinput.c v0.4.7/evdev/uinput.c
+--- orig/evdev/uinput.c 2015-06-11 14:05:00.453884795 -0500
++++ v0.4.7/evdev/uinput.c 1969-12-31 18:00:00.000000000 -0600
+@@ -1,255 +0,0 @@
+-#include
+-
+-#include
+-#include
+-#include
+-#include
+-#include
+-#include