Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
28 changes: 28 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ Gradle and CI do not access the network to obtain them.
| `ic_config_quick.xml` (`bolt`), `ic_config_graphics.xml` (`tune`), `ic_config_audio.xml` (`volume_up`), `ic_config_media.xml` (`image`), `ic_config_controls.xml` (`gamepad`), `ic_config_system.xml` (`settings`), `ic_play.xml` (`play_arrow`) | `https://github.com/google/material-design-icons` | outlined, fill 0, weight 400, grade 0, optical size 24 | SHA-256 `143ab3540dd135c4d7637bd2220bcb04ff76a4b0858de2851c91678161cfbfae`, `7620ffcecd323ed28d0479a615129efd1202701bc66e58dd3b8b28d2a8a5d45fb`, `cf6148478dc7965dad942a5f35ab1a613450f56f991e7e75129c2049f687a4d4`, `17313b4cd878f62bd13e21e2c96cc6d1023f23295349a3d33d94e67ecb862254`, `4ece323d53aee9d233ffd0a067353d8580ccfcee0b9a7d499584a0493de5812d`, `7f350bff4766520d4011185658a0c05e517aa2729e7b8113b8b28d2a8a5d45fb`, and `4cfe5685d7c22230c1263f85efd46dee0e06103b01402a9863c7f15ab0ea32c0`; revision `e083cc60a0828fdd3b404cea0cb8a5b900e9c23e` | Apache-2.0 |
| `ic_license.xml` | `https://github.com/google/material-design-icons` | outlined, fill 0, weight 400, grade 0, optical size 24 | SHA-256 `04c5ae7ae82289583a80181c11cff9e06e27e686813a202f9d069cb9df8108e2`; revision `e083cc60a0828fdd3b404cea0cb8a5b900e9c23e` | Apache-2.0 |

## Phosphor Icons assets

`app/src/main/res/drawable/ic_memory_editor_search.xml` and `ic_memory_editor_close.xml` are
VectorDrawable conversions of Phosphor Icons' regular `MagnifyingGlass` and `X` SVGs, pinned at
revision `3370cb1bc0a31ef3610367f3bd985462c2e201ea`. Only these required static assets are vendored.

MIT License

Copyright (c) 2023 Phosphor Icons

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

### `third_party/` audit

The current repository tree does **not** contain a `third_party/` directory. The active external native checkout is the TinySoundFont submodule at `app/src/main/cpp/mmapi_tsf/TinySoundFont`, and `.gitmodules` points to its upstream repository directly. Historical notices for `third_party/minimp3`, `third_party/stb`, Mesa-derived code, or other absent paths must not be treated as current shipped provenance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
import androidx.test.espresso.Espresso.pressBack
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import ru.playsoftware.j2meloader.R
import ru.playsoftware.j2meloader.ui.JLModPlusTheme

