Skip to content

Commit a35e6de

Browse files
authored
Merge pull request #671 from rupesh-kumar-lpu/aab
AAB(Android App Bundle) Support
2 parents b6cae00 + 26086b0 commit a35e6de

File tree

7 files changed

+208
-36
lines changed

7 files changed

+208
-36
lines changed

mode/languages/mode.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
menu.file.export_signed_package = Export Signed Package
1515
menu.file.export_android_project = Export Android Project
16+
menu.file.export_signed_bundle = Export Signed Bundle
1617

1718
# | File | Edit | Sketch | Android | Tools | Help |
1819
# | Android |
@@ -61,8 +62,11 @@ android_editor.status.exporting_project = Exporting an Android project of the sk
6162
android_editor.status.project_export_completed = Done with project export.
6263
android_editor.status.project_export_failed = Error with project export.
6364
android_editor.status.exporting_package = Exporting signed package...
65+
android_editor.status.exporting_bundle = Exporting signed bundle...
6466
android_editor.status.package_export_completed = Done with package export.
67+
android_editor.status.bundle_export_completed = Done with bundle export.
6568
android_editor.status.package_export_failed = Error with package export.
69+
android_editor.status.bundle_export_failed = Error with bundle export.
6670
android_editor.error.cannot_create_sketch_properties = Error While creating sketch properties file "%s": %s
6771

6872
# ---------------------------------------
@@ -84,8 +88,10 @@ android_mode.dialog.wallpaper_installed_body = Processing just built and install
8488
android_mode.dialog.watchface_installed_title = Watch face installed!
8589
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.
8690
android_mode.dialog.cannot_export_package_title = Cannot export package...
91+
android_mode.dialog.cannot_export_bundle_title = Cannot export bundle...
8792
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>.
8893
android_mode.dialog.cannot_use_default_icons_title = Cannot export package...
94+
android_mode.dialog.cannot_use_default_icons_title_bundle = Cannot export bundle...
8995
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>.
9096
android_mode.warn.cannot_load_sdk_title = Bad news...
9197
android_mode.warn.cannot_load_sdk_body = The Android SDK could not be loaded.\nThe Android Mode will be disabled.

mode/languages/mode_ko.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

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

1718
# | File | Edit | Sketch | Android | Tools | Help |
1819
# | Android |
@@ -27,4 +28,4 @@ menu.android.ar = AR
2728
menu.android.devices = 장치들
2829
menu.android.devices.no_connected_devices = 연결된 기기 없음
2930
menu.android.sdk_updater = SDK 업데이터
30-
menu.android.reset_adb = ADB 재설정
31+
menu.android.reset_adb = ADB 재설정

mode/src/processing/mode/android/AndroidBuild.java

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,21 @@ public String getPathForAPK() {
188188
return null;
189189
}
190190
return apkFile.getAbsolutePath();
191-
}
191+
}
192+
193+
/**
194+
* Build into temporary folders for building bundles (needed for the Windows 8.3 bugs in the Android SDK)
195+
* @param target "debug" or "release"
196+
* @throws SketchException
197+
* @throws IOException
198+
*/
199+
public File buildBundle(String target) throws IOException, SketchException {
200+
this.target = target;
201+
File folder = createProject(true);
202+
if (folder == null) return null;
203+
if (!gradleBuildBundle()) return null;
204+
return folder;
205+
}
192206

193207

