Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mode/languages/mode.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

menu.file.export_signed_package = Export Signed Package
menu.file.export_android_project = Export Android Project
menu.file.export_signed_bundle = Export Signed Bundle

# | File | Edit | Sketch | Android | Tools | Help |
# | Android |
Expand Down Expand Up @@ -61,8 +62,11 @@ android_editor.status.exporting_project = Exporting an Android project of the sk
android_editor.status.project_export_completed = Done with project export.
android_editor.status.project_export_failed = Error with project export.
android_editor.status.exporting_package = Exporting signed package...
android_editor.status.exporting_bundle = Exporting signed bundle...
android_editor.status.package_export_completed = Done with package export.
android_editor.status.bundle_export_completed = Done with bundle export.
android_editor.status.package_export_failed = Error with package export.
android_editor.status.bundle_export_failed = Error with bundle export.
android_editor.error.cannot_create_sketch_properties = Error While creating sketch properties file "%s": %s

# ---------------------------------------
Expand All @@ -84,8 +88,10 @@ android_mode.dialog.wallpaper_installed_body = Processing just built and install
android_mode.dialog.watchface_installed_title = Watch face installed!
android_mode.dialog.watchface_installed_body = Processing just built and installed your sketch as a watch face on the selected device.<br><br>You need to add it as a favourite watch face on the device and then select it from the watch face picker in order to run it.
android_mode.dialog.cannot_export_package_title = Cannot export package...
android_mode.dialog.cannot_export_bundle_title = Cannot export bundle...
android_mode.dialog.cannot_export_package_body = The sketch still has the default package name. Not good, since this name will uniquely identify your app on the Play store... for ever! Come up with a different package name and write in the AndroidManifest.xml file in the sketch folder, after the "package=" attribute inside the manifest tag, which also contains version code and name. Once you have done that, try exporting the sketch again.<br><br>For more info on distributing apps from Processing,<br>check <a href=\"%s\">this online tutorial</a>.
android_mode.dialog.cannot_use_default_icons_title = Cannot export package...
android_mode.dialog.cannot_use_default_icons_title_bundle = Cannot export bundle...
android_mode.dialog.cannot_use_default_icons_body = The sketch does not include all required app icons. Processing could use its default set of Android icons, which are okay to test the app on your device, but a bad idea to distribute it on the Play store. Create a full set of unique icons for your app, and copy them into the sketch folder. Once you have done that, try exporting the sketch again.<br><br>For more info on distributing apps from Processing,<br>check <a href=\"%s\">this online tutorial</a>.
android_mode.warn.cannot_load_sdk_title = Bad news...
android_mode.warn.cannot_load_sdk_body = The Android SDK could not be loaded.\nThe Android Mode will be disabled.
Expand Down
3 changes: 2 additions & 1 deletion mode/languages/mode_ko.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

menu.file.export_signed_package = 서명 된 패키지 내보내기
menu.file.export_android_project = 안드로이드 프로젝트 내보내기
menu.file.export_signed_bundle = 안드로이드 번들 내보내기

# | File | Edit | Sketch | Android | Tools | Help |
# | Android |
Expand All @@ -27,4 +28,4 @@ menu.android.ar = AR
menu.android.devices = 장치들
menu.android.devices.no_connected_devices = 연결된 기기 없음
menu.android.sdk_updater = SDK 업데이터
menu.android.reset_adb = ADB 재설정
menu.android.reset_adb = ADB 재설정
126 changes: 111 additions & 15 deletions mode/src/processing/mode/android/AndroidBuild.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,21 @@ public String getPathForAPK() {
return null;
}
return apkFile.getAbsolutePath();
}
}

/**
* Build into temporary folders for building bundles (needed for the Windows 8.3 bugs in the Android SDK)
* @param target "debug" or "release"
* @throws SketchException
* @throws IOException
*/
public File buildBundle(String target) throws IOException, SketchException {
this.target = target;
File folder = createProject(true);
if (folder == null) return null;
if (!gradleBuildBundle()) return null;
return folder;
}


