@@ -201,6 +201,7 @@ xmlstarlet sel -N "$NS" -t -c "/mvn:project/mvn:build/mvn:plugins" -n "$ROOT_POM
201201[ -f " $APP_DIR /build.sh" ] && chmod +x " $APP_DIR /build.sh"
202202
203203SETTINGS_FILE=" $APP_DIR /common/codenameone_settings.properties"
204+ echo " codename1.arg.android.useAndroidX=true" >> " $SETTINGS_FILE "
204205[ -f " $SETTINGS_FILE " ] || { ba_log " codenameone_settings.properties not found at $SETTINGS_FILE " >&2 ; exit 1; }
205206
206207# --- Read settings ---
@@ -266,112 +267,193 @@ if [ -z "$GRADLE_PROJECT_DIR" ]; then
266267fi
267268
268269ba_log " Configuring instrumentation test sources in $GRADLE_PROJECT_DIR "
270+
271+ # Ensure AndroidX flags in gradle.properties
272+ # --- BEGIN: robust Gradle patch for AndroidX tests ---
273+ GRADLE_PROPS=" $GRADLE_PROJECT_DIR /gradle.properties"
274+ grep -q ' ^android.useAndroidX=' " $GRADLE_PROPS " 2> /dev/null || echo ' android.useAndroidX=true' >> " $GRADLE_PROPS "
275+ grep -q ' ^android.enableJetifier=' " $GRADLE_PROPS " 2> /dev/null || echo ' android.enableJetifier=true' >> " $GRADLE_PROPS "
276+
269277APP_BUILD_GRADLE=" $GRADLE_PROJECT_DIR /app/build.gradle"
270- if [ -f " $APP_BUILD_GRADLE " ]; then
271- python3 - " $APP_BUILD_GRADLE " << 'PYTHON '
272- import pathlib
273- import re
274- import sys
275-
276- path = pathlib.Path(sys.argv[1 ])
277- text = path.read_text()
278- modified = False
279-
280- if " android.test.InstrumentationTestRunner" not in text:
281- def add_runner (match ):
282- prefix = match.group(0 )
283- return prefix + " \n testInstrumentationRunner \" android.test.InstrumentationTestRunner\" "
284-
285- new_text, count = re.subn(r " ( defaultConfig\s * \{ ) " , add_runner, text, count = 1 , flags = re.MULTILINE )
286- if count:
287- text = new_text
288- modified = True
289- else :
290- raise SystemExit (" defaultConfig block not found while adding instrumentation runner" )
278+ ROOT_BUILD_GRADLE=" $GRADLE_PROJECT_DIR /build.gradle"
291279
292- libraries = [
293- " useLibrary 'android.test.base' " ,
294- " useLibrary 'android.test.mock' " ,
295- " useLibrary 'android.test.runner' " ,
296- ]
280+ # Ensure repos in both root and app
281+ for F in " $ROOT_BUILD_GRADLE " " $APP_BUILD_GRADLE " ; do
282+ if [ -f " $F " ] ; then
283+ if ! grep -qE ' ^\s*repositories\s*{ ' " $F " ; then
284+ cat >> " $F " << ' EOS '
297285
298- missing_libraries = [lib for lib in libraries if lib not in text]
299- if missing_libraries:
300- match = re.search(r " ^ ( \s * android\s * \{ ) " , text, flags = re.MULTILINE )
301- if not match:
302- raise SystemExit (" android block not found while adding instrumentation libraries" )
303- line = match.group(1 )
304- indent = re.match(r " ^ ( \s * ) " , line).group(1 )
305- insertion = " " .join(f " \n { indent} { lib} " for lib in missing_libraries)
306- text = text[: match.end()] + insertion + text[match.end():]
307- modified = True
308-
309- if modified:
310- if not text.endswith(" \n " ):
311- text += " \n "
312- path.write_text(text)
313- PYTHON
314- ba_log " Ensured instrumentation runner and libraries are declared"
315- else
316- ba_log " Warning: Gradle build file not found at $APP_BUILD_GRADLE ; skipping instrumentation dependency configuration" >&2
317- fi
286+ repositories {
287+ google()
288+ mavenCentral()
289+ }
290+ EOS
291+ else
292+ grep -q ' google()' " $F " || sed -E -i ' 0,/repositories[[:space:]]*\{/s//repositories {\n google()\n mavenCentral()/' " $F "
293+ grep -q ' mavenCentral()' " $F " || sed -E -i ' 0,/repositories[[:space:]]*\{/s//repositories {\n google()\n mavenCentral()/' " $F "
294+ fi
295+ fi
296+ done
297+
298+ # Edit app/build.gradle
299+ python3 - " $APP_BUILD_GRADLE " << 'PY '
300+ import sys, re, pathlib
301+ p = pathlib.Path(sys.argv[1]); txt = p.read_text(); orig = txt; changed = False
302+
303+ def strip_block(name, s):
304+ return re.sub(rf'(?ms)^\s*{name}\s*\{{.*?\}}\s*', '', s)
305+
306+ module_view = strip_block('buildscript', strip_block('pluginManagement', txt))
307+
308+ # 1) android { compileSdkVersion/targetSdkVersion }
309+ def ensure_sdk(body):
310+ # If android { ... } exists, update/insert inside defaultConfig and the android block
311+ if re.search(r'(?m)^\s*android\s*\{', body):
312+ # compileSdkVersion
313+ if re.search(r'(?m)^\s*compileSdkVersion\s+\d+', body) is None:
314+ body = re.sub(r'(?m)(^\s*android\s*\{)', r'\1\n compileSdkVersion 33', body, count=1)
315+ else:
316+ body = re.sub(r'(?m)^\s*compileSdkVersion\s+\d+', ' compileSdkVersion 33', body)
317+ # targetSdkVersion
318+ if re.search(r'(?ms)^\s*defaultConfig\s*\{.*?^\s*\}', body):
319+ dc = re.search(r'(?ms)^\s*defaultConfig\s*\{.*?^\s*\}', body)
320+ block = dc.group(0)
321+ if re.search(r'(?m)^\s*targetSdkVersion\s+\d+', block):
322+ block2 = re.sub(r'(?m)^\s*targetSdkVersion\s+\d+', ' targetSdkVersion 33', block)
323+ else:
324+ block2 = re.sub(r'(\{\s*)', r'\1\n targetSdkVersion 33', block, count=1)
325+ body = body[:dc.start()] + block2 + body[dc.end():]
326+ else:
327+ body = re.sub(r'(?m)(^\s*android\s*\{)', r'\1\n defaultConfig {\n targetSdkVersion 33\n }', body, count=1)
328+ else:
329+ # No android block at all: add minimal
330+ body += '\n\nandroid {\n compileSdkVersion 33\n defaultConfig { targetSdkVersion 33 }\n}\n'
331+ return body
332+
333+ txt2 = ensure_sdk(txt)
334+ if txt2 != txt: txt = txt2; module_view = strip_block('buildscript', strip_block('pluginManagement', txt)); changed = True
335+
336+ # 2) testInstrumentationRunner -> AndroidX
337+ if "androidx.test.runner.AndroidJUnitRunner" not in module_view:
338+ t2, n = re.subn(r'(?m)^\s*testInstrumentationRunner\s*".*?"\s*$', ' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"', txt)
339+ if n == 0:
340+ t2, n = re.subn(r'(?m)(^\s*defaultConfig\s*\{)', r'\1\n testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"', txt, count=1)
341+ if n == 0:
342+ t2, n = re.subn(r'(?ms)(^\s*android\s*\{)', r'\1\n defaultConfig {\n testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"\n }', txt, count=1)
343+ if n: txt = t2; module_view = strip_block('buildscript', strip_block('pluginManagement', txt)); changed = True
344+
345+ # 3) remove legacy useLibrary lines
346+ t2, n = re.subn(r'(?m)^\s*useLibrary\s+\'android\.test\.(base|mock|runner)\'\s*$', '', txt)
347+ if n: txt = t2; module_view = strip_block('buildscript', strip_block('pluginManagement', txt)); changed = True
348+
349+ # 4) deps: choose androidTestImplementation vs androidTestCompile
350+ uses_modern = re.search(r'(?m)^\s*(implementation|api|testImplementation|androidTestImplementation)\b', module_view) is not None
351+ conf = "androidTestImplementation" if uses_modern else "androidTestCompile"
352+ need = [
353+ ("androidx.test.ext:junit:1.1.5", conf), # AndroidJUnit4
354+ ("androidx.test:runner:1.5.2", conf),
355+ ("androidx.test:core:1.5.0", conf),
356+ ("androidx.test.services:storage:1.4.2", conf),
357+ ]
358+ to_add = [(c, k) for (c, k) in need if c not in module_view]
359+
360+ if to_add:
361+ block = "\n\ndependencies {\n" + "".join([f" {k} \"{c}\"\n" for c, k in to_add]) + "}\n"
362+ txt = txt.rstrip() + block
363+ changed = True
364+
365+ if changed and txt != orig:
366+ if not txt.endswith("\n"): txt += "\n"
367+ p.write_text(txt)
368+ print(f"Patched app/build.gradle (SDK=33; deps via {conf})")
369+ else:
370+ print("No changes needed in app/build.gradle")
371+ PY
372+ # --- END: robust Gradle patch ---
373+
374+ echo " ----- app/build.gradle tail -----"
375+ tail -n 80 " $APP_BUILD_GRADLE " | sed ' s/^/| /'
376+ echo " ---------------------------------"
318377
319378TEST_SRC_DIR=" $GRADLE_PROJECT_DIR /app/src/androidTest/java/${PACKAGE_PATH} "
320379mkdir -p " $TEST_SRC_DIR "
321380TEST_CLASS=" $TEST_SRC_DIR /HelloCodenameOneInstrumentedTest.java"
322- cat > " $TEST_CLASS " << EOF
323- package $PACKAGE_NAME ;
324-
325- import android.content.Context;
326- import android.test.InstrumentationTestCase;
381+ cat > " $TEST_CLASS " << 'EOF '
382+ package @PACKAGE@;
327383
328384import android.app.Instrumentation;
385+ import android.content.Context;
329386import android.os.ParcelFileDescriptor;
330- import android.util.Base64;
331387
332- import java.io.FileInputStream;
333- import java.io.IOException;
388+ import androidx.test.ext.junit.runners.AndroidJUnit4;
389+ import androidx.test.platform.app.InstrumentationRegistry;
390+ import androidx.test.core.app.ApplicationProvider;
391+
392+ import org.junit.Test;
393+ import org.junit.runner.RunWith;
394+
334395import java.io.InputStream;
396+ import java.io.FileInputStream;
397+ import java.io.OutputStream;
398+ import java.io.File;
399+ import java.io.FileOutputStream;
400+ import java.io.ByteArrayOutputStream;
335401
336- public class HelloCodenameOneInstrumentedTest extends InstrumentationTestCase {
402+ import static org.junit.Assert.assertEquals;
337403
338- public void testUseAppContext() {
339- Context appContext = getInstrumentation().getTargetContext();
340- assertEquals("$PACKAGE_NAME ", appContext.getPackageName());
341- }
404+ @RunWith(AndroidJUnit4.class)
405+ public class HelloCodenameOneInstrumentedTest {
342406
343- private static final int CHUNK = 200_000;
407+ @Test
408+ public void testUseAppContext_andSaveScreenshot() throws Exception {
409+ String expectedPkg = "@PACKAGE@";
410+ Context ctx = ApplicationProvider.getApplicationContext();
411+ assertEquals(expectedPkg, ctx.getPackageName());
344412
345- public static void printPngToStdout(Instrumentation inst) {
346- try {
413+ // Capture PNG from emulator
414+ Instrumentation inst = InstrumentationRegistry.getInstrumentation();
347415 ParcelFileDescriptor pfd = inst.getUiAutomation().executeShellCommand("screencap -p");
348- byte[] png;
349- try (InputStream in = new FileInputStream(pfd.getFileDescriptor())) {
350- png = readAll(in);
416+ byte[] png = readAll(new FileInputStream(pfd.getFileDescriptor()));
417+
418+ // Try AndroidX TestStorage (preferred)
419+ boolean wrote = tryWriteViaTestStorage(png);
420+
421+ // Fallback: app's external files dir (host can adb pull this)
422+ if (!wrote) {
423+ File f = new File(ctx.getExternalFilesDir(null), "emulator-screenshot.png");
424+ try (FileOutputStream fos = new FileOutputStream(f)) {
425+ fos.write(png);
426+ }
427+ System.out.println("WROTE_TO_EXTERNAL_FILES:" + f.getAbsolutePath());
351428 }
352- String b64 = Base64.encodeToString(png, Base64.NO_WRAP);
353- System.out.println("<<CN1_SCREENSHOT_BEGIN>>");
354- for (int i = 0; i < b64.length(); i += CHUNK) {
355- int end = Math.min(i + CHUNK, b64.length());
356- System.out.println(b64.substring(i, end));
429+ }
430+
431+ private static boolean tryWriteViaTestStorage(byte[] bytes) {
432+ try {
433+ // Avoid direct reference to keep compile working if services lib is missing in some templates
434+ Class<?> cls = Class.forName("androidx.test.services.storage.TestStorage");
435+ java.lang.reflect.Method m = cls.getMethod("openOutputFile", String.class);
436+ try (OutputStream out = (OutputStream) m.invoke(null, "emulator-screenshot.png")) {
437+ out.write(bytes);
438+ }
439+ System.out.println("WROTE_TO_TEST_STORAGE:emulator-screenshot.png");
440+ return true;
441+ } catch (Throwable t) {
442+ System.out.println("TEST_STORAGE_UNAVAILABLE:" + t.getClass().getSimpleName());
443+ return false;
357444 }
358- System.out.println("<<CN1_SCREENSHOT_END>>");
359- System.out.flush();
360- } catch (IOException err) {
361- err.printStackTrace();
362- throw new RuntimeException(err);
363- }
364445 }
365446
366- private static byte[] readAll(InputStream in) throws IOException {
447+ private static byte[] readAll(InputStream in) throws Exception {
367448 byte[] buf = new byte[64 * 1024];
368449 int n;
369- java.io. ByteArrayOutputStream out = new java.io. ByteArrayOutputStream();
450+ ByteArrayOutputStream out = new ByteArrayOutputStream();
370451 while ((n = in.read(buf)) != -1) out.write(buf, 0, n);
371452 return out.toByteArray();
372453 }
373454}
374455EOF
456+ sed -i " s|@PACKAGE@|$PACKAGE_NAME |g" " $TEST_CLASS "
375457ba_log " Created instrumentation test at $TEST_CLASS "
376458
377459DEFAULT_ANDROID_TEST=" $GRADLE_PROJECT_DIR /app/src/androidTest/java/com/example/myapplication2/ExampleInstrumentedTest.java"
0 commit comments