194208
/**
@@ -242,6 +256,44 @@ protected File createProject(boolean external)
242256

243257
return tmpFolder;
244258
}
259+
260+
protected boolean gradleBuildBundle() throws SketchException {
261+
ProjectConnection connection = GradleConnector.newConnector()
262+
.forProjectDirectory(tmpFolder)
263+
.connect();
264+
265+
boolean success = false;
266+
BuildLauncher build = connection.newBuild();
267+
build.setStandardOutput(System.out);
268+
build.setStandardError(System.err);
269+
270+
try {
271+
if (target.equals("debug")) build.forTasks("bundleDebug");
272+
else build.forTasks("bundleRelease");
273+
build.run();
274+
renameAAB();
275+
success = true;
276+
} catch (org.gradle.tooling.UnsupportedVersionException e) {
277+
e.printStackTrace();
278+
success = false;
279+
} catch (org.gradle.tooling.BuildException e) {
280+
e.printStackTrace();
281+
success = false;
282+
} catch (org.gradle.tooling.BuildCancelledException e) {
283+
e.printStackTrace();
284+
success = false;
285+
} catch (org.gradle.tooling.GradleConnectionException e) {
286+
e.printStackTrace();
287+
success = false;
288+
} catch (Exception e) {
289+
e.printStackTrace();
290+
success = false;
291+
} finally {
292+
connection.close();
293+
}
294+
295+
return success;
296+
}
245297

246298

247299
protected boolean gradleBuild() throws SketchException {
@@ -676,7 +728,25 @@ public File exportProject() throws IOException, SketchException {
676728
Util.copyDir(projectFolder, exportFolder);
677729
installGradlew(exportFolder);
678730
return exportFolder;
679-
}
731+
}
732+
733+
734+
// ---------------------------------------------------------------------------
735+
// Export bundle
736+
737+
738+
public File exportBundle(String keyStorePassword) throws Exception {
739+
File projectFolder = buildBundle("release");
740+
if (projectFolder == null) return null;
741+
742+
File signedPackage = signPackage(projectFolder, keyStorePassword, "aab");
743+
if (signedPackage == null) return null;
744+
745+
// Final export folder
746+
File exportFolder = createExportFolder("buildBundle");
747+
Util.copyDir(new File(projectFolder, getPathToAAB()), exportFolder);
748+
return exportFolder;
749+
}
680750

681751

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

690-
File signedPackage = signPackage(projectFolder, keyStorePassword);
760+
File signedPackage = signPackage(projectFolder, keyStorePassword, "apk");
691761
if (signedPackage == null) return null;
692762

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

699769

700-
private File signPackage(File projectFolder, String keyStorePassword) throws Exception {
770+
private File signPackage(File projectFolder, String keyStorePassword, String fileExt) throws Exception {
701771
File keyStore = AndroidKeyStore.getKeyStore();
702772
if (keyStore == null) return null;
703-
704-
File unsignedPackage = new File(projectFolder,
705-
getPathToAPK() + sketch.getName().toLowerCase() + "_release_unsigned.apk");
706-
if (!unsignedPackage.exists()) return null;
707-
File signedPackage = new File(projectFolder,
708-
getPathToAPK() + sketch.getName().toLowerCase() + "_release_signed.apk");
773+
774+
String path=getPathToAPK();
775+
if(fileExt.equals("aab")){
776+
path = getPathToAAB();
777+
}
778+
File unsignedPackage = new File(projectFolder,
779+
path + sketch.getName().toLowerCase() + "_release_unsigned." + fileExt);
780+
if (!unsignedPackage.exists()) return null;
781+
File signedPackage = new File(projectFolder,
782+
path + sketch.getName().toLowerCase() + "_release_signed." + fileExt);
783+
709784

710785
JarSigner.signJar(unsignedPackage, signedPackage,
711786
AndroidKeyStore.ALIAS_STRING, keyStorePassword,
712787
keyStore.getAbsolutePath(), keyStorePassword);
713788

714-
File alignedPackage = zipalignPackage(signedPackage, projectFolder);
789+
if(fileExt.equals("aab")){
790+
return signedPackage;
791+
}
792+
File alignedPackage = zipalignPackage(signedPackage, projectFolder, fileExt);
715793
return alignedPackage;
716794
}
717795

718796

719-
private File zipalignPackage(File signedPackage, File projectFolder)
797+
private File zipalignPackage(File signedPackage, File projectFolder, String fileExt)
720798
throws IOException, InterruptedException {
721799
File zipAlign = sdk.getZipAlignTool();
722800
if (zipAlign == null || !zipAlign.exists()) {
723801
Messages.showWarning(AndroidMode.getTextString("android_build.warn.cannot_find_zipalign.title"),
724802
AndroidMode.getTextString("android_build.warn.cannot_find_zipalign.body"));
725803
return null;
726804
}
805+
727806

728807
File alignedPackage = new File(projectFolder,
729-
getPathToAPK() + sketch.getName().toLowerCase() + "_release_signed_aligned.apk");
808+
getPathToAPK() + sketch.getName().toLowerCase() + "_release_signed_aligned."+fileExt);
730809

731810
String[] args = {
732811
zipAlign.getAbsolutePath(), "-v", "-f", "4",
@@ -841,7 +920,24 @@ private void copyCodeFolder(final File libsFolder) throws IOException {
841920
}
842921
}
843922
}
844-
}
923+
}
924+
925+
private void renameAAB() {
926+
String suffix = target.equals("release") ? "release" : "debug";
927+
String aabName = getPathToAAB() + module + "-" + suffix + ".aab";
928+
final File aabFile = new File(tmpFolder, aabName);
929+
if (aabFile.exists()) {
930+
String suffixNew = target.equals("release") ? "release_unsigned" : "debug";
931+
String aabNameNew = getPathToAAB() +
932+
sketch.getName().toLowerCase() + "_" + suffixNew + ".aab";
933+
final File aabFileNew = new File(tmpFolder, aabNameNew);
934+
aabFile.renameTo(aabFileNew);
935+
}
936+
}
937+
938+
private String getPathToAAB() {
939+
return module + "/build/outputs/bundle/" + target + "/";
940+
}
845941

846942

847943
private void renameAPK() {
@@ -1014,4 +1110,4 @@ static private boolean versionCheck(String currentVersion, String minVersion) {
10141110

10151111
return false;
10161112
}
1017-
}
1113+
}

mode/src/processing/mode/android/AndroidEditor.java

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,15 @@ public void actionPerformed(ActionEvent e) {
136136
}
137137
});
138138

139-
return buildFileMenu(new JMenuItem[] { exportPackage, exportProject});
139+
String exportBundleTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT_BUNDLE, false);
140+
JMenuItem exportBundle = Toolkit.newJMenuItem(exportBundleTitle, 'B');
141+
exportBundle.addActionListener(new ActionListener() {
142+
public void actionPerformed(ActionEvent e) {
143+
handleExportBundle();
144+
}
145+
});
146+
147+
return buildFileMenu(new JMenuItem[] { exportPackage, exportProject, exportBundle});
140148
}
141149

142150

@@ -496,15 +504,53 @@ public void run() {
496504
}
497505
}
498506

507+
/**
508+
* Create a release bundle of the sketch
509+
*/
510+
public void handleExportBundle() {
511+
if (androidMode.checkPackageName(sketch, appComponent, true) &&
512+
androidMode.checkAppIcons(sketch, appComponent, true) && handleExportCheckModified()) {
513+
new KeyStoreManager(this, true);
514+
}
515+
}
516+
517+
public void startExportBundle(final String keyStorePassword) {
518+
new Thread() {
519+
public void run() {
520+
startIndeterminate();
521+
statusNotice(AndroidMode.getTextString("android_editor.status.exporting_bundle"));
522+
AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent);
523+
try {
524+
File projectFolder = build.exportBundle(keyStorePassword);
525+
if (projectFolder != null) {
526+
527+
statusNotice(AndroidMode.getTextString("android_editor.status.bundle_export_completed"));
528+
Platform.openFolder(projectFolder);
529+
} else {
530+
statusError(AndroidMode.getTextString("android_editor.status.bundle_export_failed"));
531+
}
532+
} catch (IOException e) {
533+
statusError(e);
534+
} catch (SketchException e) {
535+
statusError(e);
536+
} catch (InterruptedException e) {
537+
e.printStackTrace();
538+
} catch (Exception e) {
539+
e.printStackTrace();
540+
}
541+
stopIndeterminate();
542+
}
543+
}.start();
544+
}
499545