/**
Expand Down Expand Up @@ -242,6 +256,44 @@ protected File createProject(boolean external)

return tmpFolder;
}

protected boolean gradleBuildBundle() throws SketchException {
ProjectConnection connection = GradleConnector.newConnector()
.forProjectDirectory(tmpFolder)
.connect();

boolean success = false;
BuildLauncher build = connection.newBuild();
build.setStandardOutput(System.out);
build.setStandardError(System.err);

try {
if (target.equals("debug")) build.forTasks("bundleDebug");
else build.forTasks("bundleRelease");
build.run();
renameAAB();
success = true;
} catch (org.gradle.tooling.UnsupportedVersionException e) {
e.printStackTrace();
success = false;
} catch (org.gradle.tooling.BuildException e) {
e.printStackTrace();
success = false;
} catch (org.gradle.tooling.BuildCancelledException e) {
e.printStackTrace();
success = false;
} catch (org.gradle.tooling.GradleConnectionException e) {
e.printStackTrace();
success = false;
} catch (Exception e) {
e.printStackTrace();
success = false;
} finally {
connection.close();
}

return success;
}


protected boolean gradleBuild() throws SketchException {
Expand Down Expand Up @@ -676,7 +728,25 @@ public File exportProject() throws IOException, SketchException {
Util.copyDir(projectFolder, exportFolder);
installGradlew(exportFolder);
return exportFolder;
}
}


// ---------------------------------------------------------------------------
// Export bundle


public File exportBundle(String keyStorePassword) throws Exception {
File projectFolder = buildBundle("release");
if (projectFolder == null) return null;

File signedPackage = signPackage(projectFolder, keyStorePassword, "aab");
if (signedPackage == null) return null;

// Final export folder
File exportFolder = createExportFolder("buildBundle");
Util.copyDir(new File(projectFolder, getPathToAAB()), exportFolder);
return exportFolder;
}


// ---------------------------------------------------------------------------
Expand All @@ -687,7 +757,7 @@ public File exportPackage(String keyStorePassword) throws Exception {
File projectFolder = build("release");
if (projectFolder == null) return null;

File signedPackage = signPackage(projectFolder, keyStorePassword);
File signedPackage = signPackage(projectFolder, keyStorePassword, "apk");
if (signedPackage == null) return null;

// Final export folder
Expand All @@ -697,36 +767,45 @@ public File exportPackage(String keyStorePassword) throws Exception {
}


private File signPackage(File projectFolder, String keyStorePassword) throws Exception {
private File signPackage(File projectFolder, String keyStorePassword, String fileExt) throws Exception {
File keyStore = AndroidKeyStore.getKeyStore();
if (keyStore == null) return null;

File unsignedPackage = new File(projectFolder,
getPathToAPK() + sketch.getName().toLowerCase() + "_release_unsigned.apk");
if (!unsignedPackage.exists()) return null;
File signedPackage = new File(projectFolder,
getPathToAPK() + sketch.getName().toLowerCase() + "_release_signed.apk");

String path=getPathToAPK();
if(fileExt.equals("aab")){
path = getPathToAAB();
}
File unsignedPackage = new File(projectFolder,
path + sketch.getName().toLowerCase() + "_release_unsigned." + fileExt);
if (!unsignedPackage.exists()) return null;
File signedPackage = new File(projectFolder,
path + sketch.getName().toLowerCase() + "_release_signed." + fileExt);


JarSigner.signJar(unsignedPackage, signedPackage,
AndroidKeyStore.ALIAS_STRING, keyStorePassword,
keyStore.getAbsolutePath(), keyStorePassword);

File alignedPackage = zipalignPackage(signedPackage, projectFolder);
if(fileExt.equals("aab")){
return signedPackage;
}
File alignedPackage = zipalignPackage(signedPackage, projectFolder, fileExt);
return alignedPackage;
}


private File zipalignPackage(File signedPackage, File projectFolder)
private File zipalignPackage(File signedPackage, File projectFolder, String fileExt)
throws IOException, InterruptedException {
File zipAlign = sdk.getZipAlignTool();
if (zipAlign == null || !zipAlign.exists()) {
Messages.showWarning(AndroidMode.getTextString("android_build.warn.cannot_find_zipalign.title"),
AndroidMode.getTextString("android_build.warn.cannot_find_zipalign.body"));
return null;
}


File alignedPackage = new File(projectFolder,
getPathToAPK() + sketch.getName().toLowerCase() + "_release_signed_aligned.apk");
getPathToAPK() + sketch.getName().toLowerCase() + "_release_signed_aligned."+fileExt);

String[] args = {
zipAlign.getAbsolutePath(), "-v", "-f", "4",
Expand Down Expand Up @@ -841,7 +920,24 @@ private void copyCodeFolder(final File libsFolder) throws IOException {
}
}
}
}
}

private void renameAAB() {
String suffix = target.equals("release") ? "release" : "debug";
String aabName = getPathToAAB() + module + "-" + suffix + ".aab";
final File aabFile = new File(tmpFolder, aabName);
if (aabFile.exists()) {
String suffixNew = target.equals("release") ? "release_unsigned" : "debug";
String aabNameNew = getPathToAAB() +
sketch.getName().toLowerCase() + "_" + suffixNew + ".aab";
final File aabFileNew = new File(tmpFolder, aabNameNew);
aabFile.renameTo(aabFileNew);
}
}

private String getPathToAAB() {
return module + "/build/outputs/bundle/" + target + "/";
}


private void renameAPK() {
Expand Down Expand Up @@ -1014,4 +1110,4 @@ static private boolean versionCheck(String currentVersion, String minVersion) {

return false;
}
}
}
56 changes: 51 additions & 5 deletions mode/src/processing/mode/android/AndroidEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,15 @@ public void actionPerformed(ActionEvent e) {
}
});

return buildFileMenu(new JMenuItem[] { exportPackage, exportProject});
String exportBundleTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT_BUNDLE, false);
JMenuItem exportBundle = Toolkit.newJMenuItem(exportBundleTitle, 'B');
exportBundle.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
handleExportBundle();
}
});

return buildFileMenu(new JMenuItem[] { exportPackage, exportProject, exportBundle});
}


Expand Down Expand Up @@ -496,15 +504,53 @@ public void run() {
}
}

/**
* Create a release bundle of the sketch
*/
public void handleExportBundle() {
if (androidMode.checkPackageName(sketch, appComponent, true) &&
androidMode.checkAppIcons(sketch, appComponent, true) && handleExportCheckModified()) {
new KeyStoreManager(this, true);
}
}

