make Looper, Handler, and MessageQueue work properly

this for example makes Unity apps not steal the main thread,
hanging Gtk.
This commit is contained in:
Mis012
2023-07-25 14:26:29 +02:00
parent 7ac5587fca
commit 08998b0076
15 changed files with 997 additions and 158 deletions

View File

@@ -59,6 +59,7 @@ libandroid_so = shared_library('android', [
libtranslationlayer_so = shared_library('translation_layer_main', [
'src/api-impl-jni/egl/com_google_android_gles_jni_EGLImpl.c',
'src/api-impl-jni/android_os_Environment.c',
'src/api-impl-jni/android_os_MessageQueue.c',
'src/api-impl-jni/android_os_SystemClock.c',
'src/api-impl-jni/android_view_Window.c',
'src/api-impl-jni/util.c',

View File

@@ -0,0 +1,62 @@
#include "defines.h"
#include "util.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/* TODO put these in a header */
typedef void ALooper;
ALooper * ALooper_prepare(void);
void ALooper_wake(ALooper *looper);
bool ALooper_isPolling(ALooper *looper);
int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
struct native_message_queue {
ALooper *looper;
bool in_callback;
};
JNIEXPORT jlong JNICALL Java_android_os_MessageQueue_nativeInit(JNIEnv *env, jclass this)
{
struct native_message_queue *message_queue = malloc(sizeof(struct native_message_queue));
message_queue->in_callback = false;
message_queue->looper = ALooper_prepare();
return _INTPTR(message_queue);
}
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeDestroy(JNIEnv *env, jclass this, jlong ptr)
{
struct native_message_queue *message_queue = _PTR(ptr);
free(message_queue);
}
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce(JNIEnv *env, jclass this, jlong ptr, jint timeout_millis)
{
struct native_message_queue *message_queue = _PTR(ptr);
// printf("Java_android_os_MessageQueue_nativePollOnce: entry (timeout: %d)\n", timeout_millis);
message_queue->in_callback = true;
ALooper_pollOnce(timeout_millis, NULL, NULL, NULL);
message_queue->in_callback = false;
// printf("Java_android_os_MessageQueue_nativePollOnce: exit\n");
/* TODO: what's with the exception stuff */
}
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeWake(JNIEnv *env, jclass this, jlong ptr)
{
struct native_message_queue *message_queue = _PTR(ptr);
ALooper_wake(message_queue->looper);
}
JNIEXPORT jboolean JNICALL Java_android_os_MessageQueue_nativeIsIdling(JNIEnv *env, jclass this, jlong ptr)
{
struct native_message_queue *message_queue = _PTR(ptr);
return ALooper_isPolling(message_queue->looper);
}

View File

@@ -1,3 +1,6 @@
#include <time.h>
#include <math.h>
#include "generated_headers/android_os_SystemClock.h"
JNIEXPORT jlong JNICALL Java_android_os_SystemClock_elapsedRealtime(JNIEnv *env, jclass this)
@@ -5,3 +8,10 @@ JNIEXPORT jlong JNICALL Java_android_os_SystemClock_elapsedRealtime(JNIEnv *env,
printf("FIXME: Java_android_os_SystemClock_elapsedRealtime: returning 0\n");
return 0; // FIXME
}
JNIEXPORT jlong JNICALL Java_android_os_SystemClock_uptimeMillis(JNIEnv *env, jclass this)
{
struct timespec t;
clock_gettime(CLOCK_REALTIME, &t);
return t.tv_sec * 1000 + lround(t.tv_nsec / 1e6);
}

View File

@@ -10,42 +10,42 @@ extern "C" {
/*
* Class: android_os_MessageQueue
* Method: nativeInit
* Signature: ()I
* Signature: ()J
*/
JNIEXPORT jint JNICALL Java_android_os_MessageQueue_nativeInit
JNIEXPORT jlong JNICALL Java_android_os_MessageQueue_nativeInit
(JNIEnv *, jclass);
/*
* Class: android_os_MessageQueue
* Method: nativeDestroy
* Signature: (I)V
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeDestroy
(JNIEnv *, jclass, jint);
(JNIEnv *, jclass, jlong);
/*
* Class: android_os_MessageQueue
* Method: nativePollOnce
* Signature: (II)V
* Signature: (JI)V
*/
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativePollOnce
(JNIEnv *, jclass, jint, jint);
(JNIEnv *, jclass, jlong, jint);
/*
* Class: android_os_MessageQueue
* Method: nativeWake
* Signature: (I)V
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_android_os_MessageQueue_nativeWake
(JNIEnv *, jclass, jint);
(JNIEnv *, jclass, jlong);
/*
* Class: android_os_MessageQueue
* Method: nativeIsIdling
* Signature: (I)Z
* Signature: (J)Z
*/
JNIEXPORT jboolean JNICALL Java_android_os_MessageQueue_nativeIsIdling
(JNIEnv *, jclass, jint);
(JNIEnv *, jclass, jlong);
#ifdef __cplusplus
}

View File

@@ -15,6 +15,14 @@ extern "C" {
JNIEXPORT jboolean JNICALL Java_android_os_SystemClock_setCurrentTimeMillis
(JNIEnv *, jclass, jlong);
/*
* Class: android_os_SystemClock
* Method: uptimeMillis
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_android_os_SystemClock_uptimeMillis
(JNIEnv *, jclass);
/*
* Class: android_os_SystemClock
* Method: elapsedRealtime

View File

@@ -6,6 +6,8 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -102,6 +104,12 @@ public class Activity extends Context {
protected void onCreate(Bundle savedInstanceState) {
System.out.println("- onCreate - yay!");
/* TODO: this probably belongs elsewhere, but this is our entry point for better or worse */
Looper looper = Looper.myLooper();
if(looper == null) {
Looper.prepareMainLooper();
}
return;
}
@@ -212,7 +220,11 @@ public class Activity extends Context {
}
public final void runOnUiThread(Runnable action) {
action.run(); // FIXME: running synchronously for now
if(Looper.myLooper() == Looper.getMainLooper()) {
action.run();
} else {
new Handler(Looper.getMainLooper()).post(action);
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {}

View File

@@ -33,6 +33,7 @@ import android.view.WindowManager;
import android.view.WindowManagerImpl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class Context extends Object {
@@ -138,8 +139,14 @@ public class Context extends Object {
}
public Looper getMainLooper() {
System.out.println("returning the main Looper, most definitely doing just that!");
return new Looper();
/* TODO: this is not what AOSP does, which could be a problem */
Looper looper = Looper.myLooper();
if(looper == null) {
Looper.prepare();
looper = Looper.myLooper();
}
return looper;
}
public String getPackageName() {
@@ -251,9 +258,12 @@ public class Context extends Object {
return new ComponentName("", "");
}
// FIXME - it should be *trivial* to do actually implement this
public FileInputStream openFileInput(String name) {
return null;
// TODO: do these both work? make them look more alike
public FileInputStream openFileInput(String name) throws FileNotFoundException {
System.out.println("openFileInput called for: '" + name + "'");
File file = new File(getFilesDir(), name);
return new FileInputStream(file);
}
public FileOutputStream openFileOutput(String name, int mode) throws java.io.FileNotFoundException {

View File

@@ -738,6 +738,7 @@ public final class Bundle implements Cloneable {
*/
public boolean getBoolean(String key, boolean defaultValue) {
Object o = mMap.get(key);
System.out.println("bundle.getBoolean(" + key + ", " + defaultValue + ") called");
if (o == null) {
return defaultValue;
}

View File

@@ -225,7 +225,7 @@ public class Handler {
*/
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = null /*looper.mQueue*/;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
@@ -316,9 +316,7 @@ public class Handler {
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r) {
// return sendMessageDelayed(getPostMessage(r), 0);
r.run();
return true;
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
@@ -571,29 +569,14 @@ public class Handler {
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
/* MessageQueue queue = mQueue;
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);*/
if (mCallback != null) {
// System.out.println("Handler.sendMessageAtTime: directly calling mCallback.handleMessage)");
if (msg.callback != null) {
msg.callback.run();
}
return mCallback.handleMessage(msg);
} else {
// System.out.println("Handler.sendMessageAtTime: not directly calling mCallback.handleMessage - mCallback is null)");
/* do this in this case as well?
if(msg.callback != null) {
msg.callback.run();
}
*/
return true; // false?
}
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
@@ -609,15 +592,14 @@ public class Handler {
* looper processing the message queue is exiting.
*/
public final boolean sendMessageAtFrontOfQueue(Message msg) {
/*MessageQueue queue = mQueue;
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, 0);*/
return true;
return enqueueMessage(queue, msg, 0);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

View File

@@ -1,19 +1,149 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 android.os;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
String name;
public HandlerThread() {}
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
this.name = name;
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public void start() {
// if(name.equals("FlurryAgent")) { return; }
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
return new Looper();
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}

View File

@@ -53,13 +53,6 @@ import android.util.Printer;
public final class Looper {
private static final String TAG = "Looper";
// FIXME
public Looper() {
mQueue = null;
mThread = Thread.currentThread();
System.out.println("making a fake Looper object, let's hope noone tries to actually use it");
}
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
@@ -84,7 +77,7 @@ public final class Looper {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(/*quitAllowed*/));
sThreadLocal.set(new Looper(quitAllowed));
}
/**
@@ -107,10 +100,9 @@ public final class Looper {
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
return new Looper();
/* synchronized (Looper.class) {
synchronized (Looper.class) {
return sMainLooper;
}*/
}
}
/**
@@ -118,8 +110,7 @@ public final class Looper {
* {@link #quit()} to end the loop.
*/
public static void loop() {
System.out.println("oops, Looper.loop called... and we don't implement that");
/*final Looper me = myLooper();
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
@@ -152,17 +143,13 @@ public final class Looper {
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
// final long newIdent = Binder.clearCallingIdentity();
/* if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}* /
/* final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what);
}
*/
msg.recycle();
}*/
}
}
/**
@@ -170,11 +157,7 @@ public final class Looper {
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return new Looper();
/* if(sThreadLocal.get() == null) {
prepare(false);
}
return sThreadLocal.get();*/
return sThreadLocal.get();
}
/**
@@ -293,8 +276,7 @@ public final class Looper {
* Return the Thread associated with this Looper.
*/
public Thread getThread() {
return Thread.currentThread(); // ugly hack
// return mThread;
return mThread;
}
/**

View File

@@ -34,7 +34,7 @@ public final class MessageQueue {
private final boolean mQuitAllowed;
@SuppressWarnings("unused")
private int mPtr; // used by native code
private long mPtr; // used by native code
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
@@ -48,11 +48,11 @@ public final class MessageQueue {
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
private native static int nativeInit();
private native static void nativeDestroy(int ptr);
private native static void nativePollOnce(int ptr, int timeoutMillis);
private native static void nativeWake(int ptr);
private native static boolean nativeIsIdling(int ptr);
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native static void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsIdling(long ptr);
/**
* Callback interface for discovering when a thread is going to block

View File

@@ -105,16 +105,13 @@ public final class SystemClock {
* @param ms to sleep before returning, in milliseconds of uptime.
*/
public static void sleep(long ms) {
System.out.println("!!! sleep(...) doesn't work, need to implement uptimeMillis");
/*
long start = uptimeMillis();
long duration = ms;
boolean interrupted = false;
do {
try {
Thread.sleep(duration);
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
interrupted = true;
}
duration = start + ms - uptimeMillis();
@@ -126,7 +123,7 @@ public final class SystemClock {
// call to Thread.sleep() or Object.wait() will be interrupted.
Thread.currentThread().interrupt();
}
*/}
}
/**
* Sets the current wall time, in milliseconds. Requires the calling
@@ -141,9 +138,7 @@ public final class SystemClock {
*
* @return milliseconds of non-sleep uptime since boot.
*/
/*native */ public static long uptimeMillis() {
return 654000; // FIXME
}
native public static long uptimeMillis();
/**
* Returns milliseconds since boot, including time spent in sleep.

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@@ -49,12 +50,34 @@ int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outDat
return _ZN7android6Looper7pollAllEiPiS1_PPv(looper, timeoutMillis, outFd, outEvents, outData);
}
int _ZN7android6Looper8pollOnceEiPiS1_PPv(ALooper *this, int timeoutMillis, int *outFd, int *outEvents, void **outData);
int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)
{
ALooper *looper = ALooper_forThread();
if(!looper) {
fprintf(stderr, "ALooper_pollAll: ALooper_forThread returned NULL\n");
return 0;
}
return _ZN7android6Looper8pollOnceEiPiS1_PPv(looper, timeoutMillis, outFd, outEvents, outData);
}
int _ZN7android6Looper5addFdEiiiPFiiiPvES1_(ALooper *this, int fd, int ident, int events, Looper_callbackFunc callback, void *data);
int ALooper_addFd(ALooper* looper, int fd, int ident, int events, Looper_callbackFunc callback, void* data)
{
return _ZN7android6Looper5addFdEiiiPFiiiPvES1_(looper, fd, ident, events, callback, data);
}
void _ZN7android6Looper4wakeEv(ALooper *this);
void ALooper_wake(ALooper* looper) {
void ALooper_wake(ALooper *looper)
{
_ZN7android6Looper4wakeEv(looper);
}
/* this is not part of the android API, but we use it internally */
bool _ZNK7android6Looper9isPollingEv(ALooper *this);
bool ALooper_isPolling(ALooper *looper)
{
return _ZNK7android6Looper9isPollingEv(looper);
}