500546
/**
501547
* Create a release build of the sketch and install its apk files on the
502548
* attached device.
503549
*/
504550
public void handleExportPackage() {
505-
if (androidMode.checkPackageName(sketch, appComponent) &&
506-
androidMode.checkAppIcons(sketch, appComponent) && handleExportCheckModified()) {
507-
new KeyStoreManager(this);
551+
if (androidMode.checkPackageName(sketch, appComponent, false) &&
552+
androidMode.checkAppIcons(sketch, appComponent, false) && handleExportCheckModified()) {
553+
new KeyStoreManager(this, false);
508554
}
509555
}
510556

@@ -753,4 +799,4 @@ public void actionPerformed(ActionEvent e) {
753799
}
754800
}
755801
}
756-
}
802+
}

mode/src/processing/mode/android/AndroidMode.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -326,21 +326,26 @@ public void handleStop(RunnerListener listener) {
326326
}
327327

328328

329-
public boolean checkPackageName(Sketch sketch, int comp) {
329+
public boolean checkPackageName(Sketch sketch, int comp, boolean forBundle) {
330330
Manifest manifest = new Manifest(sketch, comp, getFolder(), false);
331331
String defName = Manifest.BASE_PACKAGE + "." + sketch.getName().toLowerCase();
332332
String name = manifest.getPackageName();
333333
if (name.toLowerCase().equals(defName.toLowerCase())) {
334334
// The user did not set the package name, show error and stop
335-
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_package_title"),
336-
AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL));
335+
if(forBundle){
336+
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_bundle_title"),
337+
AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL));
338+
}else {
339+
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_package_title"),
340+
AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL));
341+
}
337342
return false;
338343
}
339344
return true;
340345
}
341346

342347

343-
public boolean checkAppIcons(Sketch sketch, int comp) {
348+
public boolean checkAppIcons(Sketch sketch, int comp, boolean forBundle) {
344349
File sketchFolder = sketch.getFolder();
345350

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

356361
if (!allFilesExist) {
357362
// The user did not set custom icons, show error and stop
358-
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title"),
359-
AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL));
360-
return false;
363+
if(forBundle){
364+
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title_bundle"),
365+
AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL));
366+
}else {
367+
AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title"),
368+
AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL));
369+
}
370+
return false;
361371
}
362372
return true;
363373
}
@@ -419,4 +429,5 @@ static public String getTextString(String key, Object... arguments) {
419429
}
420430
return String.format(value, arguments);
421431
}
422-
}
432+
}
433+

0 commit comments

Comments
 (0)