class RuntimeMenuComposeTest {
Expand Down Expand Up @@ -123,6 +125,27 @@ class RuntimeMenuComposeTest {
assertEquals(listOf("dismiss", "saveLog"), events)
}

@Test
fun canvasMenu_opensMemoryEditorAfterDismissal() {
val events = mutableListOf<String>()
composeRule.setContent {
JLModPlusTheme {
RuntimeMenuHost(
state = RuntimeMenuUiState(title = "MIDlet", isCanvas = true),
menuVisible = true,
actions = RecordingRuntimeMenuActions(events),
onDismissMenu = { events += "dismiss" },
)
}
}

val memoryEditorLabel = InstrumentationRegistry.getInstrumentation()
.targetContext.getString(R.string.memory_editor_bubble)
composeRule.onNodeWithText(memoryEditorLabel).performScrollTo().performClick()

assertEquals(listOf("dismiss", "memoryEditor"), events)
}

@Test
fun lockRotationToggle_dismissesBeforeDispatchingExistingCallback() {
val events = mutableListOf<String>()
Expand Down Expand Up @@ -296,6 +319,10 @@ private class RecordingRuntimeMenuActions(
events += "resetSpeed"
}

override fun onMemoryEditor() {
events += "memoryEditor"
}

override fun onEditVirtualKeyboardLayout() {
events += "edit"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 ru.playsoftware.j2meloader.memory

import android.view.MotionEvent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.dp
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import ru.playsoftware.j2meloader.R
import ru.playsoftware.j2meloader.ui.JLModPlusTheme

class MemoryEditorBubbleTest {
@get:Rule
val composeRule = createComposeRule()

@Test
fun entireBubbleReceivesTouchAndAccessibilityClick() {
val touchActions = mutableListOf<Int>()
var openCount = 0
val description = InstrumentationRegistry.getInstrumentation().targetContext
.getString(R.string.memory_editor)

composeRule.setContent {
JLModPlusTheme {
Box(Modifier.size(72.dp)) {
MemoryEditorBubble(
visible = true,
onOpen = { openCount++ },
onTouch = { event ->
touchActions += event.actionMasked
true
},
)
}
}
}

val bubble = composeRule.onNodeWithContentDescription(description)
bubble.performTouchInput {
down(Offset(1f, 1f))
up()
}
bubble.performTouchInput {
down(Offset(width.toFloat() - 1f, 1f))
up()
}
bubble.performTouchInput {
down(Offset(1f, height.toFloat() - 1f))
up()
}
bubble.performTouchInput {
down(Offset(width.toFloat() - 1f, height.toFloat() - 1f))
up()
}

composeRule.runOnIdle {
assertEquals(4, touchActions.count { it == MotionEvent.ACTION_DOWN })
assertEquals(4, touchActions.count { it == MotionEvent.ACTION_UP })
}

touchActions.clear()
bubble.performTouchInput {
down(Offset(8f, 36f))
moveTo(Offset(64f, 36f))
up()
}
composeRule.runOnIdle {
assertTrue(touchActions.contains(MotionEvent.ACTION_MOVE))
assertEquals(MotionEvent.ACTION_UP, touchActions.last())
}

bubble.performSemanticsAction(SemanticsActions.OnClick)
composeRule.runOnIdle { assertEquals(1, openCount) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package ru.playsoftware.j2meloader.memory;

import android.os.Process;
import android.os.SystemClock;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
Expand All @@ -30,6 +31,34 @@ public class MemoryTargetProbeTest {
private static final int MANAGED_PROBE_A = 0x5A17C0DE;
private static final int MANAGED_PROBE_B = 0x31C0FFEE;
private static volatile int managedProbe;
private static volatile int managedProbe01, managedProbe02, managedProbe03, managedProbe04;
private static volatile int managedProbe05, managedProbe06, managedProbe07, managedProbe08;
private static volatile int managedProbe09, managedProbe10, managedProbe11, managedProbe12;
private static volatile int managedProbe13, managedProbe14, managedProbe15, managedProbe16;
private static volatile int managedProbe17, managedProbe18, managedProbe19, managedProbe20;

private static void setManagedProbeSet(int value) {
managedProbe01 = value;
managedProbe02 = value;
managedProbe03 = value;
managedProbe04 = value;
managedProbe05 = value;
managedProbe06 = value;
managedProbe07 = value;
managedProbe08 = value;
managedProbe09 = value;
managedProbe10 = value;
managedProbe11 = value;
managedProbe12 = value;
managedProbe13 = value;
managedProbe14 = value;
managedProbe15 = value;
managedProbe16 = value;
managedProbe17 = value;
managedProbe18 = value;
managedProbe19 = value;
managedProbe20 = value;
}

@Test
public void thoroughProbeReturnsAlignedCompleteResidentRuns() {
Expand Down Expand Up @@ -91,7 +120,94 @@ public void nativeEngineFindsAndRefinesManagedArtValue() {
NativeMemoryEngine.resultCount() > 0L);
} finally {
managedProbe = 0;
NativeMemoryEngine.clear();
NativeMemoryEngine.clearTarget();
}
}

@Test
public void passiveRefreshDoesNotReplaceNextScanBaseline() {
int pageSize = NativeMemoryTarget.pageSize();
long[] runs = NativeMemoryTarget.collectResidentRuns(
MemoryEngineContract.SCOPE_JAVA_FAST, 4096);
assertNotNull(runs);
assertTrue(MemoryEngineContract.isCompleteRunList(runs));

long token = 0x4A4C4C4956455445L;
managedProbe = MANAGED_PROBE_A;
try {
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.configureTarget(
Process.myPid(), pageSize, token, runs));
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.startKnown(
MemoryEngineContract.TYPE_INT,
MemoryEngineContract.PREDICATE_EQUAL,
Integer.toString(MANAGED_PROBE_A), ""));

long[] page = NativeMemoryEngine.resultPage(0, 100);
assertNotNull(page);
int count = Math.toIntExact(page[0]);
assertTrue(count > 0);
long[] ids = new long[count];
for (int index = 0; index < count; index++) {
ids[index] = page[1 + index * MemoryEngineContract.RESULT_PAGE_STRIDE];
}

managedProbe = MANAGED_PROBE_B;
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.refresh(ids, false));
managedProbe = MANAGED_PROBE_A;
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.refineRelative(
MemoryEngineContract.PREDICATE_UNCHANGED,
MemoryEngineContract.COMPARE_PREVIOUS, "", ""));
assertTrue("passive live refresh replaced the scan baseline",
NativeMemoryEngine.resultCount() > 0L);
} finally {
managedProbe = 0;
NativeMemoryEngine.clearTarget();
}
}

@Test
public void smallCandidateRefineDoesNotRescanTheWholeHeap() {
int pageSize = NativeMemoryTarget.pageSize();
long[] runs = NativeMemoryTarget.collectResidentRuns(
MemoryEngineContract.SCOPE_JAVA_FAST, 4096);
assertNotNull(runs);
assertTrue(MemoryEngineContract.isCompleteRunList(runs));

long token = 0x4A4C464153545245L;
setManagedProbeSet(MANAGED_PROBE_A);
try {
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.configureTarget(
Process.myPid(), pageSize, token, runs));
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.startKnown(
MemoryEngineContract.TYPE_INT,
MemoryEngineContract.PREDICATE_EQUAL,
Integer.toString(MANAGED_PROBE_A), ""));
long candidateCount = NativeMemoryEngine.resultCount();
assertTrue("managed probe set was not found", candidateCount >= 20L);
assertTrue("probe value unexpectedly selected the large-set refine path",
candidateCount <= 4096L);

setManagedProbeSet(MANAGED_PROBE_B);
long started = SystemClock.elapsedRealtimeNanos();
assertEquals(MemoryEngineContract.RESULT_OK,
NativeMemoryEngine.refineKnown(
MemoryEngineContract.PREDICATE_EQUAL,
Integer.toString(MANAGED_PROBE_B), ""));
long elapsedMillis = (SystemClock.elapsedRealtimeNanos() - started) / 1_000_000L;
System.out.println("Memory refine candidates=" + candidateCount
+ " elapsedMs=" + elapsedMillis);
assertTrue("20-value refine took " + elapsedMillis + " ms",
elapsedMillis < 1_000L);
assertTrue(NativeMemoryEngine.resultCount() >= 20L);
} finally {
setManagedProbeSet(0);
NativeMemoryEngine.clearTarget();
}
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
android:theme="@style/AppTheme.NoActionBar"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout|smallestScreenSize|uiMode"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="stateHidden|adjustResize"
android:process=":midlet" />
<service
android:name=".runtime.MidletKeepAliveService"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ package ru.playsoftware.j2meloader.memory;

/** Publishes completion of an asynchronous Memory Editor engine operation. */
oneway interface IMemoryEngineCallback {
void onOperationFinished(long operationId, int resultCode, long resultCount, String message);
void onOperationFinished(long operationId, int resultCode, long resultCount, String message,
boolean passiveRefresh);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,27 @@ interface IMemoryEngineService {
long startKnownSearch(long runtimeToken, int scope, int valueType, int predicate,
String firstValue, String secondValue);
long startUnknownSearch(long runtimeToken, int scope, int valueType);
long startGroupSearch(long runtimeToken, int scope, in int[] valueTypes,
in String[] values, int maxDistance);
long refineKnown(long runtimeToken, int predicate, String firstValue, String secondValue);
long refineRelative(long runtimeToken, int predicate, int compareTarget,
String firstValue, String secondValue);
long undoSearch(long runtimeToken);
long refreshCandidates(long runtimeToken, in long[] candidateIds);
long removeCandidates(long runtimeToken, in long[] candidateIds);
long keepCandidates(long runtimeToken, in long[] candidateIds);
long editCandidates(long runtimeToken, in long[] candidateIds, String replacementValue);

long getResultCount(long runtimeToken);
// Offset/limit count unique raw addresses. Rows may include typed aliases for those addresses.
long[] getResultPage(long runtimeToken, int offset, int limit);
Bundle getWatchPage(long runtimeToken);
long addWatch(long runtimeToken, in long[] candidateIds);
long removeWatch(long runtimeToken, in long[] candidateIds);
long setWatchLabel(long runtimeToken, long candidateId, String label);
long setFreeze(long runtimeToken, in long[] candidateIds, int mode,
String firstValue, String secondValue);
long clearFreeze(long runtimeToken, in long[] candidateIds);
void clearSearch(long runtimeToken);
void cancelOperation(long runtimeToken);
}
23 changes: 23 additions & 0 deletions app/src/main/assets/licenses.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ <h6>Material Symbols</h6>
<code>THIRD_PARTY_NOTICES.md</code>.<br/>
License: Apache License 2.0.</p>

<h6>Phosphor Icons</h6>
<p>The Memory Editor search and close icons are static VectorDrawable conversions of the regular
<code>MagnifyingGlass</code> and <code>X</code> assets from
<a href="https://github.com/phosphor-icons/core">https://github.com/phosphor-icons/core</a>,
pinned at revision <code>3370cb1bc0a31ef3610367f3bd985462c2e201ea</code>.</p>
<p>MIT License</p>
<p>Copyright (c) 2023 Phosphor Icons</p>
<p>Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:</p>
<p>The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.</p>

<h5>Runtime libraries</h5>

<h6>AndroidX / Android Open Source Project</h6>
Expand Down
Loading