Android: import project in native

This commit is contained in:
Yueyu 2021-04-09 13:17:05 +08:00
parent e88b25dea0
commit d77fc04f2d
4 changed files with 229 additions and 24 deletions

View file

@ -1,6 +1,8 @@
package org.scratchjr.android; package org.scratchjr.android;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.json.JSONArray; import org.json.JSONArray;
@ -14,6 +16,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
/** /**
@ -200,6 +203,27 @@ public class DatabaseManager {
return Long.toString(id); return Long.toString(id);
} }
public String insert(String table, JSONObject data) throws DatabaseException {
List<String> values = new ArrayList<>();
List<String> keys = new ArrayList<>();
List<String> placeholders = new ArrayList<>();
JSONArray names = data.names();
for (int i = 0; i < names.length(); i++) {
String key = names.optString(i);
keys.add(key);
placeholders.add("?");
values.add(data.optString(key));
}
String statement = String.format(
"INSERT INTO %s (%s) VALUES (%s)",
table,
TextUtils.join(",", keys),
TextUtils.join(",", placeholders)
);
return stmt(statement, values.toArray(new String[0]));
}
private JSONObject getRowDataAsJsonObject(Cursor cursor) private JSONObject getRowDataAsJsonObject(Cursor cursor)
throws SQLiteException, JSONException throws SQLiteException, JSONException
{ {

View file

@ -8,12 +8,16 @@ import java.io.InputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
@ -272,4 +276,98 @@ public class IOManager {
} }
return new String(hexChars); return new String(hexChars);
} }
public void receiveProject(ScratchJrActivity activity, Uri uri) throws JSONException, IOException, DatabaseException {
File tempDir = new File(activity.getCacheDir() + File.separator + UUID.randomUUID().toString());
tempDir.mkdir();
List<String> entries = ScratchJrUtil.unzip(uri.getPath(), tempDir.getPath());
if (entries.isEmpty()) {
Log.e(LOG_TAG, "no entries found");
// no files
return;
}
// read project json
JSONObject json = ScratchJrUtil.readJson(tempDir + "/project/data.json");
JSONObject projectData = json.optJSONObject("json");
JSONObject projectJson = new JSONObject();
projectJson.put("isgift", "1");
projectJson.put("deleted", "NO");
projectJson.put("json", projectData.toString());
JSONObject thumbnail = json.optJSONObject("thumbnail");
projectJson.put("thumbnail", thumbnail.toString());
projectJson.put("version", "iOSv01");
projectJson.put("name", projectData.optString("name"));
_databaseManager.insert("projects", projectJson);
HashMap<String, JSONObject> spriteMap = new HashMap<>();
JSONArray pages = projectData.optJSONArray("pages");
for (int i = 0; i < pages.length(); i++) {
String pageName = pages.optString(i);
if (pageName == null) {
continue;
}
JSONObject page = projectData.optJSONObject(pageName);
JSONArray spriteNames = page.optJSONArray("sprites");
for (int j = 0; j < spriteNames.length(); j++) {
String spriteName = spriteNames.optString(j);
JSONObject sprite = page.optJSONObject(spriteName);
spriteMap.put(sprite.optString("md5"), sprite);
}
}
for (int i = 0; i < entries.size(); i++) {
String entry = entries.get(i);
if (entry == null) {
continue;
}
if (!(entry.endsWith(".png") || entry.endsWith(".wav") || entry.endsWith(".svg"))) {
continue;
}
// copy file to target file
File sourceFile = new File(tempDir + File.separator + entry);
String fileName = sourceFile.getName();
File targetFile = new File(activity.getFilesDir().getPath() + File.separator + fileName);
if (!targetFile.exists()) {
ScratchJrUtil.copyFile(sourceFile, targetFile);
}
String folderName = sourceFile.getParentFile().getName();
if ("thumbnails".equals(folderName) || "sounds".equals(folderName)) {
continue;
}
String table = "characters".equals(folderName) ? "usershapes" : "userbkgs";
String statement = String.format("SELECT id FROM %s WHERE md5 = ?", table);
JSONArray rows = _databaseManager.query(statement, new String[]{fileName});
if (rows.length() > 0) {
Log.e(LOG_TAG, "asset for " + fileName + "exists");
continue;
}
String pngName = fileName.replace(".svg", ".png");
JSONObject asset = new JSONObject();
asset.put("version", "iOSv01");
asset.put("md5", fileName);
asset.put("altmd5", pngName);
asset.put("width", "480");
asset.put("height", "360");
asset.put("ext", fileName.split("\\.")[1]);
if ("characters".equals(folderName)) {
JSONObject sprite = spriteMap.get(fileName);
if (sprite == null) {
continue;
}
asset.put("width", sprite.optString("w"));
asset.put("height", sprite.optString("h"));
asset.put("scale", sprite.optString("scale"));
asset.put("name", sprite.optString("name"));
}
File png = new File(activity.getFilesDir().getPath() + File.separator + pngName);
if (!png.exists()) {
String js = String.format("ScratchJr.makeThumb('%s', %s, %s)", fileName, asset.optString("width"), asset.optString("height"));
Log.d(LOG_TAG, js);
activity.runJavaScript(js);
}
_databaseManager.insert(table, asset);
}
// refresh lobby
activity.runJavaScript("Lobby.refresh();");
}
} }

View file

@ -17,7 +17,6 @@ import android.os.Handler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
@ -36,10 +35,7 @@ import android.widget.RelativeLayout;
import com.google.firebase.analytics.FirebaseAnalytics; import com.google.firebase.analytics.FirebaseAnalytics;
import java.io.ByteArrayOutputStream; import java.util.ArrayList;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Vector; import java.util.Vector;
@ -110,6 +106,11 @@ public class ScratchJrActivity
/* Firebase analytics tracking */ /* Firebase analytics tracking */
private FirebaseAnalytics _FirebaseAnalytics; private FirebaseAnalytics _FirebaseAnalytics;
/**
* Project uri that need to be imported.
*/
private ArrayList<Uri> projectUris = new ArrayList<>();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -310,7 +311,15 @@ public class ScratchJrActivity
} }
} }
private void receiveProject(Uri projectUri) { private void receiveProject(final Uri projectUri) {
if (!isSplashDone()) {
projectUris.add(projectUri);
return;
}
importProject(projectUri);
}
private void importProject(final Uri projectUri) {
String PROJECT_EXTENSION = getApplicationContext().getString(R.string.share_extension_filter); String PROJECT_EXTENSION = getApplicationContext().getString(R.string.share_extension_filter);
String scheme = projectUri.getScheme(); String scheme = projectUri.getScheme();
Log.i(LOG_TAG, "receiveProject(scheme): " + scheme); Log.i(LOG_TAG, "receiveProject(scheme): " + scheme);
@ -324,25 +333,16 @@ public class ScratchJrActivity
if (scheme.equals(ContentResolver.SCHEME_FILE) && !projectUri.getPath().matches(PROJECT_EXTENSION)) { if (scheme.equals(ContentResolver.SCHEME_FILE) && !projectUri.getPath().matches(PROJECT_EXTENSION)) {
return; return;
} }
// Read the project one byte at a time into a buffer runOnUiThread(new Runnable() {
ByteArrayOutputStream projectData = new ByteArrayOutputStream(); @Override
try { public void run() {
InputStream is = getContentResolver().openInputStream(projectUri); try {
_ioManager.receiveProject(ScratchJrActivity.this, projectUri);
byte[] readByte = new byte[1]; } catch (Exception e) {
while ((is.read(readByte)) == 1) { e.printStackTrace();
projectData.write(readByte[0]); }
} }
} catch (FileNotFoundException e) { });
Log.i(LOG_TAG, "File not found in project load");
return;
} catch (IOException e) {
Log.i(LOG_TAG, "IOException in project load");
return;
}
// We send the project Base64-encoded to JavaScript where it's processed and unpacked
String base64Project = Base64.encodeToString(projectData.toByteArray(), Base64.DEFAULT);
runJavaScript("OS.loadProjectFromSjr('" + base64Project + "');");
} }
public RelativeLayout getContainer() { public RelativeLayout getContainer() {
@ -483,6 +483,10 @@ public class ScratchJrActivity
public void setSplashDone(boolean done) { public void setSplashDone(boolean done) {
_splashDone = done; _splashDone = done;
while (projectUris.size() > 0) {
Uri uri = projectUris.remove(0);
importProject(uri);
}
} }
/** /**

View file

@ -2,16 +2,22 @@ package org.scratchjr.android;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
/** /**
@ -186,4 +192,77 @@ public class ScratchJrUtil {
} }
return segments[segments.length - 1]; return segments[segments.length - 1];
} }
public static List<String> unzip(String zipPath, String toPath) {
List<String> entries = new ArrayList<>();
File zipFile = new File(zipPath);
ZipInputStream zin;
try {
zin = new ZipInputStream(new FileInputStream(zipFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
return entries;
}
try {
ZipEntry ze;
while ((ze = zin.getNextEntry()) != null) {
String path = toPath + File.separator + ze.getName();
File unzipFile = new File(path);
if (ze.isDirectory()) {
if(!unzipFile.isDirectory()) {
unzipFile.mkdirs();
}
continue;
}
File folder = unzipFile.getParentFile();
if (!folder.isDirectory()) {
folder.mkdirs();
}
FileOutputStream fout = new FileOutputStream(path, false);
BufferedOutputStream bout = new BufferedOutputStream(fout);
try {
byte[] buffer = new byte[1024];
int read;
while ((read = zin.read(buffer)) != -1) {
bout.write(buffer, 0, read);
}
bout.flush();
zin.closeEntry();
entries.add(ze.getName());
} catch (IOException e) {
e.printStackTrace();
} finally {
fout.close();
bout.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
try {
zin.close();
} catch (IOException e) {
e.printStackTrace();
}
return entries;
}
public static JSONObject readJson(String path) throws IOException, JSONException {
byte[] data;
InputStream in = new FileInputStream(new File(path));
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
data = bos.toByteArray();
} finally {
in.close();
}
return new JSONObject(new String(data));
}
} }