public void startExportBundle(final String keyStorePassword) {
new Thread() {
public void run() {
startIndeterminate();
statusNotice(AndroidMode.getTextString("android_editor.status.exporting_bundle"));
AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent);
try {
File projectFolder = build.exportBundle(keyStorePassword);
if (projectFolder != null) {

statusNotice(AndroidMode.getTextString("android_editor.status.bundle_export_completed"));
Platform.openFolder(projectFolder);
} else {
statusError(AndroidMode.getTextString("android_editor.status.bundle_export_failed"));
}
} catch (IOException e) {
statusError(e);
} catch (SketchException e) {
statusError(e);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
stopIndeterminate();
}
}.start();
}

/**
* Create a release build of the sketch and install its apk files on the
* attached device.
*/
public void handleExportPackage() {
if (androidMode.checkPackageName(sketch, appComponent) &&
androidMode.checkAppIcons(sketch, appComponent) && handleExportCheckModified()) {
new KeyStoreManager(this);
if (androidMode.checkPackageName(sketch, appComponent, false) &&
androidMode.checkAppIcons(sketch, appComponent, false) && handleExportCheckModified()) {
new KeyStoreManager(this, false);
}
}

Expand Down Expand Up @@ -753,4 +799,4 @@ public void actionPerformed(ActionEvent e) {
}
}
}
}
}
27 changes: 19 additions & 8 deletions mode/src/processing/mode/android/AndroidMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -326,21 +326,26 @@ public void handleStop(RunnerListener listener) {
}


public boolean checkPackageName(Sketch sketch, int comp) {
public boolean checkPackageName(Sketch sketch, int comp, boolean forBundle) {
Manifest manifest = new Manifest(sketch, comp, getFolder(), false);
String defName = Manifest.BASE_PACKAGE + "." + sketch.getName().toLowerCase();
String name = manifest.getPackageName();
if (name.toLowerCase().equals(defName.toLowerCase())) {
// The user did not set the package name, show error and stop
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_package_title"),
AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL));
if(forBundle){
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_bundle_title"),
AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL));
}else {
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_package_title"),
AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL));
}
return false;
}
return true;
}


public boolean checkAppIcons(Sketch sketch, int comp) {
public boolean checkAppIcons(Sketch sketch, int comp, boolean forBundle) {
File sketchFolder = sketch.getFolder();

File[] launcherIcons = AndroidUtil.getFileList(sketchFolder, AndroidBuild.SKETCH_LAUNCHER_ICONS,
Expand All @@ -355,9 +360,14 @@ public boolean checkAppIcons(Sketch sketch, int comp) {

if (!allFilesExist) {
// The user did not set custom icons, show error and stop
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title"),
AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL));
return false;
if(forBundle){
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title_bundle"),
AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL));
}else {
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title"),
AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL));
}
return false;
}
return true;
}
Expand Down Expand Up @@ -419,4 +429,5 @@ static public String getTextString(String key, Object... arguments) {
}
return String.format(value, arguments);
}
}
}

Loading