Merge "GameController compat. fix for GameActivity 1.2"
diff --git a/OWNERS b/OWNERS
index 65e6ffb..21d473e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,4 +4,5 @@
 jeremyns@google.com
 bkaya@google.com
 tomnom@google.com
-eriste@google.com
\ No newline at end of file
+artyompp@google.com
+gvamsi@google.com
\ No newline at end of file
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 49e4f95..760ffcf 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,8 +1,6 @@
 [Hook Scripts]
 
-swappy_abi_hook = ${REPO_ROOT}/gamesdk/hooks/check_swappy_abi_version.sh ${PREUPLOAD_COMMIT} ${PREUPLOAD_FILES}
-tuningfork_abi_hook = ${REPO_ROOT}/gamesdk/hooks/check_tuningfork_abi_version.sh ${PREUPLOAD_COMMIT} ${PREUPLOAD_FILES}
-bugfix_hook = ${REPO_ROOT}/gamesdk/hooks/check_bugfix_version.sh ${PREUPLOAD_COMMIT} ${PREUPLOAD_FILES}
+versions_hook = ${REPO_ROOT}/gamesdk/hooks/check_library_versions.sh ${PREUPLOAD_COMMIT} ${PREUPLOAD_FILES}
 
 [Builtin Hooks]
 clang_format = true
diff --git a/VERSIONS b/VERSIONS
index c5930c7..2a2f1f4 100644
--- a/VERSIONS
+++ b/VERSIONS
@@ -6,10 +6,10 @@
 # Nickname      JetPackName             ProjectName             Version  JetPackSubscript
 #
 AGDK            NA                      NA                      2022.1.1 alpha01
-swappy          games-frame-pacing      games-frame-pacing      1.10.2   alpha01
-tuningfork      games-performance-tuner games-performance-tuner 1.6.1    alpha01
+swappy          games-frame-pacing      games-frame-pacing      2.0.0    alpha01
+tuningfork      games-performance-tuner games-performance-tuner 2.0.0    alpha01
 oboe            oboe                    None                    1.0.1    alpha01
-game_activity   games-activity          game-activity           1.2.2
-game_text_input games-text-input        game-text-input         1.1.2    alpha01
-paddleboat      games-controller        games-controller        1.1.1    alpha01
-memory_advice   games-memory-advice     games-memory-advice     1.0.0    beta03
+game_activity   games-activity          game-activity           2.0.0    alpha01
+game_text_input games-text-input        game-text-input         2.0.0    alpha01
+paddleboat      games-controller        games-controller        2.0.0    alpha01
+memory_advice   games-memory-advice     games-memory-advice     2.0.0    alpha01
diff --git a/build.sh b/build.sh
index efd28fa..ccda7d6 100755
--- a/build.sh
+++ b/build.sh
@@ -110,7 +110,7 @@
     mkdir -p ./third-party
     pushd third-party
     if [ ! -d "imgui" ] ; then
-        git clone https://github.com/ocornut/imgui
+        git clone https://github.com/ocornut/imgui -b v1.89
     fi
     popd
     popd
diff --git a/game-activity/CMakeLists.txt b/game-activity/CMakeLists.txt
index e947466..9fda9b4 100644
--- a/game-activity/CMakeLists.txt
+++ b/game-activity/CMakeLists.txt
@@ -18,11 +18,13 @@
 set(CMAKE_CXX_STANDARD 17)
 
 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/prefab-src/modules/game-activity/include/)
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include/)
 
 set(GAMEACTIVITY_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/prefab-src/modules/game-activity/include/")
 
 set(GAMEACTIVITY_SRCS
     ${GAMEACTIVITY_SRC_DIR}/game-activity/GameActivity.cpp
+    ${GAMEACTIVITY_SRC_DIR}/game-activity/GameActivityEvents.cpp
     ${GAMEACTIVITY_SRC_DIR}/game-activity/native_app_glue/android_native_app_glue.c
     ${GAMEACTIVITY_SRC_DIR}/game-text-input/gametextinput.cpp)
 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Os")
diff --git a/game-activity/prefab-src/modules/game-activity/include/common b/game-activity/prefab-src/modules/game-activity/include/common
new file mode 120000
index 0000000..9388f34
--- /dev/null
+++ b/game-activity/prefab-src/modules/game-activity/include/common
@@ -0,0 +1 @@
+../../../../../include/common/
\ No newline at end of file
diff --git a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.cpp b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.cpp
index dee363c..3c0e762 100644
--- a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.cpp
+++ b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.cpp
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#define LOG_TAG "GameActivity"
 
 #include "GameActivity.h"
 
@@ -39,30 +38,10 @@
 #include <mutex>
 #include <string>
 
-// TODO(b/187147166): these functions were extracted from the Game SDK
-// (gamesdk/src/common/system_utils.h). system_utils.h/cpp should be used
-// instead.
+#include "GameActivityLog.h"
+
 namespace {
 
-std::string getSystemPropViaGet(const char *key,
-                                const char *default_value = "") {
-    char buffer[PROP_VALUE_MAX + 1] = "";  // +1 for terminator
-    int bufferLen = __system_property_get(key, buffer);
-    if (bufferLen > 0)
-        return buffer;
-    else
-        return "";
-}
-
-std::string GetSystemProp(const char *key, const char *default_value = "") {
-    return getSystemPropViaGet(key, default_value);
-}
-
-int GetSystemPropAsInt(const char *key, int default_value = 0) {
-    std::string prop = GetSystemProp(key);
-    return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
-}
-
 struct OwnedGameTextInputState {
     OwnedGameTextInputState &operator=(const GameTextInputState &rhs) {
         inner = rhs;
@@ -76,93 +55,6 @@
 
 }  // anonymous namespace
 
-#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
-#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);
-#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
-#ifdef NDEBUG
-#define ALOGV(...)
-#else
-#define ALOGV(...) \
-    __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
-#endif
-
-/* Returns 2nd arg.  Used to substitute default value if caller's vararg list
- * is empty.
- */
-#define __android_second(first, second, ...) second
-
-/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
- * returns nothing.
- */
-#define __android_rest(first, ...) , ##__VA_ARGS__
-
-#define android_printAssert(cond, tag, fmt...) \
-    __android_log_assert(cond, tag,            \
-                         __android_second(0, ##fmt, NULL) __android_rest(fmt))
-
-#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
-
-#ifndef LOG_ALWAYS_FATAL_IF
-#define LOG_ALWAYS_FATAL_IF(cond, ...)                                \
-    ((CONDITION(cond))                                                \
-         ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
-         : (void)0)
-#endif
-
-#ifndef LOG_ALWAYS_FATAL
-#define LOG_ALWAYS_FATAL(...) \
-    (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
-#endif
-
-/*
- * Simplified macro to send a warning system log message using current LOG_TAG.
- */
-#ifndef SLOGW
-#define SLOGW(...) \
-    ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
-#endif
-
-#ifndef SLOGW_IF
-#define SLOGW_IF(cond, ...)                                                    \
-    ((__predict_false(cond))                                                   \
-         ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
-         : (void)0)
-#endif
-
-/*
- * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
- * are stripped out of release builds.
- */
-#if LOG_NDEBUG
-
-#ifndef LOG_FATAL_IF
-#define LOG_FATAL_IF(cond, ...) ((void)0)
-#endif
-#ifndef LOG_FATAL
-#define LOG_FATAL(...) ((void)0)
-#endif
-
-#else
-
-#ifndef LOG_FATAL_IF
-#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
-#endif
-#ifndef LOG_FATAL
-#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
-#endif
-
-#endif
-
-/*
- * Assertion that generates a log message when the assertion fails.
- * Stripped out of release builds.  Uses the current LOG_TAG.
- */
-#ifndef ALOG_ASSERT
-#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
-#endif
-
-#define LOG_TRACE(...)
-
 #ifndef NELEM
 #define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
 #endif
@@ -189,6 +81,30 @@
 } gInsetsClassInfo;
 
 /*
+ * JNI fields of the Configuration Java class.
+ */
+static struct ConfigurationClassInfo {
+    jfieldID colorMode;
+    jfieldID densityDpi;
+    jfieldID fontScale;
+    jfieldID fontWeightAdjustment;
+    jfieldID hardKeyboardHidden;
+    jfieldID keyboard;
+    jfieldID keyboardHidden;
+    jfieldID mcc;
+    jfieldID mnc;
+    jfieldID navigation;
+    jfieldID navigationHidden;
+    jfieldID orientation;
+    jfieldID screenHeightDp;
+    jfieldID screenLayout;
+    jfieldID screenWidthDp;
+    jfieldID smallestScreenWidthDp;
+    jfieldID touchscreen;
+    jfieldID uiMode;
+} gConfigurationClassInfo;
+
+/*
  * JNI methods of the WindowInsetsCompat.Type Java class.
  */
 static struct {
@@ -222,6 +138,31 @@
 };
 
 /*
+ * Last known Configuration values. They may be accessed from the different
+ * thread, this is why they are made atomic.
+ */
+static struct Configuration {
+    std::atomic_int colorMode;
+    std::atomic_int densityDpi;
+    std::atomic<float> fontScale;
+    std::atomic_int fontWeightAdjustment;
+    std::atomic_int hardKeyboardHidden;
+    std::atomic_int keyboard;
+    std::atomic_int keyboardHidden;
+    std::atomic_int mcc;
+    std::atomic_int mnc;
+    std::atomic_int navigation;
+    std::atomic_int navigationHidden;
+    std::atomic_int orientation;
+    std::atomic_int screenHeightDp;
+    std::atomic_int screenLayout;
+    std::atomic_int screenWidthDp;
+    std::atomic_int smallestScreenWidthDp;
+    std::atomic_int touchscreen;
+    std::atomic_int uiMode;
+} gConfiguration;
+
+/*
  * Write a command to be executed by the GameActivity on the application main
  * thread.
  */
@@ -342,6 +283,8 @@
     ARect insetsState[GAMECOMMON_INSETS_TYPE_COUNT];
 };
 
+static void readConfigurationValues(NativeCode *code, jobject javaConfig);
+
 extern "C" void GameActivity_finish(GameActivity *activity) {
     NativeCode *code = static_cast<NativeCode *>(activity);
     write_work(code->mainWorkWrite, CMD_FINISH, 0);
@@ -462,10 +405,10 @@
 // ------------------------------------------------------------------------
 static thread_local std::string g_error_msg;
 
-static jlong initializeNativeCode_native(JNIEnv *env, jobject javaGameActivity,
-                                   jstring internalDataDir, jstring obbDir,
-                                   jstring externalDataDir, jobject jAssetMgr,
-                                   jbyteArray savedState) {
+static jlong initializeNativeCode_native(
+    JNIEnv *env, jobject javaGameActivity, jstring internalDataDir,
+    jstring obbDir, jstring externalDataDir, jobject jAssetMgr,
+    jbyteArray savedState, jobject javaConfig) {
     LOG_TRACE("initializeNativeCode_native");
     NativeCode *code = NULL;
 
@@ -539,6 +482,9 @@
         rawSavedState = env->GetByteArrayElements(savedState, NULL);
         rawSavedSize = env->GetArrayLength(savedState);
     }
+
+    readConfigurationValues(code, javaConfig);
+
     GameActivity_onCreate(code, rawSavedState, rawSavedSize);
 
     code->gameTextInput = GameTextInput_init(env, 0);
@@ -646,11 +592,50 @@
     }
 }
 
+static void readConfigurationValues(NativeCode *code, jobject javaConfig) {
+    gConfiguration.colorMode =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.colorMode);
+    gConfiguration.densityDpi =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.densityDpi);
+    gConfiguration.fontScale =
+        code->env->GetFloatField(javaConfig, gConfigurationClassInfo.fontScale);
+    gConfiguration.fontWeightAdjustment = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.fontWeightAdjustment);
+    gConfiguration.hardKeyboardHidden = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.hardKeyboardHidden);
+    gConfiguration.mcc =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.mcc);
+    gConfiguration.mnc =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.mnc);
+    gConfiguration.navigation =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.navigation);
+    gConfiguration.navigationHidden = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.navigationHidden);
+    gConfiguration.orientation =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.orientation);
+    gConfiguration.screenHeightDp = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.screenHeightDp);
+    gConfiguration.screenLayout = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.screenLayout);
+    gConfiguration.screenWidthDp = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.screenWidthDp);
+    gConfiguration.smallestScreenWidthDp = code->env->GetIntField(
+        javaConfig, gConfigurationClassInfo.smallestScreenWidthDp);
+    gConfiguration.touchscreen =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.touchscreen);
+    gConfiguration.uiMode =
+        code->env->GetIntField(javaConfig, gConfigurationClassInfo.uiMode);
+
+    checkAndClearException(code->env, "Configuration.get");
+}
+
 static void onConfigurationChanged_native(JNIEnv *env, jobject javaGameActivity,
-                                          jlong handle) {
+                                          jlong handle, jobject javaNewConfig) {
     LOG_TRACE("onConfigurationChanged_native");
     if (handle != 0) {
         NativeCode *code = (NativeCode *)handle;
+        readConfigurationValues(code, javaNewConfig);
+
         if (code->callbacks.onConfigurationChanged != NULL) {
             code->callbacks.onConfigurationChanged(code);
         }
@@ -772,58 +757,6 @@
     }
 }
 
-static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
-    /* AMOTION_EVENT_AXIS_X */ true,
-    /* AMOTION_EVENT_AXIS_Y */ true,
-    // Disable all other axes by default (they can be enabled using
-    // `GameActivityPointerAxes_enableAxis`).
-    false};
-
-extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
-    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
-        return;
-    }
-
-    enabledAxes[axis] = true;
-}
-
-float GameActivityPointerAxes_getAxisValue(
-    const GameActivityPointerAxes *pointerInfo, int32_t axis) {
-    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
-        return 0;
-    }
-
-    if (!enabledAxes[axis]) {
-        ALOGW("Axis %d must be enabled before it can be accessed.", axis);
-        return 0;
-    }
-
-    return pointerInfo->axisValues[axis];
-}
-
-extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
-    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
-        return;
-    }
-
-    enabledAxes[axis] = false;
-}
-
-float GameActivityMotionEvent_getHistoricalAxisValue(
-    const GameActivityMotionEvent *event, int axis, int pointerIndex,
-    int historyPos) {
-    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
-        return 0;
-    }
-
-    if (!enabledAxes[axis]) {
-        ALOGW("Axis %d must be enabled before it can be accessed.", axis);
-        return 0;
-    }
-
-    return event->historicalAxisValues[event->pointerCount * historyPos + axis];
-}
-
 extern "C" void GameActivity_setImeEditorInfo(GameActivity *activity,
                                               int inputType, int actionId,
                                               int imeOptions) {
@@ -832,295 +765,76 @@
                actionId, imeOptions);
 }
 
-static struct {
-    jmethodID getDeviceId;
-    jmethodID getSource;
-    jmethodID getAction;
-
-    jmethodID getEventTime;
-    jmethodID getDownTime;
-
-    jmethodID getFlags;
-    jmethodID getMetaState;
-
-    jmethodID getActionButton;
-    jmethodID getButtonState;
-    jmethodID getClassification;
-    jmethodID getEdgeFlags;
-
-    jmethodID getHistorySize;
-    jmethodID getHistoricalEventTime;
-
-    jmethodID getPointerCount;
-    jmethodID getPointerId;
-
-    jmethodID getToolType;
-
-    jmethodID getRawX;
-    jmethodID getRawY;
-    jmethodID getXPrecision;
-    jmethodID getYPrecision;
-    jmethodID getAxisValue;
-
-    jmethodID getHistoricalAxisValue;
-} gMotionEventClassInfo;
-
-extern "C" void GameActivityMotionEvent_destroy(
-    GameActivityMotionEvent *c_event) {
-    delete c_event->historicalAxisValues;
-    delete c_event->historicalEventTimes;
+extern "C" int GameActivity_getColorMode(GameActivity *) {
+    return gConfiguration.colorMode;
 }
 
-extern "C" void GameActivityMotionEvent_fromJava(
-    JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event) {
-    static bool gMotionEventClassInfoInitialized = false;
-    if (!gMotionEventClassInfoInitialized) {
-        int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
-        gMotionEventClassInfo = {0};
-        jclass motionEventClass = env->FindClass("android/view/MotionEvent");
-        gMotionEventClassInfo.getDeviceId =
-            env->GetMethodID(motionEventClass, "getDeviceId", "()I");
-        gMotionEventClassInfo.getSource =
-            env->GetMethodID(motionEventClass, "getSource", "()I");
-        gMotionEventClassInfo.getAction =
-            env->GetMethodID(motionEventClass, "getAction", "()I");
-        gMotionEventClassInfo.getEventTime =
-            env->GetMethodID(motionEventClass, "getEventTime", "()J");
-        gMotionEventClassInfo.getDownTime =
-            env->GetMethodID(motionEventClass, "getDownTime", "()J");
-        gMotionEventClassInfo.getFlags =
-            env->GetMethodID(motionEventClass, "getFlags", "()I");
-        gMotionEventClassInfo.getMetaState =
-            env->GetMethodID(motionEventClass, "getMetaState", "()I");
-        if (sdkVersion >= 23) {
-            gMotionEventClassInfo.getActionButton =
-                env->GetMethodID(motionEventClass, "getActionButton", "()I");
-        }
-        if (sdkVersion >= 14) {
-            gMotionEventClassInfo.getButtonState =
-                env->GetMethodID(motionEventClass, "getButtonState", "()I");
-        }
-        if (sdkVersion >= 29) {
-            gMotionEventClassInfo.getClassification =
-                env->GetMethodID(motionEventClass, "getClassification", "()I");
-        }
-        gMotionEventClassInfo.getEdgeFlags =
-            env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
-
-        gMotionEventClassInfo.getHistorySize =
-            env->GetMethodID(motionEventClass, "getHistorySize", "()I");
-        gMotionEventClassInfo.getHistoricalEventTime = env->GetMethodID(
-            motionEventClass, "getHistoricalEventTime", "(I)J");
-
-        gMotionEventClassInfo.getPointerCount =
-            env->GetMethodID(motionEventClass, "getPointerCount", "()I");
-        gMotionEventClassInfo.getPointerId =
-            env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
-        gMotionEventClassInfo.getToolType =
-            env->GetMethodID(motionEventClass, "getToolType", "(I)I");
-        if (sdkVersion >= 29) {
-            gMotionEventClassInfo.getRawX =
-                env->GetMethodID(motionEventClass, "getRawX", "(I)F");
-            gMotionEventClassInfo.getRawY =
-                env->GetMethodID(motionEventClass, "getRawY", "(I)F");
-        }
-        gMotionEventClassInfo.getXPrecision =
-            env->GetMethodID(motionEventClass, "getXPrecision", "()F");
-        gMotionEventClassInfo.getYPrecision =
-            env->GetMethodID(motionEventClass, "getYPrecision", "()F");
-        gMotionEventClassInfo.getAxisValue =
-            env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
-
-        gMotionEventClassInfo.getHistoricalAxisValue = env->GetMethodID(
-            motionEventClass, "getHistoricalAxisValue", "(III)F");
-        gMotionEventClassInfoInitialized = true;
-    }
-
-    int pointerCount =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getPointerCount);
-    pointerCount =
-        std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
-    out_event->pointerCount = pointerCount;
-    for (int i = 0; i < pointerCount; ++i) {
-        out_event->pointers[i] = {
-            /*id=*/env->CallIntMethod(motionEvent,
-                                      gMotionEventClassInfo.getPointerId, i),
-            /*toolType=*/
-            env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType,
-                               i),
-            /*axisValues=*/{0},
-            /*rawX=*/gMotionEventClassInfo.getRawX
-                ? env->CallFloatMethod(motionEvent,
-                                       gMotionEventClassInfo.getRawX, i)
-                : 0,
-            /*rawY=*/gMotionEventClassInfo.getRawY
-                ? env->CallFloatMethod(motionEvent,
-                                       gMotionEventClassInfo.getRawY, i)
-                : 0,
-        };
-
-        for (int axisIndex = 0;
-             axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) {
-            if (enabledAxes[axisIndex]) {
-                out_event->pointers[i].axisValues[axisIndex] =
-                    env->CallFloatMethod(motionEvent,
-                                         gMotionEventClassInfo.getAxisValue,
-                                         axisIndex, i);
-            }
-        }
-    }
-
-    int historySize =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getHistorySize);
-    out_event->historySize = historySize;
-    out_event->historicalAxisValues =
-        new float[historySize * pointerCount *
-                  GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
-    out_event->historicalEventTimes = new long[historySize];
-
-    for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
-        out_event->historicalEventTimes[historyIndex] = env->CallLongMethod(
-            motionEvent, gMotionEventClassInfo.getHistoricalEventTime,
-            historyIndex);
-        for (int i = 0; i < pointerCount; ++i) {
-            int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
-            int historyAxisOffset = historyIndex * pointerCount *
-                                    GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
-            float *axisValues =
-                &out_event
-                     ->historicalAxisValues[historyAxisOffset + pointerOffset];
-            for (int axisIndex = 0;
-                 axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
-                 ++axisIndex) {
-                if (enabledAxes[axisIndex]) {
-                    axisValues[axisIndex] = env->CallFloatMethod(
-                        motionEvent,
-                        gMotionEventClassInfo.getHistoricalAxisValue, axisIndex,
-                        i, historyIndex);
-                }
-            }
-        }
-    }
-
-    out_event->deviceId =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getDeviceId);
-    out_event->source =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getSource);
-    out_event->action =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getAction);
-    out_event->eventTime =
-        env->CallLongMethod(motionEvent, gMotionEventClassInfo.getEventTime) *
-        1000000;
-    out_event->downTime =
-        env->CallLongMethod(motionEvent, gMotionEventClassInfo.getDownTime) *
-        1000000;
-    out_event->flags =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getFlags);
-    out_event->metaState =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getMetaState);
-    out_event->actionButton =
-        gMotionEventClassInfo.getActionButton
-            ? env->CallIntMethod(motionEvent,
-                                 gMotionEventClassInfo.getActionButton)
-            : 0;
-    out_event->buttonState =
-        gMotionEventClassInfo.getButtonState
-            ? env->CallIntMethod(motionEvent,
-                                 gMotionEventClassInfo.getButtonState)
-            : 0;
-    out_event->classification =
-        gMotionEventClassInfo.getClassification
-            ? env->CallIntMethod(motionEvent,
-                                 gMotionEventClassInfo.getClassification)
-            : 0;
-    out_event->edgeFlags =
-        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getEdgeFlags);
-    out_event->precisionX =
-        env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getXPrecision);
-    out_event->precisionY =
-        env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getYPrecision);
+extern "C" int GameActivity_getDensityDpi(GameActivity *) {
+    return gConfiguration.densityDpi;
 }
 
-static struct {
-    jmethodID getDeviceId;
-    jmethodID getSource;
-    jmethodID getAction;
+extern "C" float GameActivity_getFontScale(GameActivity *) {
+    return gConfiguration.fontScale;
+}
 
-    jmethodID getEventTime;
-    jmethodID getDownTime;
+extern "C" int GameActivity_getFontWeightAdjustment(GameActivity *) {
+    return gConfiguration.fontWeightAdjustment;
+}
 
-    jmethodID getFlags;
-    jmethodID getMetaState;
+extern "C" int GameActivity_getHardKeyboardHidden(GameActivity *) {
+    return gConfiguration.hardKeyboardHidden;
+}
 
-    jmethodID getModifiers;
-    jmethodID getRepeatCount;
-    jmethodID getKeyCode;
-    jmethodID getScanCode;
-    jmethodID getUnicodeChar;
-} gKeyEventClassInfo;
+extern "C" int GameActivity_getKeyboard(GameActivity *) {
+    return gConfiguration.keyboard;
+}
 
-extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent,
-                                              GameActivityKeyEvent *out_event) {
-    static bool gKeyEventClassInfoInitialized = false;
-    if (!gKeyEventClassInfoInitialized) {
-        int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
-        gKeyEventClassInfo = {0};
-        jclass keyEventClass = env->FindClass("android/view/KeyEvent");
-        gKeyEventClassInfo.getDeviceId =
-            env->GetMethodID(keyEventClass, "getDeviceId", "()I");
-        gKeyEventClassInfo.getSource =
-            env->GetMethodID(keyEventClass, "getSource", "()I");
-        gKeyEventClassInfo.getAction =
-            env->GetMethodID(keyEventClass, "getAction", "()I");
-        gKeyEventClassInfo.getEventTime =
-            env->GetMethodID(keyEventClass, "getEventTime", "()J");
-        gKeyEventClassInfo.getDownTime =
-            env->GetMethodID(keyEventClass, "getDownTime", "()J");
-        gKeyEventClassInfo.getFlags =
-            env->GetMethodID(keyEventClass, "getFlags", "()I");
-        gKeyEventClassInfo.getMetaState =
-            env->GetMethodID(keyEventClass, "getMetaState", "()I");
-        if (sdkVersion >= 13) {
-            gKeyEventClassInfo.getModifiers =
-                env->GetMethodID(keyEventClass, "getModifiers", "()I");
-        }
-        gKeyEventClassInfo.getRepeatCount =
-            env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
-        gKeyEventClassInfo.getKeyCode =
-            env->GetMethodID(keyEventClass, "getKeyCode", "()I");
-        gKeyEventClassInfo.getScanCode =
-            env->GetMethodID(keyEventClass, "getScanCode", "()I");
-        gKeyEventClassInfo.getUnicodeChar =
-            env->GetMethodID(keyEventClass, "getUnicodeChar", "()I");
+extern "C" int GameActivity_getKeyboardHidden(GameActivity *) {
+    return gConfiguration.keyboardHidden;
+}
 
-        gKeyEventClassInfoInitialized = true;
-    }
+extern "C" int GameActivity_getMcc(GameActivity *) {
+    return gConfiguration.mcc;
+}
 
-    *out_event = {
-        /*deviceId=*/env->CallIntMethod(keyEvent,
-                                        gKeyEventClassInfo.getDeviceId),
-        /*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
-        /*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
-        // TODO: introduce a millisecondsToNanoseconds helper:
-        /*eventTime=*/
-        env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) *
-            1000000,
-        /*downTime=*/
-        env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
-        /*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
-        /*metaState=*/
-        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
-        /*modifiers=*/gKeyEventClassInfo.getModifiers
-            ? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
-            : 0,
-        /*repeatCount=*/
-        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
-        /*keyCode=*/
-        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode),
-        /*scanCode=*/
-        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode),
-        /*unicodeChar=*/
-        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)};
+extern "C" int GameActivity_getMnc(GameActivity *) {
+    return gConfiguration.mnc;
+}
+
+extern "C" int GameActivity_getNavigation(GameActivity *) {
+    return gConfiguration.navigation;
+}
+
+extern "C" int GameActivity_getNavigationHidden(GameActivity *) {
+    return gConfiguration.navigationHidden;
+}
+
+extern "C" int GameActivity_getOrientation(GameActivity *) {
+    return gConfiguration.orientation;
+}
+
+extern "C" int GameActivity_getScreenHeightDp(GameActivity *) {
+    return gConfiguration.screenHeightDp;
+}
+
+extern "C" int GameActivity_getScreenLayout(GameActivity *) {
+    return gConfiguration.screenLayout;
+}
+
+extern "C" int GameActivity_getScreenWidthDp(GameActivity *) {
+    return gConfiguration.screenWidthDp;
+}
+
+extern "C" int GameActivity_getSmallestScreenWidthDp(GameActivity *) {
+    return gConfiguration.smallestScreenWidthDp;
+}
+
+extern "C" int GameActivity_getTouchscreen(GameActivity *) {
+    return gConfiguration.touchscreen;
+}
+
+extern "C" int GameActivity_getUIMode(GameActivity *) {
+    return gConfiguration.uiMode;
 }
 
 static bool onTouchEvent_native(JNIEnv *env, jobject javaGameActivity,
@@ -1209,10 +923,28 @@
     GameTextInput_setInputConnection(code->gameTextInput, inputConnection);
 }
 
+static void onContentRectChangedNative_native(JNIEnv *env, jobject activity,
+                                              jlong handle, jint x, jint y,
+                                              jint w, jint h) {
+    if (handle != 0) {
+        NativeCode *code = (NativeCode *)handle;
+
+        if (code->callbacks.onContentRectChanged != nullptr) {
+            ARect rect;
+            rect.left = x;
+            rect.top = y;
+            rect.right = x+w;
+            rect.bottom = y+h;
+            code->callbacks.onContentRectChanged(code, &rect);
+        }
+    }
+}
+
 static const JNINativeMethod g_methods[] = {
     {"initializeNativeCode",
      "(Ljava/lang/String;Ljava/lang/String;"
-     "Ljava/lang/String;Landroid/content/res/AssetManager;[B)J",
+     "Ljava/lang/String;Landroid/content/res/AssetManager;"
+     "[BLandroid/content/res/Configuration;)J",
      (void *)initializeNativeCode_native},
     {"getDlError", "()Ljava/lang/String;", (void *)getDlError_native},
     {"terminateNativeCode", "(J)V", (void *)terminateNativeCode_native},
@@ -1221,7 +953,7 @@
     {"onSaveInstanceStateNative", "(J)[B", (void *)onSaveInstanceState_native},
     {"onPauseNative", "(J)V", (void *)onPause_native},
     {"onStopNative", "(J)V", (void *)onStop_native},
-    {"onConfigurationChangedNative", "(J)V",
+    {"onConfigurationChangedNative", "(JLandroid/content/res/Configuration;)V",
      (void *)onConfigurationChanged_native},
     {"onTrimMemoryNative", "(JI)V", (void *)onTrimMemory_native},
     {"onWindowFocusChangedNative", "(JZ)V",
@@ -1246,13 +978,16 @@
     {"setInputConnectionNative",
      "(JLcom/google/androidgamesdk/gametextinput/InputConnection;)V",
      (void *)setInputConnection_native},
+    {"onContentRectChangedNative", "(JIIII)V",
+     (void *)onContentRectChangedNative_native},
 };
 
 static const char *const kGameActivityPathName =
     "com/google/androidgamesdk/GameActivity";
 
 static const char *const kInsetsPathName = "androidx/core/graphics/Insets";
-
+static const char *const kConfigurationPathName =
+    "android/content/res/Configuration";
 static const char *const kWindowInsetsCompatTypePathName =
     "androidx/core/view/WindowInsetsCompat$Type";
 
@@ -1310,12 +1045,51 @@
                   "getWaterfallInsets", "()Landroidx/core/graphics/Insets;");
     GET_METHOD_ID(gGameActivityClassInfo.setImeEditorInfoFields, activity_class,
                   "setImeEditorInfoFields", "(III)V");
+
     jclass insets_class;
     FIND_CLASS(insets_class, kInsetsPathName);
     GET_FIELD_ID(gInsetsClassInfo.left, insets_class, "left", "I");
     GET_FIELD_ID(gInsetsClassInfo.right, insets_class, "right", "I");
     GET_FIELD_ID(gInsetsClassInfo.top, insets_class, "top", "I");
     GET_FIELD_ID(gInsetsClassInfo.bottom, insets_class, "bottom", "I");
+
+    jclass configuration_class;
+    FIND_CLASS(configuration_class, kConfigurationPathName);
+    GET_FIELD_ID(gConfigurationClassInfo.colorMode, configuration_class,
+                 "colorMode", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.densityDpi, configuration_class,
+                 "densityDpi", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.fontScale, configuration_class,
+                 "fontScale", "F");
+    GET_FIELD_ID(gConfigurationClassInfo.fontWeightAdjustment,
+                 configuration_class, "fontWeightAdjustment", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.hardKeyboardHidden,
+                 configuration_class, "hardKeyboardHidden", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.keyboard, configuration_class,
+                 "keyboard", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.keyboardHidden, configuration_class,
+                 "keyboardHidden", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.mcc, configuration_class, "mcc", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.mnc, configuration_class, "mnc", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.navigation, configuration_class,
+                 "navigation", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.navigationHidden, configuration_class,
+                 "navigationHidden", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.orientation, configuration_class,
+                 "orientation", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.screenHeightDp, configuration_class,
+                 "screenHeightDp", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.screenLayout, configuration_class,
+                 "screenLayout", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.screenWidthDp, configuration_class,
+                 "screenWidthDp", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.smallestScreenWidthDp,
+                 configuration_class, "smallestScreenWidthDp", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.touchscreen, configuration_class,
+                 "touchscreen", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.uiMode, configuration_class, "uiMode",
+                 "I");
+
     jclass windowInsetsCompatType_class;
     FIND_CLASS(windowInsetsCompatType_class, kWindowInsetsCompatTypePathName);
     gWindowInsetsCompatTypeClassInfo.clazz =
@@ -1344,12 +1118,12 @@
 
 extern "C" JNIEXPORT jlong JNICALL
 Java_com_google_androidgamesdk_GameActivity_initializeNativeCode(
-    JNIEnv *env, jobject javaGameActivity,
-    jstring internalDataDir, jstring obbDir, jstring externalDataDir,
-    jobject jAssetMgr, jbyteArray savedState) {
+    JNIEnv *env, jobject javaGameActivity, jstring internalDataDir,
+    jstring obbDir, jstring externalDataDir, jobject jAssetMgr,
+    jbyteArray savedState, jobject javaConfig) {
     GameActivity_register(env);
     jlong nativeCode = initializeNativeCode_native(
-        env, javaGameActivity,internalDataDir, obbDir,
-        externalDataDir, jAssetMgr, savedState);
+        env, javaGameActivity, internalDataDir, obbDir, externalDataDir,
+        jAssetMgr, savedState, javaConfig);
     return nativeCode;
 }
diff --git a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.h b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.h
index 31fd7ca..d43ab25 100644
--- a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.h
+++ b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.h
@@ -36,19 +36,21 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include "common/gamesdk_common.h"
+#include "game-activity/GameActivityEvents.h"
 #include "game-text-input/gametextinput.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-#define GAMEACTIVITY_MAJOR_VERSION 1
-#define GAMEACTIVITY_MINOR_VERSION 2
-#define GAMEACTIVITY_BUGFIX_VERSION 2
-
-#define GAMEACTIVITY_PACKED_VERSION                                           \
-    ((GAMEACTIVITY_MAJOR_VERSION << 16) | (GAMEACTIVITY_MINOR_VERSION << 8) | \
-     (GAMEACTIVITY_BUGFIX_VERSION))
+#define GAMEACTIVITY_MAJOR_VERSION 2
+#define GAMEACTIVITY_MINOR_VERSION 0
+#define GAMEACTIVITY_BUGFIX_VERSION 0
+#define GAMEACTIVITY_PACKED_VERSION                            \
+    ANDROID_GAMESDK_PACKED_VERSION(GAMEACTIVITY_MAJOR_VERSION, \
+                                   GAMEACTIVITY_MINOR_VERSION, \
+                                   GAMEACTIVITY_BUGFIX_VERSION)
 
 /**
  * {@link GameActivityCallbacks}
@@ -124,267 +126,6 @@
 } GameActivity;
 
 /**
- * The maximum number of axes supported in an Android MotionEvent.
- * See https://developer.android.com/ndk/reference/group/input.
- */
-#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
-
-/**
- * \brief Describe information about a pointer, found in a
- * GameActivityMotionEvent.
- *
- * You can read values directly from this structure, or use helper functions
- * (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and
- * `GameActivityPointerAxes_getAxisValue`).
- *
- * The X axis and Y axis are enabled by default but any other axis that you want
- * to read **must** be enabled first, using
- * `GameActivityPointerAxes_enableAxis`.
- *
- * \see GameActivityMotionEvent
- */
-typedef struct GameActivityPointerAxes {
-    int32_t id;
-    int32_t toolType;
-    float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
-    float rawX;
-    float rawY;
-} GameActivityPointerAxes;
-
-/** \brief Get the toolType of the pointer. */
-inline int32_t GameActivityPointerAxes_getToolType(
-    const GameActivityPointerAxes* pointerInfo) {
-    return pointerInfo->toolType;
-}
-
-/** \brief Get the current X coordinate of the pointer. */
-inline float GameActivityPointerAxes_getX(
-    const GameActivityPointerAxes* pointerInfo) {
-    return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
-}
-
-/** \brief Get the current Y coordinate of the pointer. */
-inline float GameActivityPointerAxes_getY(
-    const GameActivityPointerAxes* pointerInfo) {
-    return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
-}
-
-/**
- * \brief Enable the specified axis, so that its value is reported in the
- * GameActivityPointerAxes structures stored in a motion event.
- *
- * You must enable any axis that you want to read, apart from
- * `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by
- * default.
- *
- * If the axis index is out of range, nothing is done.
- */
-void GameActivityPointerAxes_enableAxis(int32_t axis);
-
-/**
- * \brief Disable the specified axis. Its value won't be reported in the
- * GameActivityPointerAxes structures stored in a motion event anymore.
- *
- * Apart from X and Y, any axis that you want to read **must** be enabled first,
- * using `GameActivityPointerAxes_enableAxis`.
- *
- * If the axis index is out of range, nothing is done.
- */
-void GameActivityPointerAxes_disableAxis(int32_t axis);
-
-/**
- * \brief Get the value of the requested axis.
- *
- * Apart from X and Y, any axis that you want to read **must** be enabled first,
- * using `GameActivityPointerAxes_enableAxis`.
- *
- * Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`,
- * `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...)
- * in https://developer.android.com/ndk/reference/group/input.
- *
- * @param pointerInfo The structure containing information about the pointer,
- * obtained from GameActivityMotionEvent.
- * @param axis The axis to get the value from
- * @return The value of the axis, or 0 if the axis is invalid or was not
- * enabled.
- */
-float GameActivityPointerAxes_getAxisValue(
-    const GameActivityPointerAxes* pointerInfo, int32_t axis);
-
-inline float GameActivityPointerAxes_getPressure(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_PRESSURE);
-}
-
-inline float GameActivityPointerAxes_getSize(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_SIZE);
-}
-
-inline float GameActivityPointerAxes_getTouchMajor(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_TOUCH_MAJOR);
-}
-
-inline float GameActivityPointerAxes_getTouchMinor(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_TOUCH_MINOR);
-}
-
-inline float GameActivityPointerAxes_getToolMajor(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_TOOL_MAJOR);
-}
-
-inline float GameActivityPointerAxes_getToolMinor(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_TOOL_MINOR);
-}
-
-inline float GameActivityPointerAxes_getOrientation(
-    const GameActivityPointerAxes* pointerInfo) {
-    return GameActivityPointerAxes_getAxisValue(pointerInfo,
-                                                AMOTION_EVENT_AXIS_ORIENTATION);
-}
-
-/**
- * The maximum number of pointers returned inside a motion event.
- */
-#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
-#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
-    GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
-#else
-#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
-#endif
-
-/**
- * \brief Describe a motion event that happened on the GameActivity SurfaceView.
- *
- * This is 1:1 mapping to the information contained in a Java `MotionEvent`
- * (see https://developer.android.com/reference/android/view/MotionEvent).
- */
-typedef struct GameActivityMotionEvent {
-    int32_t deviceId;
-    int32_t source;
-    int32_t action;
-
-    int64_t eventTime;
-    int64_t downTime;
-
-    int32_t flags;
-    int32_t metaState;
-
-    int32_t actionButton;
-    int32_t buttonState;
-    int32_t classification;
-    int32_t edgeFlags;
-
-    uint32_t pointerCount;
-    GameActivityPointerAxes
-        pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
-
-    int historySize;
-    long* historicalEventTimes;
-    float* historicalAxisValues;
-
-    float precisionX;
-    float precisionY;
-} GameActivityMotionEvent;
-
-/**
- * \brief Describe a key event that happened on the GameActivity SurfaceView.
- *
- * This is 1:1 mapping to the information contained in a Java `KeyEvent`
- * (see https://developer.android.com/reference/android/view/KeyEvent).
- */
-typedef struct GameActivityKeyEvent {
-    int32_t deviceId;
-    int32_t source;
-    int32_t action;
-
-    int64_t eventTime;
-    int64_t downTime;
-
-    int32_t flags;
-    int32_t metaState;
-
-    int32_t modifiers;
-    int32_t repeatCount;
-    int32_t keyCode;
-    int32_t scanCode;
-    int32_t unicodeChar;
-} GameActivityKeyEvent;
-
-float GameActivityMotionEvent_getHistoricalAxisValue(
-    const GameActivityMotionEvent* event, int axis, int pointerIndex,
-    int historyPos);
-
-inline int GameActivityMotionEvent_getHistorySize(
-    const GameActivityMotionEvent* event) {
-    return event->historySize;
-}
-
-inline float GameActivityMotionEvent_getHistoricalX(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalY(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalPressure(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalSize(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalTouchMajor(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalTouchMinor(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalToolMajor(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalToolMinor(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos);
-}
-
-inline float GameActivityMotionEvent_getHistoricalOrientation(
-    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
-    return GameActivityMotionEvent_getHistoricalAxisValue(
-        event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos);
-}
-
-/**
  * A function the user should call from their callback with the data, its length
  * and the library- supplied context.
  */
@@ -530,35 +271,14 @@
      * Call GameActivity_getWindowInsets to retrieve the insets themselves.
      */
     void (*onWindowInsetsChanged)(GameActivity* activity);
+
+    /**
+     * Callback called when the rectangle in the window where the content
+     * should be placed has changed.
+     */
+    void (*onContentRectChanged)(GameActivity *activity, const ARect *rect);
 } GameActivityCallbacks;
 
-/** \brief Handle the freeing of the GameActivityMotionEvent struct. */
-void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event);
-
-/**
- * \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
- *
- * This is done automatically by the GameActivity: see `onTouchEvent` to set
- * a callback to consume the received events.
- * This function can be used if you re-implement events handling in your own
- * activity.
- * Ownership of out_event is maintained by the caller.
- */
-void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
-                                      GameActivityMotionEvent* out_event);
-
-/**
- * \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
- *
- * This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
- * to set a callback to consume the received events.
- * This function can be used if you re-implement events handling in your own
- * activity.
- * Ownership of out_event is maintained by the caller.
- */
-void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
-                                   GameActivityKeyEvent* out_event);
-
 /**
  * This is the function that must be in the native code to instantiate the
  * application's native activity.  It is called with the activity instance (see
@@ -883,6 +603,30 @@
 void GameActivity_setImeEditorInfo(GameActivity* activity, int inputType,
                                    int actionId, int imeOptions);
 
+/**
+ * These are getters for Configuration class members. They may be called from
+ * any thread.
+ */
+int GameActivity_getOrientation(GameActivity* activity);
+int GameActivity_getColorMode(GameActivity* activity);
+int GameActivity_getDensityDpi(GameActivity* activity);
+float GameActivity_getFontScale(GameActivity* activity);
+int GameActivity_getFontWeightAdjustment(GameActivity* activity);
+int GameActivity_getHardKeyboardHidden(GameActivity* activity);
+int GameActivity_getKeyboard(GameActivity* activity);
+int GameActivity_getKeyboardHidden(GameActivity* activity);
+int GameActivity_getMcc(GameActivity* activity);
+int GameActivity_getMnc(GameActivity* activity);
+int GameActivity_getNavigation(GameActivity* activity);
+int GameActivity_getNavigationHidden(GameActivity* activity);
+int GameActivity_getOrientation(GameActivity* activity);
+int GameActivity_getScreenHeightDp(GameActivity* activity);
+int GameActivity_getScreenLayout(GameActivity* activity);
+int GameActivity_getScreenWidthDp(GameActivity* activity);
+int GameActivity_getSmallestScreenWidthDp(GameActivity* activity);
+int GameActivity_getTouchscreen(GameActivity* activity);
+int GameActivity_getUIMode(GameActivity* activity);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.cpp b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.cpp
new file mode 100644
index 0000000..2cf2356
--- /dev/null
+++ b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.cpp
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "GameActivityEvents.h"
+
+#include <sys/system_properties.h>
+
+#include <string>
+
+#include "GameActivityLog.h"
+
+// TODO(b/187147166): these functions were extracted from the Game SDK
+// (gamesdk/src/common/system_utils.h). system_utils.h/cpp should be used
+// instead.
+namespace {
+
+std::string getSystemPropViaGet(const char *key,
+                                const char *default_value = "") {
+    char buffer[PROP_VALUE_MAX + 1] = "";  // +1 for terminator
+    int bufferLen = __system_property_get(key, buffer);
+    if (bufferLen > 0)
+        return buffer;
+    else
+        return "";
+}
+
+std::string GetSystemProp(const char *key, const char *default_value = "") {
+    return getSystemPropViaGet(key, default_value);
+}
+
+int GetSystemPropAsInt(const char *key, int default_value = 0) {
+    std::string prop = GetSystemProp(key);
+    return prop == "" ? default_value : strtoll(prop.c_str(), nullptr, 10);
+}
+
+}  // anonymous namespace
+
+#ifndef NELEM
+#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
+#endif
+
+static bool enabledAxes[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT] = {
+    /* AMOTION_EVENT_AXIS_X */ true,
+    /* AMOTION_EVENT_AXIS_Y */ true,
+    // Disable all other axes by default (they can be enabled using
+    // `GameActivityPointerAxes_enableAxis`).
+    false};
+
+extern "C" void GameActivityPointerAxes_enableAxis(int32_t axis) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return;
+    }
+
+    enabledAxes[axis] = true;
+}
+
+float GameActivityPointerAxes_getAxisValue(
+    const GameActivityPointerAxes *pointerInfo, int32_t axis) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return 0;
+    }
+
+    if (!enabledAxes[axis]) {
+        ALOGW("Axis %d must be enabled before it can be accessed.", axis);
+        return 0;
+    }
+
+    return pointerInfo->axisValues[axis];
+}
+
+extern "C" void GameActivityPointerAxes_disableAxis(int32_t axis) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return;
+    }
+
+    enabledAxes[axis] = false;
+}
+
+float GameActivityMotionEvent_getHistoricalAxisValue(
+    const GameActivityMotionEvent *event, int axis, int pointerIndex,
+    int historyPos) {
+    if (axis < 0 || axis >= GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT) {
+        return 0;
+    }
+
+    if (!enabledAxes[axis]) {
+        ALOGW("Axis %d must be enabled before it can be accessed.", axis);
+        return 0;
+    }
+
+    return event->historicalAxisValues[event->pointerCount * historyPos + axis];
+}
+
+static struct {
+    jmethodID getDeviceId;
+    jmethodID getSource;
+    jmethodID getAction;
+
+    jmethodID getEventTime;
+    jmethodID getDownTime;
+
+    jmethodID getFlags;
+    jmethodID getMetaState;
+
+    jmethodID getActionButton;
+    jmethodID getButtonState;
+    jmethodID getClassification;
+    jmethodID getEdgeFlags;
+
+    jmethodID getHistorySize;
+    jmethodID getHistoricalEventTime;
+
+    jmethodID getPointerCount;
+    jmethodID getPointerId;
+
+    jmethodID getToolType;
+
+    jmethodID getRawX;
+    jmethodID getRawY;
+    jmethodID getXPrecision;
+    jmethodID getYPrecision;
+    jmethodID getAxisValue;
+
+    jmethodID getHistoricalAxisValue;
+} gMotionEventClassInfo;
+
+extern "C" void GameActivityMotionEvent_destroy(
+    GameActivityMotionEvent *c_event) {
+    delete c_event->historicalAxisValues;
+    delete c_event->historicalEventTimesMillis;
+    delete c_event->historicalEventTimesNanos;
+}
+
+extern "C" void GameActivityMotionEvent_fromJava(
+    JNIEnv *env, jobject motionEvent, GameActivityMotionEvent *out_event) {
+    static bool gMotionEventClassInfoInitialized = false;
+    if (!gMotionEventClassInfoInitialized) {
+        int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
+        gMotionEventClassInfo = {0};
+        jclass motionEventClass = env->FindClass("android/view/MotionEvent");
+        gMotionEventClassInfo.getDeviceId =
+            env->GetMethodID(motionEventClass, "getDeviceId", "()I");
+        gMotionEventClassInfo.getSource =
+            env->GetMethodID(motionEventClass, "getSource", "()I");
+        gMotionEventClassInfo.getAction =
+            env->GetMethodID(motionEventClass, "getAction", "()I");
+        gMotionEventClassInfo.getEventTime =
+            env->GetMethodID(motionEventClass, "getEventTime", "()J");
+        gMotionEventClassInfo.getDownTime =
+            env->GetMethodID(motionEventClass, "getDownTime", "()J");
+        gMotionEventClassInfo.getFlags =
+            env->GetMethodID(motionEventClass, "getFlags", "()I");
+        gMotionEventClassInfo.getMetaState =
+            env->GetMethodID(motionEventClass, "getMetaState", "()I");
+        if (sdkVersion >= 23) {
+            gMotionEventClassInfo.getActionButton =
+                env->GetMethodID(motionEventClass, "getActionButton", "()I");
+        }
+        if (sdkVersion >= 14) {
+            gMotionEventClassInfo.getButtonState =
+                env->GetMethodID(motionEventClass, "getButtonState", "()I");
+        }
+        if (sdkVersion >= 29) {
+            gMotionEventClassInfo.getClassification =
+                env->GetMethodID(motionEventClass, "getClassification", "()I");
+        }
+        gMotionEventClassInfo.getEdgeFlags =
+            env->GetMethodID(motionEventClass, "getEdgeFlags", "()I");
+
+        gMotionEventClassInfo.getHistorySize =
+            env->GetMethodID(motionEventClass, "getHistorySize", "()I");
+        gMotionEventClassInfo.getHistoricalEventTime = env->GetMethodID(
+            motionEventClass, "getHistoricalEventTime", "(I)J");
+
+        gMotionEventClassInfo.getPointerCount =
+            env->GetMethodID(motionEventClass, "getPointerCount", "()I");
+        gMotionEventClassInfo.getPointerId =
+            env->GetMethodID(motionEventClass, "getPointerId", "(I)I");
+        gMotionEventClassInfo.getToolType =
+            env->GetMethodID(motionEventClass, "getToolType", "(I)I");
+        if (sdkVersion >= 29) {
+            gMotionEventClassInfo.getRawX =
+                env->GetMethodID(motionEventClass, "getRawX", "(I)F");
+            gMotionEventClassInfo.getRawY =
+                env->GetMethodID(motionEventClass, "getRawY", "(I)F");
+        }
+        gMotionEventClassInfo.getXPrecision =
+            env->GetMethodID(motionEventClass, "getXPrecision", "()F");
+        gMotionEventClassInfo.getYPrecision =
+            env->GetMethodID(motionEventClass, "getYPrecision", "()F");
+        gMotionEventClassInfo.getAxisValue =
+            env->GetMethodID(motionEventClass, "getAxisValue", "(II)F");
+
+        gMotionEventClassInfo.getHistoricalAxisValue = env->GetMethodID(
+            motionEventClass, "getHistoricalAxisValue", "(III)F");
+        gMotionEventClassInfoInitialized = true;
+    }
+
+    int pointerCount =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getPointerCount);
+    pointerCount =
+        std::min(pointerCount, GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT);
+    out_event->pointerCount = pointerCount;
+    for (int i = 0; i < pointerCount; ++i) {
+        out_event->pointers[i] = {
+            /*id=*/env->CallIntMethod(motionEvent,
+                                      gMotionEventClassInfo.getPointerId, i),
+            /*toolType=*/
+            env->CallIntMethod(motionEvent, gMotionEventClassInfo.getToolType,
+                               i),
+            /*axisValues=*/{0},
+            /*rawX=*/gMotionEventClassInfo.getRawX
+                ? env->CallFloatMethod(motionEvent,
+                                       gMotionEventClassInfo.getRawX, i)
+                : 0,
+            /*rawY=*/gMotionEventClassInfo.getRawY
+                ? env->CallFloatMethod(motionEvent,
+                                       gMotionEventClassInfo.getRawY, i)
+                : 0,
+        };
+
+        for (int axisIndex = 0;
+             axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT; ++axisIndex) {
+            if (enabledAxes[axisIndex]) {
+                out_event->pointers[i].axisValues[axisIndex] =
+                    env->CallFloatMethod(motionEvent,
+                                         gMotionEventClassInfo.getAxisValue,
+                                         axisIndex, i);
+            }
+        }
+    }
+
+    int historySize =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getHistorySize);
+    out_event->historySize = historySize;
+    out_event->historicalAxisValues =
+        new float[historySize * pointerCount *
+                  GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
+    out_event->historicalEventTimesMillis = new long[historySize];
+    out_event->historicalEventTimesNanos = new long[historySize];
+
+    for (int historyIndex = 0; historyIndex < historySize; historyIndex++) {
+        out_event->historicalEventTimesMillis[historyIndex] =
+            env->CallLongMethod(motionEvent,
+                                gMotionEventClassInfo.getHistoricalEventTime,
+                                historyIndex);
+        out_event->historicalEventTimesNanos[historyIndex] =
+            out_event->historicalEventTimesMillis[historyIndex] * 1000000;
+        for (int i = 0; i < pointerCount; ++i) {
+            int pointerOffset = i * GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
+            int historyAxisOffset = historyIndex * pointerCount *
+                                    GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
+            float *axisValues =
+                &out_event
+                     ->historicalAxisValues[historyAxisOffset + pointerOffset];
+            for (int axisIndex = 0;
+                 axisIndex < GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT;
+                 ++axisIndex) {
+                if (enabledAxes[axisIndex]) {
+                    axisValues[axisIndex] = env->CallFloatMethod(
+                        motionEvent,
+                        gMotionEventClassInfo.getHistoricalAxisValue, axisIndex,
+                        i, historyIndex);
+                }
+            }
+        }
+    }
+
+    out_event->deviceId =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getDeviceId);
+    out_event->source =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getSource);
+    out_event->action =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getAction);
+    out_event->eventTime =
+        env->CallLongMethod(motionEvent, gMotionEventClassInfo.getEventTime) *
+        1000000;
+    out_event->downTime =
+        env->CallLongMethod(motionEvent, gMotionEventClassInfo.getDownTime) *
+        1000000;
+    out_event->flags =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getFlags);
+    out_event->metaState =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getMetaState);
+    out_event->actionButton =
+        gMotionEventClassInfo.getActionButton
+            ? env->CallIntMethod(motionEvent,
+                                 gMotionEventClassInfo.getActionButton)
+            : 0;
+    out_event->buttonState =
+        gMotionEventClassInfo.getButtonState
+            ? env->CallIntMethod(motionEvent,
+                                 gMotionEventClassInfo.getButtonState)
+            : 0;
+    out_event->classification =
+        gMotionEventClassInfo.getClassification
+            ? env->CallIntMethod(motionEvent,
+                                 gMotionEventClassInfo.getClassification)
+            : 0;
+    out_event->edgeFlags =
+        env->CallIntMethod(motionEvent, gMotionEventClassInfo.getEdgeFlags);
+    out_event->precisionX =
+        env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getXPrecision);
+    out_event->precisionY =
+        env->CallFloatMethod(motionEvent, gMotionEventClassInfo.getYPrecision);
+}
+
+static struct {
+    jmethodID getDeviceId;
+    jmethodID getSource;
+    jmethodID getAction;
+
+    jmethodID getEventTime;
+    jmethodID getDownTime;
+
+    jmethodID getFlags;
+    jmethodID getMetaState;
+
+    jmethodID getModifiers;
+    jmethodID getRepeatCount;
+    jmethodID getKeyCode;
+    jmethodID getScanCode;
+    jmethodID getUnicodeChar;
+} gKeyEventClassInfo;
+
+extern "C" void GameActivityKeyEvent_fromJava(JNIEnv *env, jobject keyEvent,
+                                              GameActivityKeyEvent *out_event) {
+    static bool gKeyEventClassInfoInitialized = false;
+    if (!gKeyEventClassInfoInitialized) {
+        int sdkVersion = GetSystemPropAsInt("ro.build.version.sdk");
+        gKeyEventClassInfo = {0};
+        jclass keyEventClass = env->FindClass("android/view/KeyEvent");
+        gKeyEventClassInfo.getDeviceId =
+            env->GetMethodID(keyEventClass, "getDeviceId", "()I");
+        gKeyEventClassInfo.getSource =
+            env->GetMethodID(keyEventClass, "getSource", "()I");
+        gKeyEventClassInfo.getAction =
+            env->GetMethodID(keyEventClass, "getAction", "()I");
+        gKeyEventClassInfo.getEventTime =
+            env->GetMethodID(keyEventClass, "getEventTime", "()J");
+        gKeyEventClassInfo.getDownTime =
+            env->GetMethodID(keyEventClass, "getDownTime", "()J");
+        gKeyEventClassInfo.getFlags =
+            env->GetMethodID(keyEventClass, "getFlags", "()I");
+        gKeyEventClassInfo.getMetaState =
+            env->GetMethodID(keyEventClass, "getMetaState", "()I");
+        if (sdkVersion >= 13) {
+            gKeyEventClassInfo.getModifiers =
+                env->GetMethodID(keyEventClass, "getModifiers", "()I");
+        }
+        gKeyEventClassInfo.getRepeatCount =
+            env->GetMethodID(keyEventClass, "getRepeatCount", "()I");
+        gKeyEventClassInfo.getKeyCode =
+            env->GetMethodID(keyEventClass, "getKeyCode", "()I");
+        gKeyEventClassInfo.getScanCode =
+            env->GetMethodID(keyEventClass, "getScanCode", "()I");
+        gKeyEventClassInfo.getUnicodeChar =
+            env->GetMethodID(keyEventClass, "getUnicodeChar", "()I");
+
+        gKeyEventClassInfoInitialized = true;
+    }
+
+    *out_event = {
+        /*deviceId=*/env->CallIntMethod(keyEvent,
+                                        gKeyEventClassInfo.getDeviceId),
+        /*source=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getSource),
+        /*action=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getAction),
+        // TODO: introduce a millisecondsToNanoseconds helper:
+        /*eventTime=*/
+        env->CallLongMethod(keyEvent, gKeyEventClassInfo.getEventTime) *
+            1000000,
+        /*downTime=*/
+        env->CallLongMethod(keyEvent, gKeyEventClassInfo.getDownTime) * 1000000,
+        /*flags=*/env->CallIntMethod(keyEvent, gKeyEventClassInfo.getFlags),
+        /*metaState=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getMetaState),
+        /*modifiers=*/gKeyEventClassInfo.getModifiers
+            ? env->CallIntMethod(keyEvent, gKeyEventClassInfo.getModifiers)
+            : 0,
+        /*repeatCount=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getRepeatCount),
+        /*keyCode=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getKeyCode),
+        /*scanCode=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getScanCode),
+        /*unicodeChar=*/
+        env->CallIntMethod(keyEvent, gKeyEventClassInfo.getUnicodeChar)};
+}
diff --git a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.h b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.h
new file mode 100644
index 0000000..4149dda
--- /dev/null
+++ b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityEvents.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+/**
+ * @addtogroup GameActivity Game Activity Events
+ * The interface to use Game Activity Events.
+ * @{
+ */
+
+/**
+ * @file GameActivityEvents.h
+ */
+#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
+#define ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
+
+#include <android/input.h>
+#include <jni.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The maximum number of axes supported in an Android MotionEvent.
+ * See https://developer.android.com/ndk/reference/group/input.
+ */
+#define GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT 48
+
+/**
+ * \brief Describe information about a pointer, found in a
+ * GameActivityMotionEvent.
+ *
+ * You can read values directly from this structure, or use helper functions
+ * (`GameActivityPointerAxes_getX`, `GameActivityPointerAxes_getY` and
+ * `GameActivityPointerAxes_getAxisValue`).
+ *
+ * The X axis and Y axis are enabled by default but any other axis that you want
+ * to read **must** be enabled first, using
+ * `GameActivityPointerAxes_enableAxis`.
+ *
+ * \see GameActivityMotionEvent
+ */
+typedef struct GameActivityPointerAxes {
+    int32_t id;
+    int32_t toolType;
+    float axisValues[GAME_ACTIVITY_POINTER_INFO_AXIS_COUNT];
+    float rawX;
+    float rawY;
+} GameActivityPointerAxes;
+
+/** \brief Get the toolType of the pointer. */
+inline int32_t GameActivityPointerAxes_getToolType(
+    const GameActivityPointerAxes* pointerInfo) {
+    return pointerInfo->toolType;
+}
+
+/** \brief Get the current X coordinate of the pointer. */
+inline float GameActivityPointerAxes_getX(
+    const GameActivityPointerAxes* pointerInfo) {
+    return pointerInfo->axisValues[AMOTION_EVENT_AXIS_X];
+}
+
+/** \brief Get the current Y coordinate of the pointer. */
+inline float GameActivityPointerAxes_getY(
+    const GameActivityPointerAxes* pointerInfo) {
+    return pointerInfo->axisValues[AMOTION_EVENT_AXIS_Y];
+}
+
+/**
+ * \brief Enable the specified axis, so that its value is reported in the
+ * GameActivityPointerAxes structures stored in a motion event.
+ *
+ * You must enable any axis that you want to read, apart from
+ * `AMOTION_EVENT_AXIS_X` and `AMOTION_EVENT_AXIS_Y` that are enabled by
+ * default.
+ *
+ * If the axis index is out of range, nothing is done.
+ */
+void GameActivityPointerAxes_enableAxis(int32_t axis);
+
+/**
+ * \brief Disable the specified axis. Its value won't be reported in the
+ * GameActivityPointerAxes structures stored in a motion event anymore.
+ *
+ * Apart from X and Y, any axis that you want to read **must** be enabled first,
+ * using `GameActivityPointerAxes_enableAxis`.
+ *
+ * If the axis index is out of range, nothing is done.
+ */
+void GameActivityPointerAxes_disableAxis(int32_t axis);
+
+/**
+ * \brief Get the value of the requested axis.
+ *
+ * Apart from X and Y, any axis that you want to read **must** be enabled first,
+ * using `GameActivityPointerAxes_enableAxis`.
+ *
+ * Find the valid enums for the axis (`AMOTION_EVENT_AXIS_X`,
+ * `AMOTION_EVENT_AXIS_Y`, `AMOTION_EVENT_AXIS_PRESSURE`...)
+ * in https://developer.android.com/ndk/reference/group/input.
+ *
+ * @param pointerInfo The structure containing information about the pointer,
+ * obtained from GameActivityMotionEvent.
+ * @param axis The axis to get the value from
+ * @return The value of the axis, or 0 if the axis is invalid or was not
+ * enabled.
+ */
+float GameActivityPointerAxes_getAxisValue(
+    const GameActivityPointerAxes* pointerInfo, int32_t axis);
+
+inline float GameActivityPointerAxes_getPressure(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_PRESSURE);
+}
+
+inline float GameActivityPointerAxes_getSize(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_SIZE);
+}
+
+inline float GameActivityPointerAxes_getTouchMajor(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+}
+
+inline float GameActivityPointerAxes_getTouchMinor(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_TOUCH_MINOR);
+}
+
+inline float GameActivityPointerAxes_getToolMajor(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_TOOL_MAJOR);
+}
+
+inline float GameActivityPointerAxes_getToolMinor(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_TOOL_MINOR);
+}
+
+inline float GameActivityPointerAxes_getOrientation(
+    const GameActivityPointerAxes* pointerInfo) {
+    return GameActivityPointerAxes_getAxisValue(pointerInfo,
+                                                AMOTION_EVENT_AXIS_ORIENTATION);
+}
+
+/**
+ * The maximum number of pointers returned inside a motion event.
+ */
+#if (defined GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE)
+#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT \
+    GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT_OVERRIDE
+#else
+#define GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT 8
+#endif
+
+/**
+ * \brief Describe a motion event that happened on the GameActivity SurfaceView.
+ *
+ * This is 1:1 mapping to the information contained in a Java `MotionEvent`
+ * (see https://developer.android.com/reference/android/view/MotionEvent).
+ */
+typedef struct GameActivityMotionEvent {
+    int32_t deviceId;
+    int32_t source;
+    int32_t action;
+
+    int64_t eventTime;
+    int64_t downTime;
+
+    int32_t flags;
+    int32_t metaState;
+
+    int32_t actionButton;
+    int32_t buttonState;
+    int32_t classification;
+    int32_t edgeFlags;
+
+    uint32_t pointerCount;
+    GameActivityPointerAxes
+        pointers[GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT];
+
+    int historySize;
+    long* historicalEventTimesMillis;
+    long* historicalEventTimesNanos;
+    float* historicalAxisValues;
+
+    float precisionX;
+    float precisionY;
+} GameActivityMotionEvent;
+
+float GameActivityMotionEvent_getHistoricalAxisValue(
+    const GameActivityMotionEvent* event, int axis, int pointerIndex,
+    int historyPos);
+
+inline int GameActivityMotionEvent_getHistorySize(
+    const GameActivityMotionEvent* event) {
+    return event->historySize;
+}
+
+inline float GameActivityMotionEvent_getHistoricalX(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_X, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalY(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_Y, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalPressure(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalSize(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_SIZE, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalTouchMajor(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalTouchMinor(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalToolMajor(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalToolMinor(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historyPos);
+}
+
+inline float GameActivityMotionEvent_getHistoricalOrientation(
+    const GameActivityMotionEvent* event, int pointerIndex, int historyPos) {
+    return GameActivityMotionEvent_getHistoricalAxisValue(
+        event, AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historyPos);
+}
+
+/** \brief Handle the freeing of the GameActivityMotionEvent struct. */
+void GameActivityMotionEvent_destroy(GameActivityMotionEvent* c_event);
+
+/**
+ * \brief Convert a Java `MotionEvent` to a `GameActivityMotionEvent`.
+ *
+ * This is done automatically by the GameActivity: see `onTouchEvent` to set
+ * a callback to consume the received events.
+ * This function can be used if you re-implement events handling in your own
+ * activity.
+ * Ownership of out_event is maintained by the caller.
+ */
+void GameActivityMotionEvent_fromJava(JNIEnv* env, jobject motionEvent,
+                                      GameActivityMotionEvent* out_event);
+
+/**
+ * \brief Describe a key event that happened on the GameActivity SurfaceView.
+ *
+ * This is 1:1 mapping to the information contained in a Java `KeyEvent`
+ * (see https://developer.android.com/reference/android/view/KeyEvent).
+ * The only exception is the event times, which are reported as
+ * nanoseconds in this struct.
+ */
+typedef struct GameActivityKeyEvent {
+    int32_t deviceId;
+    int32_t source;
+    int32_t action;
+
+    int64_t eventTime;
+    int64_t downTime;
+
+    int32_t flags;
+    int32_t metaState;
+
+    int32_t modifiers;
+    int32_t repeatCount;
+    int32_t keyCode;
+    int32_t scanCode;
+    int32_t unicodeChar;
+} GameActivityKeyEvent;
+
+/**
+ * \brief Convert a Java `KeyEvent` to a `GameActivityKeyEvent`.
+ *
+ * This is done automatically by the GameActivity: see `onKeyUp` and `onKeyDown`
+ * to set a callback to consume the received events.
+ * This function can be used if you re-implement events handling in your own
+ * activity.
+ * Ownership of out_event is maintained by the caller.
+ */
+void GameActivityKeyEvent_fromJava(JNIEnv* env, jobject motionEvent,
+                                   GameActivityKeyEvent* out_event);
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @} */
+
+#endif  // ANDROID_GAME_SDK_GAME_ACTIVITY_EVENTS_H
diff --git a/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityLog.h b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityLog.h
new file mode 100644
index 0000000..ba9a9e9
--- /dev/null
+++ b/game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivityLog.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#ifndef ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
+#define ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
+
+#define LOG_TAG "GameActivity"
+#include <android/log.h>
+
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
+#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__);
+#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
+#ifdef NDEBUG
+#define ALOGV(...)
+#else
+#define ALOGV(...) \
+    __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
+#endif
+
+/* Returns 2nd arg.  Used to substitute default value if caller's vararg list
+ * is empty.
+ */
+#define __android_second(first, second, ...) second
+
+/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
+ * returns nothing.
+ */
+#define __android_rest(first, ...) , ##__VA_ARGS__
+
+#define android_printAssert(cond, tag, fmt...) \
+    __android_log_assert(cond, tag,            \
+                         __android_second(0, ##fmt, NULL) __android_rest(fmt))
+
+#define CONDITION(cond) (__builtin_expect((cond) != 0, 0))
+
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...)                                \
+    ((CONDITION(cond))                                                \
+         ? ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__)) \
+         : (void)0)
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+    (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__)))
+#endif
+
+/*
+ * Simplified macro to send a warning system log message using current LOG_TAG.
+ */
+#ifndef SLOGW
+#define SLOGW(...) \
+    ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGW_IF
+#define SLOGW_IF(cond, ...)                                                    \
+    ((__predict_false(cond))                                                   \
+         ? ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+         : (void)0)
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) ((void)0)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) ((void)0)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds.  Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
+#endif
+
+#define LOG_TRACE(...)
+
+#endif  // ANDROID_GAME_SDK_GAME_ACTIVITY_LOG_H_
diff --git a/game-activity/prefab-src/modules/game-activity/include/game-activity/native_app_glue/android_native_app_glue.c b/game-activity/prefab-src/modules/game-activity/include/game-activity/native_app_glue/android_native_app_glue.c
index f4fff5f..ebc265e 100644
--- a/game-activity/prefab-src/modules/game-activity/include/game-activity/native_app_glue/android_native_app_glue.c
+++ b/game-activity/prefab-src/modules/game-activity/include/game-activity/native_app_glue/android_native_app_glue.c
@@ -593,6 +593,20 @@
     android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_INSETS_CHANGED);
 }
 
+static void onContentRectChanged(GameActivity* activity, const ARect *rect) {
+    LOGV("ContentRectChanged: %p -- (%d %d) (%d %d)", activity, rect->left, rect->top,
+         rect->right, rect->bottom);
+
+    struct android_app* android_app = ToApp(activity);
+
+    pthread_mutex_lock(&android_app->mutex);
+    android_app->contentRect = *rect;
+
+    android_app_write_cmd(android_app, APP_CMD_CONTENT_RECT_CHANGED);
+    pthread_mutex_unlock(&android_app->mutex);
+}
+
+
 JNIEXPORT
 void GameActivity_onCreate(GameActivity* activity, void* savedState,
                            size_t savedStateSize) {
@@ -616,6 +630,7 @@
         onNativeWindowRedrawNeeded;
     activity->callbacks->onNativeWindowResized = onNativeWindowResized;
     activity->callbacks->onWindowInsetsChanged = onWindowInsetsChanged;
+    activity->callbacks->onContentRectChanged = onContentRectChanged;
     LOGV("Callbacks set: %p", activity->callbacks);
 
     activity->instance =
diff --git a/game-activity/src/main/java/com/google/androidgamesdk/GameActivity.java b/game-activity/src/main/java/com/google/androidgamesdk/GameActivity.java
index fa43611..00c2890 100644
--- a/game-activity/src/main/java/com/google/androidgamesdk/GameActivity.java
+++ b/game-activity/src/main/java/com/google/androidgamesdk/GameActivity.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
+import android.content.Context;
 import android.graphics.PixelFormat;
 import android.os.Build;
 import android.os.Bundle;
@@ -58,7 +59,8 @@
 
 public class GameActivity
     extends AppCompatActivity
-    implements SurfaceHolder.Callback2, Listener, OnApplyWindowInsetsListener {
+    implements SurfaceHolder.Callback2, Listener, OnApplyWindowInsetsListener,
+        OnGlobalLayoutListener {
   private static final String LOG_TAG = "GameActivity";
 
   private static final String DEFAULT_NATIVE_LIB_NAME = "main";
@@ -110,6 +112,27 @@
     onTextInputEventNative(mNativeHandle, newState);
   }
 
+  @Override
+  public void onGlobalLayout() {
+    mSurfaceView.getLocationInWindow(mLocation);
+    int w = mSurfaceView.getWidth();
+    int h = mSurfaceView.getHeight();
+
+   if (mLocation[0] != mLastContentX || mLocation[1] != mLastContentY
+           || w != mLastContentWidth || h != mLastContentHeight)
+    {
+      mLastContentX = mLocation[0];
+      mLastContentY = mLocation[1];
+      mLastContentWidth = w;
+      mLastContentHeight = h;
+
+      if (!mDestroyed) {
+        onContentRectChangedNative(mNativeHandle, mLastContentX, mLastContentY,
+                mLastContentWidth, mLastContentHeight);
+      }
+    }
+  }
+
   // Called when we want to set the input state, e.g. before first showing the IME
   public void setTextInputState(State s) {
     if (mSurfaceView == null) return;
@@ -125,11 +148,16 @@
   private SurfaceHolder mCurSurfaceHolder;
 
   protected final int[] mLocation = new int[2];
+  protected int mLastContentX;
+  protected int mLastContentY;
+  protected int mLastContentWidth;
+  protected int mLastContentHeight;
+
 
   protected boolean mDestroyed;
 
   protected native long initializeNativeCode(String internalDataPath, String obbPath,
-      String externalDataPath, AssetManager assetMgr, byte[] savedState);
+      String externalDataPath, AssetManager assetMgr, byte[] savedState, Configuration config);
 
   protected native String getDlError();
 
@@ -145,7 +173,7 @@
 
   protected native void onStopNative(long handle);
 
-  protected native void onConfigurationChangedNative(long handle);
+  protected native void onConfigurationChangedNative(long handle, Configuration newConfig);
 
   protected native void onTrimMemoryNative(long handle, int level);
 
@@ -172,6 +200,8 @@
 
   protected native void onWindowInsetsChangedNative(long handle);
 
+  protected native void onContentRectChangedNative(long handle, int x, int y, int w, int h);
+
   /**
    * Get the pointer to the C `GameActivity` struct associated to this activity.
    * @return the pointer to the C `GameActivity` struct associated to this activity.
@@ -226,6 +256,11 @@
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     onCreateSurfaceView();
+
+    if (mSurfaceView != null) {
+      mSurfaceView.getViewTreeObserver().addOnGlobalLayoutListener(this);
+    }
+
     onSetUpWindow();
 
     String libname = new String(DEFAULT_NATIVE_LIB_NAME);
@@ -269,8 +304,8 @@
         savedInstanceState != null ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
 
     mNativeHandle = initializeNativeCode(getAbsolutePath(getFilesDir()),
-        getAbsolutePath(getObbDir()), getAbsolutePath(getExternalFilesDir(null)),
-        getAssets(), nativeSavedState);
+        getAbsolutePath(getObbDir()), getAbsolutePath(getExternalFilesDir(null)), getAssets(),
+        nativeSavedState, getResources().getConfiguration());
 
     if (mNativeHandle == 0) {
       throw new UnsatisfiedLinkError(
@@ -338,7 +373,7 @@
   public void onConfigurationChanged(Configuration newConfig) {
     super.onConfigurationChanged(newConfig);
     if (!mDestroyed) {
-      onConfigurationChangedNative(mNativeHandle);
+      onConfigurationChangedNative(mNativeHandle, newConfig);
     }
   }
 
diff --git a/game-text-input/CMakeLists.txt b/game-text-input/CMakeLists.txt
index 2a7b757..a59d2f5 100644
--- a/game-text-input/CMakeLists.txt
+++ b/game-text-input/CMakeLists.txt
@@ -18,6 +18,7 @@
 set(CMAKE_CXX_STANDARD 17)
 
 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/prefab-src/modules/game-text-input/include/)
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include/)
 
 set(GAMETEXTINPUT_SRCS
     ./prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp)
diff --git a/game-text-input/prefab-src/modules/game-text-input/include/common b/game-text-input/prefab-src/modules/game-text-input/include/common
new file mode 120000
index 0000000..9388f34
--- /dev/null
+++ b/game-text-input/prefab-src/modules/game-text-input/include/common
@@ -0,0 +1 @@
+../../../../../include/common/
\ No newline at end of file
diff --git a/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp b/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp
index 6a39943..b8244fb 100644
--- a/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp
+++ b/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.cpp
@@ -48,6 +48,7 @@
     void processEvent(jobject textInputEvent);
     void showIme(uint32_t flags);
     void hideIme(uint32_t flags);
+    void restartInput();
     void setEventCallback(GameTextInputEventCallback callback, void *context);
     jobject stateToJava(const GameTextInputState &state) const;
     void stateFromJava(jobject textInputEvent,
@@ -73,6 +74,7 @@
     jobject inputConnection_ = nullptr;
     jmethodID inputConnectionSetStateMethod_;
     jmethodID setSoftKeyboardActiveMethod_;
+    jmethodID restartInputMethod_;
     void (*eventCallback_)(void *context,
                            const struct GameTextInputState *state) = nullptr;
     void *eventCallbackContext_ = nullptr;
@@ -164,6 +166,10 @@
     input->hideIme(flags);
 }
 
+void GameTextInput_restartInput(struct GameTextInput *input) {
+    input->restartInput();
+}
+
 void GameTextInput_setEventCallback(struct GameTextInput *input,
                                     GameTextInputEventCallback callback,
                                     void *context) {
@@ -199,6 +205,8 @@
                           "(Lcom/google/androidgamesdk/gametextinput/State;)V");
     setSoftKeyboardActiveMethod_ = env_->GetMethodID(
         inputConnectionClass_, "setSoftKeyboardActive", "(ZI)V");
+    restartInputMethod_ =
+        env_->GetMethodID(inputConnectionClass_, "restartInput", "()V");
 
     stateClassInfo_.text =
         env_->GetFieldID(stateJavaClass_, "text", "Ljava/lang/String;");
@@ -307,6 +315,11 @@
                          flags);
 }
 
+void GameTextInput::restartInput() {
+    if (inputConnection_ == nullptr) return;
+    env_->CallVoidMethod(inputConnection_, restartInputMethod_, false);
+}
+
 jobject GameTextInput::stateToJava(const GameTextInputState &state) const {
     static jmethodID constructor = nullptr;
     if (constructor == nullptr) {
diff --git a/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h b/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h
index a85265e..8c33da5 100644
--- a/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h
+++ b/game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h
@@ -26,12 +26,21 @@
 #include <jni.h>
 #include <stdint.h>
 
+#include "common/gamesdk_common.h"
 #include "gamecommon.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+#define GAMETEXTINPUT_MAJOR_VERSION 2
+#define GAMETEXTINPUT_MINOR_VERSION 0
+#define GAMETEXTINPUT_BUGFIX_VERSION 0
+#define GAMETEXTINPUT_PACKED_VERSION                            \
+    ANDROID_GAMESDK_PACKED_VERSION(GAMETEXTINPUT_MAJOR_VERSION, \
+                                   GAMETEXTINPUT_MINOR_VERSION, \
+                                   GAMETEXTINPUT_BUGFIX_VERSION)
+
 /**
  * This struct holds a span within a region of text from start (inclusive) to
  * end (exclusive). An empty span or cursor position is specified with
@@ -174,6 +183,12 @@
 void GameTextInput_hideIme(GameTextInput *input, uint32_t flags);
 
 /**
+ * Restarts the input method. Calls InputMethodManager.restartInput().
+ * @param input A valid GameTextInput library handle.
+ */
+void GameTextInput_restartInput(GameTextInput *input);
+
+/**
  * Call a callback with the current GameTextInput state, which may have been
  * modified by changes in the IME and calls to GameTextInput_setState. We use a
  * callback rather than returning the state in order to simplify ownership of
diff --git a/games-controller/src/main/cpp/paddleboat/include/common b/games-controller/src/main/cpp/paddleboat/include/common
new file mode 120000
index 0000000..a3ff523
--- /dev/null
+++ b/games-controller/src/main/cpp/paddleboat/include/common
@@ -0,0 +1 @@
+../../../../../../include/common/
\ No newline at end of file
diff --git a/games-controller/src/main/cpp/paddleboat/include/paddleboat.h b/games-controller/src/main/cpp/paddleboat/include/paddleboat.h
index b1d5c2c..20a91e2 100644
--- a/games-controller/src/main/cpp/paddleboat/include/paddleboat.h
+++ b/games-controller/src/main/cpp/paddleboat/include/paddleboat.h
@@ -37,10 +37,20 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+#include "common/gamesdk_common.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+#define PADDLEBOAT_MAJOR_VERSION 2
+#define PADDLEBOAT_MINOR_VERSION 0
+#define PADDLEBOAT_BUGFIX_VERSION 0
+#define PADDLEBOAT_PACKED_VERSION                            \
+    ANDROID_GAMESDK_PACKED_VERSION(PADDLEBOAT_MAJOR_VERSION, \
+                                   PADDLEBOAT_MINOR_VERSION, \
+                                   PADDLEBOAT_BUGFIX_VERSION)
+
 /**
  * @brief Maximum number of simultaneously connected controllers.
  */
diff --git a/games-controller/src/main/cpp/paddleboat_c.cpp b/games-controller/src/main/cpp/paddleboat_c.cpp
index d6bc14e..5b4d443 100644
--- a/games-controller/src/main/cpp/paddleboat_c.cpp
+++ b/games-controller/src/main/cpp/paddleboat_c.cpp
@@ -21,15 +21,6 @@
 
 extern "C" {
 
-// Internal macros to track Paddleboat version, do not use directly.
-#define PADDLEBOAT_MAJOR_VERSION 1
-#define PADDLEBOAT_MINOR_VERSION 2
-#define PADDLEBOAT_BUGFIX_VERSION 0
-
-#define PADDLEBOAT_PACKED_VERSION                                          \
-    ((PADDLEBOAT_MAJOR_VERSION << 24) | (PADDLEBOAT_MINOR_VERSION << 16) | \
-     (PADDLEBOAT_BUGFIX_VERSION))
-
 #define PADDLEBOAT_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX) \
     PREFIX##_##MAJOR##_##MINOR##_##BUGFIX
 #define PADDLEBOAT_VERSION_CONCAT(PREFIX, MAJOR, MINOR, BUGFIX) \
diff --git a/games-memory-advice/core/memory_advice_internal.h b/games-memory-advice/core/memory_advice_internal.h
index fde1a34..12c84c2 100644
--- a/games-memory-advice/core/memory_advice_internal.h
+++ b/games-memory-advice/core/memory_advice_internal.h
@@ -19,15 +19,6 @@
 #include "memory_advice/memory_advice.h"
 #include "memory_advice/memory_advice_debug.h"
 
-// Memory advice version symbols for tracking the version.
-#define MEMORY_ADVICE_MAJOR_VERSION 1
-#define MEMORY_ADVICE_MINOR_VERSION 0
-#define MEMORY_ADVICE_BUGFIX_VERSION 0
-#define MEMORY_ADVICE_PACKED_VERSION                            \
-    ANDROID_GAMESDK_PACKED_VERSION(MEMORY_ADVICE_MAJOR_VERSION, \
-                                   MEMORY_ADVICE_MINOR_VERSION, \
-                                   MEMORY_ADVICE_BUGFIX_VERSION)
-
 // Internal macros to generate a symbol to track Memory Advice version, do not
 // use directly.
 #define MEMORY_ADVICE_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR) \
diff --git a/games-memory-advice/core/predictor.cpp b/games-memory-advice/core/predictor.cpp
index 11cf332..16b2b47 100644
--- a/games-memory-advice/core/predictor.cpp
+++ b/games-memory-advice/core/predictor.cpp
@@ -45,6 +45,10 @@
                                               std::string features_file) {
     apk_utils::NativeAsset features_asset(features_file.c_str());
 
+    if (!features_asset.IsValid()) {
+        return MEMORYADVICE_ERROR_TFLITE_MODEL_INVALID;
+    }
+
     // Get the features list from the corresponding asset,
     // which is a list of strings denoted with quotation marks
     std::string features_string(
@@ -71,6 +75,10 @@
 
     // Read the tflite model from the given asset file
     model_asset = std::make_unique<apk_utils::NativeAsset>(model_file.c_str());
+
+    if (!model_asset->IsValid()) {
+        return MEMORYADVICE_ERROR_TFLITE_MODEL_INVALID;
+    }
     const char* model_buffer =
         static_cast<const char*>(AAsset_getBuffer(*model_asset));
     const size_t model_capacity =
diff --git a/games-performance-tuner/core/request_info.cpp b/games-performance-tuner/core/request_info.cpp
index b9968ea..ba45462 100644
--- a/games-performance-tuner/core/request_info.cpp
+++ b/games-performance-tuner/core/request_info.cpp
@@ -127,6 +127,14 @@
             info.soc_manufacturer =
                 gamesdk::jni::android::os::Build::SOC_MANUFACTURER().C();
         }
+
+        gamesdk::jni::android::util::DisplayMetrics display_metrics;
+        gamesdk::jni::AppContext()
+            .getWindowManager()
+            .getDefaultDisplay()
+            .getMetrics(display_metrics);
+        info.height_pixels = display_metrics.heightPixels();
+        info.width_pixels = display_metrics.widthPixels();
     }
     info.tuningfork_version = TUNINGFORK_PACKED_VERSION;
     info.swappy_version = settings.c_settings.swappy_version;
diff --git a/games-performance-tuner/core/request_info.h b/games-performance-tuner/core/request_info.h
index 17db1c0..7bc8d63 100644
--- a/games-performance-tuner/core/request_info.h
+++ b/games-performance-tuner/core/request_info.h
@@ -51,6 +51,8 @@
     std::string soc_manufacturer;
     int64_t swap_total_bytes;
     uint32_t swappy_version;
+    int32_t height_pixels;
+    int32_t width_pixels;
 
     // Note that this will include an empty experiment_id and
     // current_fidelity_parameters.
diff --git a/games-performance-tuner/core/tuningfork_utils.cpp b/games-performance-tuner/core/tuningfork_utils.cpp
index a86373b..0cd04bc 100644
--- a/games-performance-tuner/core/tuningfork_utils.cpp
+++ b/games-performance-tuner/core/tuningfork_utils.cpp
@@ -249,7 +249,9 @@
                         {"soc_model", request_info.soc_model},
                         {"soc_manufacturer", request_info.soc_manufacturer},
                         {"swap_total_bytes",
-                         static_cast<double>(request_info.swap_total_bytes)}};
+                         static_cast<double>(request_info.swap_total_bytes)},
+                        {"height_pixels", request_info.height_pixels},
+                        {"width_pixels", request_info.width_pixels}};
 }
 
 }  // namespace json_utils
diff --git a/hooks/check_bugfix_version.sh b/hooks/check_bugfix_version.sh
deleted file mode 100755
index aba002f..0000000
--- a/hooks/check_bugfix_version.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-SWAPPY_PATTERN="games-frame-pacing/.*[.](cpp|h)$"
-TUNINGFORK_PATTERN="src/tuningfork/.*[.](cpp|h)$"
-
-SWAPPY_COMMON_H="include/swappy/swappy_common.h"
-TUNINGFORK_H="include/tuningfork/tuningfork.h"
-
-files=$2
-swappyChanged=0
-tuningforkChanged=0
-for file in ${files[@]}; do
-    echo $file
-
-    if [[ $file =~ $SWAPPY_PATTERN ]]; then
-        swappyChanged=1
-    fi
-    if [[ $file =~ $TUNINGFORK_PATTERN ]]; then
-        tuningforkChanged=1
-    fi
-done
-
-echo "Swappy changed: $swappyChanged"
-echo "TuningFork changed: $tuningforkChanged"
-
-# TODO (willosborn): check that swappy and tuningfork versions match those in VERSIONS
-
-exit 0
diff --git a/hooks/check_library_versions.sh b/hooks/check_library_versions.sh
new file mode 100755
index 0000000..ce2b95f
--- /dev/null
+++ b/hooks/check_library_versions.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+SWAPPY_COMMON_H="include/swappy/swappy_common.h"
+TUNINGFORK_H="include/tuningfork/tuningfork.h"
+
+VERSIONS="VERSIONS"
+
+library_names=(games-frame-pacing games-performance-tuner game-activity game-text-input games-controller games-memory-advice)
+header_files=(include/swappy/swappy_common.h include/tuningfork/tuningfork.h game-activity/prefab-src/modules/game-activity/include/game-activity/GameActivity.h game-text-input/prefab-src/modules/game-text-input/include/game-text-input/gametextinput.h include/paddleboat/paddleboat.h include/memory_advice/memory_advice.h)
+library_version_prefix=(SWAPPY TUNINGFORK GAMEACTIVITY GAMETEXTINPUT PADDLEBOAT MEMORY_ADVICE)
+
+
+for i in "${!library_names[@]}"; do
+  library="${library_names[i]}"
+  header="${header_files[i]}"
+  prefix="${library_version_prefix[i]}"
+  [[ `cat VERSIONS` =~ $library[[:space:]]*([0-9]+.[0-9]+.[0-9]+)[[:space:]]*([a-z]*) ]]
+  versions_version="${BASH_REMATCH[1]}"
+  version_suffix="${BASH_REMATCH[2]}"
+  [[ `cat $header` =~ ${prefix}_MAJOR_VERSION.([0-9]+) ]]
+  major="${BASH_REMATCH[1]}"
+  [[ `cat $header` =~ ${prefix}_MINOR_VERSION.([0-9]+) ]]
+  minor="${BASH_REMATCH[1]}"
+  [[ `cat $header` =~ ${prefix}_BUGFIX_VERSION.([0-9]+) ]]
+  bugfix="${BASH_REMATCH[1]}"
+  header_version="${major}.${minor}.${bugfix}"
+  if [[ "${major}.${minor}.${bugfix}" != $versions_version ]]; then
+    echo "Version mismatch! For ${library}, the version declared in its header is ${header_version} but the version declared in the VERSIONS file is ${versions_version}."
+    exit 1
+  fi
+  if [[ $version_suffix =~ "alpha" ]] && [[ $bugfix != "0" ]]; then
+    echo "Invalid version! ${library} is declared as an alpha release, but has a bugfix version."
+    exit 1
+  fi
+  if [[ $version_suffix =~ "beta" ]] && [[ $bugfix != "0" ]]; then
+    echo "Invalid version! ${library} is declared as a beta release, but has a bugfix version."
+    exit 1
+  fi
+done
+
+exit 0
diff --git a/hooks/check_swappy_abi_version.sh b/hooks/check_swappy_abi_version.sh
deleted file mode 100755
index 7c2957d..0000000
--- a/hooks/check_swappy_abi_version.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-
-SWAPPY_GL_H="include/swappy/swappyGL.h"
-SWAPPY_GL_EXTRA_H="include/swappy/swappyGL_extra.h"
-SWAPPY_VK_H="include/swappy/swappyVk.h"
-SWAPPY_VK_EXTRA_H="include/swappy/swappyVk_extra.h"
-SWAPPY_COMMON_H="include/swappy/swappy_common.h"
-
-SWAPPY_HEADERS=($SWAPPY_GL_H $SWAPPY_GL_EXTRA_H $SWAPPY_VK_H $SWAPPY_VK_EXTRA_H $SWAPPY_COMMON_H)
-
-files=$2
-headerChanged=0
-for file in ${files[@]}; do
-    for header in ${SWAPPY_HEADERS[@]}; do
-      if [[ $file =~ $header ]]; then
-        headerChanged=1
-        break
-      fi
-    done
-done
-
-if [ $headerChanged -eq "1" ]; then
-    majorVersion=`git show $1 $SWAPPY_COMMON_H | grep "#define SWAPPY_MAJOR_VERSION" | wc -l`
-    minorVersion=`git show $1 $SWAPPY_COMMON_H | grep "#define SWAPPY_MINOR_VERSION" | wc -l`
-    # echo "version_files $version_files"
-    if [[ $majorVersion -eq "0" && $minorVersion -eq "0" ]]; then
-	echo "Warning: did you break the Swappy ABI without changing SWAPPY_MAJOR_VERSION or SWAPPY_MINOR_VERSION ?"
-    fi
-
-fi
-
-exit 0
diff --git a/hooks/check_tuningfork_abi_version.sh b/hooks/check_tuningfork_abi_version.sh
deleted file mode 100755
index fdf30cb..0000000
--- a/hooks/check_tuningfork_abi_version.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-
-TUNINGFORK_H="include/tuningfork/tuningfork.h"
-TUNINGFORK_EXTRA_H="include/tuningfork/tuningfork_extra.h"
-UNITY_TUNINGFORK_H="include/tuningfork/unity_tuningfork.h"
-TUNINGFORK_HEADERS=($TUNINGFORK_H $TUNINGFORK_EXTRA_H $UNITY_TUNINGFORK_H)
-
-files=$2
-headerChanged=0
-for file in ${files[@]}; do
-    for header in ${TUNINGFORK_HEADERS[@]}; do
-      if [[ $file =~ $header ]]; then
-        headerChanged=1
-        break
-      fi
-    done
-done
-
-if [ $headerChanged -eq "1" ]; then
-
-    majorVersion=`git show $1 $TUNINGFORK_H | grep "#define TUNINGFORK_MAJOR_VERSION" | wc -l`
-    minorVersion=`git show $1 $TUNINGFORK_H | grep "#define TUNINGFORK_MINOR_VERSION" | wc -l`
-    # echo "version_files $version_files"
-    if [[ $majorVersion -eq "0" && $minorVersion -eq "0" ]]; then
-      echo "Warning: did you break the TuningFork ABI without changing TUNINGFORK_MAJOR_VERSION or TUNINGFORK_MINOR_VERSION ?"
-    fi
-
-fi
-
-exit 0
diff --git a/include/common/gamesdk_common.h b/include/common/gamesdk_common.h
index 8512262..d29ac01 100644
--- a/include/common/gamesdk_common.h
+++ b/include/common/gamesdk_common.h
@@ -31,11 +31,11 @@
 
 // There are separate versions for each GameSDK component that use this format:
 #define ANDROID_GAMESDK_PACKED_VERSION(MAJOR, MINOR, BUGFIX) \
-    ((MAJOR << 16) | (MINOR) | (BUGFIX << 8))
+    ((MAJOR << 16) | (MINOR << 8) | (BUGFIX))
 // Accessors
 #define ANDROID_GAMESDK_MAJOR_VERSION(PACKED) ((PACKED) >> 16)
-#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) ((PACKED)&0xff)
-#define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
+#define ANDROID_GAMESDK_MINOR_VERSION(PACKED) (((PACKED) >> 8) & 0xff)
+#define ANDROID_GAMESDK_BUGFIX_VERSION(PACKED) ((PACKED) & 0xff)
 
 #define AGDK_STRING_VERSION(MAJOR, MINOR, BUGFIX, GIT) \
 #MAJOR "." #MINOR "." #BUGFIX "." #GIT
diff --git a/include/memory_advice/memory_advice.h b/include/memory_advice/memory_advice.h
index 7d714e9..340744e 100644
--- a/include/memory_advice/memory_advice.h
+++ b/include/memory_advice/memory_advice.h
@@ -25,10 +25,20 @@
 #include <jni.h>
 #include <stdint.h>
 
+#include "common/gamesdk_common.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+#define MEMORY_ADVICE_MAJOR_VERSION 2
+#define MEMORY_ADVICE_MINOR_VERSION 0
+#define MEMORY_ADVICE_BUGFIX_VERSION 0
+#define MEMORY_ADVICE_PACKED_VERSION                         \
+    ANDROID_GAMESDK_PACKED_VERSION(TUNINGFORK_MAJOR_VERSION, \
+                                   TUNINGFORK_MINOR_VERSION, \
+                                   TUNINGFORK_BUGFIX_VERSION)
+
 /**
  * @brief All the error codes that can be returned by MemoryAdvice functions.
  */
diff --git a/include/swappy/swappyVk.h b/include/swappy/swappyVk.h
index 4bd7d0d..847bc75 100644
--- a/include/swappy/swappyVk.h
+++ b/include/swappy/swappyVk.h
@@ -25,7 +25,9 @@
 #include "jni.h"
 #include "swappy_common.h"
 
+#ifndef VK_NO_PROTOTYPES
 #define VK_NO_PROTOTYPES 1
+#endif
 #include <vulkan/vulkan.h>
 
 #ifdef __cplusplus
@@ -261,6 +263,14 @@
 void SwappyVk_injectTracer(const SwappyTracer* tracer);
 
 /**
+ * @brief Remove callbacks that were previously added using
+ * SwappyVk_injectTracer.
+ *
+ * @param[in]  tracer - Collection of callback functions
+ */
+void SwappyVk_uninjectTracer(const SwappyTracer* tracer);
+
+/**
  * @brief A structure enabling you to provide your own Vulkan function wrappers
  * by calling ::SwappyVk_setFunctionProvider.
  *
diff --git a/include/swappy/swappy_common.h b/include/swappy/swappy_common.h
index 6797521..ed2386f 100644
--- a/include/swappy/swappy_common.h
+++ b/include/swappy/swappy_common.h
@@ -47,8 +47,8 @@
 #define SWAPPY_SYSTEM_PROP_KEY_DISABLE "swappy.disable"
 
 // Internal macros to track Swappy version, do not use directly.
-#define SWAPPY_MAJOR_VERSION 1
-#define SWAPPY_MINOR_VERSION 10
+#define SWAPPY_MAJOR_VERSION 2
+#define SWAPPY_MINOR_VERSION 0
 #define SWAPPY_BUGFIX_VERSION 0
 #define SWAPPY_PACKED_VERSION                                                  \
     ANDROID_GAMESDK_PACKED_VERSION(SWAPPY_MAJOR_VERSION, SWAPPY_MINOR_VERSION, \
diff --git a/include/tuningfork/tuningfork.h b/include/tuningfork/tuningfork.h
index 237e058..1395c41 100644
--- a/include/tuningfork/tuningfork.h
+++ b/include/tuningfork/tuningfork.h
@@ -47,8 +47,8 @@
 
 /** @cond INTERNAL */
 
-#define TUNINGFORK_MAJOR_VERSION 1
-#define TUNINGFORK_MINOR_VERSION 5
+#define TUNINGFORK_MAJOR_VERSION 2
+#define TUNINGFORK_MINOR_VERSION 0
 #define TUNINGFORK_BUGFIX_VERSION 0
 #define TUNINGFORK_PACKED_VERSION                            \
     ANDROID_GAMESDK_PACKED_VERSION(TUNINGFORK_MAJOR_VERSION, \
diff --git a/prepproto.gradle b/prepproto.gradle
index f4eb6ac..07f881b 100644
--- a/prepproto.gradle
+++ b/prepproto.gradle
@@ -54,11 +54,28 @@
         setEnvironment env
         commandLine "python", "setup.py", "install", "--user"
     }
-    // Generate nano-pb requirements
-    exec {
-        workingDir "${gameSdkRoot}/../external/nanopb-c/generator/proto"
-        setEnvironment env
-        commandLine 'make'
+    // Generate nano-pb requirements.
+    // Nanopb provides a Makefile to generate the requirements, however on windows it
+    // is not trivial to work with make. Instead we just use protoc to generate the requirements
+    // which is what the Makefile does as well to begin with.
+    if (OperatingSystem.current().isWindows()) {
+	exec {
+	    workingDir "${gameSdkRoot}/../external/nanopb-c/generator/proto"
+	    setEnvironment env
+	    commandLine "${protocBinDir}/protoc.exe", "nanopb.proto", "--python_out=."
+	}
+	exec {
+	    workingDir "${gameSdkRoot}/../external/nanopb-c/generator/proto"
+	    setEnvironment env
+	    commandLine "${protocBinDir}/protoc.exe", "plugin.proto", "--python_out=."
+	}
+    }
+    else {
+        exec {
+            workingDir "${gameSdkRoot}/../external/nanopb-c/generator/proto"
+            setEnvironment env
+            commandLine "make"
+	}
     }
     outputs.upToDateWhen { false }
 }
diff --git a/samples/agdktunnel/app/build.gradle b/samples/agdktunnel/app/build.gradle
index f7bbed5..48a7015 100644
--- a/samples/agdktunnel/app/build.gradle
+++ b/samples/agdktunnel/app/build.gradle
@@ -146,7 +146,7 @@
 
 // Android Performance Tuner validation setup and protoc compile tasks
 task createJar(type: GradleBuild) {
-    buildFile = GameSDKPath + '/src/tuningfork/tools/validation/build.gradle'
+    buildFile = GameSDKPath + '/games-performance-tuner/tools/validation/build.gradle'
     tasks = ['createJar']
 }
 
@@ -161,7 +161,7 @@
 task buildTuningForkBinFiles(type: JavaExec) {
     dependsOn createJar
     main "-jar"
-    args   (GameSDKPath + "/src/tuningfork/tools/validation/build/libs/TuningforkApkValidationTool.jar",
+    args   (GameSDKPath + "/games-performance-tuner/tools/validation/build/libs/TuningforkApkValidationTool.jar",
             "--tuningforkPath",
             "src/main/assets/tuningfork",
             "--protoCompiler",
diff --git a/samples/game_text_input/game_text_input_testbed/app/src/main/cpp/game-input.cpp b/samples/game_text_input/game_text_input_testbed/app/src/main/cpp/game-input.cpp
index a569e79..7fc1014 100644
--- a/samples/game_text_input/game_text_input_testbed/app/src/main/cpp/game-input.cpp
+++ b/samples/game_text_input/game_text_input_testbed/app/src/main/cpp/game-input.cpp
@@ -82,6 +82,11 @@
 }
 
 extern "C" JNIEXPORT void JNICALL
+Java_com_gametextinput_testbed_MainActivity_restartInput(JNIEnv *env, jobject thiz) {
+  GameTextInput_restartInput(gameTextInput);
+}
+
+extern "C" JNIEXPORT void JNICALL
 Java_com_gametextinput_testbed_MainActivity_sendSelectionToStart(JNIEnv *env,
                                                              jobject thiz) {
   GameTextInput_getState(gameTextInput, [](void* context, const GameTextInputState* state) {
diff --git a/samples/game_text_input/game_text_input_testbed/app/src/main/java/com/gameinput/testbed/MainActivity.java b/samples/game_text_input/game_text_input_testbed/app/src/main/java/com/gameinput/testbed/MainActivity.java
index a50a8a5..86015ab 100644
--- a/samples/game_text_input/game_text_input_testbed/app/src/main/java/com/gameinput/testbed/MainActivity.java
+++ b/samples/game_text_input/game_text_input_testbed/app/src/main/java/com/gameinput/testbed/MainActivity.java
@@ -37,6 +37,7 @@
     native void setInputConnectionNative(InputConnection c);
     native void showIme();
     native void hideIme();
+    native void restartInput();
     native void sendSelectionToStart();
     native void sendSelectionToEnd();
 
@@ -66,21 +67,21 @@
             EditorInfo editorInfo = new EditorInfo();
             editorInfo.inputType = InputType.TYPE_NULL;
             inputEnabledTextView.mInputConnection.setEditorInfo(editorInfo);
-            inputEnabledTextView.mInputConnection.restartInput();
+            restartInput();
         });
         Button typeTextButton = (Button) findViewById(R.id.type_text_button);
         typeTextButton.setOnClickListener(view -> {
             EditorInfo editorInfo = new EditorInfo();
             editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
             inputEnabledTextView.mInputConnection.setEditorInfo(editorInfo);
-            inputEnabledTextView.mInputConnection.restartInput();
+            restartInput();
         });
         Button typeNumberButton = (Button) findViewById(R.id.type_number_button);
         typeNumberButton.setOnClickListener(view -> {
             EditorInfo editorInfo = new EditorInfo();
             editorInfo.inputType = InputType.TYPE_CLASS_NUMBER;
             inputEnabledTextView.mInputConnection.setEditorInfo(editorInfo);
-            inputEnabledTextView.mInputConnection.restartInput();
+            restartInput();
         });
 
         onCreated();
diff --git a/samples/memory_advice/hogger/app/CMakeLists.txt b/samples/memory_advice/hogger/app/CMakeLists.txt
index 2f602ec..677e23a 100644
--- a/samples/memory_advice/hogger/app/CMakeLists.txt
+++ b/samples/memory_advice/hogger/app/CMakeLists.txt
@@ -8,11 +8,6 @@
 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti" )
 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGOOGLE_PROTOBUF_NO_RTTI -DHAVE_PTHREAD")
 
-set( MEMORYADVICE_RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../test/memoryadvice/memoryadvice/src/main/resources")
-
-file(GLOB TF_MODEL_FILES "${MEMORYADVICE_RESOURCES_DIR}/*.*")
-file(COPY ${TF_MODEL_FILES} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets)
-
 include_directories(../../../../include)
 include_directories(../../../common/include ) # Samples Includes
 
diff --git a/samples/memory_advice/hogger/app/build.gradle b/samples/memory_advice/hogger/app/build.gradle
index 8b4fab0..20e4bfb 100644
--- a/samples/memory_advice/hogger/app/build.gradle
+++ b/samples/memory_advice/hogger/app/build.gradle
@@ -41,6 +41,15 @@
     }
 }
 
+task copyAssets() {
+    copy {
+        from "../../../../test/memoryadvice/memoryadvice/src/main/resources"
+        into "src/main/assets"
+    }
+}
+
+tasks.preBuild.dependsOn('copyAssets')
+
 dependencies {
 
     implementation 'androidx.appcompat:appcompat:1.1.0'
diff --git a/src/common/JNIUtil.h b/src/common/JNIUtil.h
index decfd01..702e24d 100644
--- a/src/common/JNIUtil.h
+++ b/src/common/JNIUtil.h
@@ -231,6 +231,10 @@
             env->DeleteLocalRef(imclassloaderClass);
         }
 #endif  // #ifdef ANDROIDGAMESDK_NO_BINARY_DEX_LINKAGE
+    } else {
+        // Register the native methods loaded by the classLoaderClass
+        env->RegisterNatives(targetClass, nativeMethods,
+                             nativeMethodsSize);
     }
     env->DeleteLocalRef(className);
     return targetClass;
diff --git a/src/common/jni/jni_wrap.h b/src/common/jni/jni_wrap.h
index 4b08e73..6e84cca 100644
--- a/src/common/jni/jni_wrap.h
+++ b/src/common/jni/jni_wrap.h
@@ -315,6 +315,41 @@
 
 namespace android {
 
+namespace util {
+
+class DisplayMetrics : public java::Object {
+   public:
+    DisplayMetrics(java::Object&& o) : java::Object(std::move(o)) {}
+    DisplayMetrics()
+        : java::Object("android/util/DisplayMetrics", "()V") {}
+    int heightPixels() const { return obj_.GetIntField("heightPixels"); }
+    int widthPixels() const { return obj_.GetIntField("widthPixels"); }
+};
+
+}  // namespace util
+
+namespace view {
+
+class Display : public java::Object {
+   public:
+    Display(java::Object&& o) : java::Object(std::move(o)) {}
+    void getMetrics(util::DisplayMetrics& displayMetrics) {
+        CallOVMethod("getMetrics", "android/util/DisplayMetrics",
+                     displayMetrics);
+    }
+};
+
+class WindowManager : public java::Object {
+   public:
+    WindowManager(java::Object&& o) : java::Object(std::move(o)) {}
+    Display getDefaultDisplay() {
+        return CallVOMethod("getDefaultDisplay",
+                            "android/view/Display");
+    }
+};
+
+}  // namespace view
+
 namespace content {
 
 namespace pm {
@@ -443,6 +478,10 @@
         return CallVOMethod("getPackageManager",
                             "android/content/pm/PackageManager");
     }
+    android::view::WindowManager getWindowManager() {
+        return CallVOMethod("getWindowManager",
+                            "android/view/WindowManager");
+    }
     jni::String getPackageName() { return CallVSMethod("getPackageName"); }
     res::AssetManager getAssets() {
         return CallVOMethod("getAssets", "android/content/res/AssetManager");
diff --git a/test/memoryadvice/README.md b/test/memoryadvice/README.md
index d342944..ec9d08a 100644
--- a/test/memoryadvice/README.md
+++ b/test/memoryadvice/README.md
@@ -156,7 +156,7 @@
 ## Adding the library to an Android project
 
 The library is published on
-[Google's Maven repository](https://maven.google.com/web/index.html?q=com.google.android.games#com.google.android.games:memory-advice:0.24).
+[Google's Maven repository](https://maven.google.com/web/index.html?q=com.google.android.games#com.google.android.games:memory-advice:0.25).
 
 In the application root `build.gradle` file, ensure `google()` is specified as a
 repository for the project, as well as `jitpack.io` for some of its
@@ -180,7 +180,7 @@
 ```gradle
 dependencies {
     // ..
-    implementation 'com.google.android.games:memory-advice:0.24'
+    implementation 'com.google.android.games:memory-advice:0.25'
 
 }
 ```
diff --git a/test/memoryadvice/memoryadvice/build.gradle b/test/memoryadvice/memoryadvice/build.gradle
index f4d1cab..f072060 100644
--- a/test/memoryadvice/memoryadvice/build.gradle
+++ b/test/memoryadvice/memoryadvice/build.gradle
@@ -10,7 +10,7 @@
         minSdkVersion 19
         versionCode 9
         // LINT.IfChange
-        versionName '0.24'
+        versionName '0.25'
         // LINT.ThenChange(../README.md)
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/test/tuningfork/endtoend/abandoned_loading.cpp b/test/tuningfork/endtoend/abandoned_loading.cpp
index d9af8c8..9568c66 100644
--- a/test/tuningfork/endtoend/abandoned_loading.cpp
+++ b/test/tuningfork/endtoend/abandoned_loading.cpp
@@ -37,12 +37,14 @@
       "device": "",
       "fingerprint": "",
       "gles_version": {"major": 0, "minor": 0},
+      "height_pixels": 0,
       "model": "",
       "product": "",
       "soc_manufacturer": "",
       "soc_model": "",
       "swap_total_bytes": 123,
-      "total_memory_bytes": 0
+      "total_memory_bytes": 0,
+      "width_pixels": 0
     },
     "game_sdk_info": {"session_id": "", "version": "1.0.0"},
     "time_period": {"end_time": "1970-01-01T00:00:00.000000Z",
diff --git a/test/tuningfork/endtoend/battery.cpp b/test/tuningfork/endtoend/battery.cpp
index 8d6f68d..a69a7b1 100644
--- a/test/tuningfork/endtoend/battery.cpp
+++ b/test/tuningfork/endtoend/battery.cpp
@@ -62,12 +62,14 @@
         "major": 0,
         "minor": 0
       },
+      "height_pixels": 0,
       "model": "",
       "product": "",
       "soc_manufacturer": "",
       "soc_model": "",
       "swap_total_bytes": 123,
-      "total_memory_bytes": 0
+      "total_memory_bytes": 0,
+      "width_pixels": 0
     },
     "game_sdk_info": {
       "session_id": "",
diff --git a/test/tuningfork/endtoend/common.cpp b/test/tuningfork/endtoend/common.cpp
index 2b45f30..d9925a7 100644
--- a/test/tuningfork/endtoend/common.cpp
+++ b/test/tuningfork/endtoend/common.cpp
@@ -32,12 +32,14 @@
       "major": 0,
       "minor": 0
     },
+    "height_pixels": 0,
     "model": "",
     "product": "",
     "soc_manufacturer": "",
     "soc_model": "",
     "swap_total_bytes": 123,
-    "total_memory_bytes": 0
+    "total_memory_bytes": 0,
+    "width_pixels": 0
   },
   "game_sdk_info": {
     "session_id": "",
@@ -63,12 +65,14 @@
       "major": 0,
       "minor": 0
     },
+    "height_pixels": 0,
     "model": "",
     "product": "",
     "soc_manufacturer": "",
     "soc_model": "",
     "swap_total_bytes": 123,
-    "total_memory_bytes": 0
+    "total_memory_bytes": 0,
+    "width_pixels": 0
   },
   "game_sdk_info": {
     "session_id": "",
diff --git a/test/tuningfork/endtoend/fidelityparam_download.cpp b/test/tuningfork/endtoend/fidelityparam_download.cpp
index ff34f68..a854d8a 100644
--- a/test/tuningfork/endtoend/fidelityparam_download.cpp
+++ b/test/tuningfork/endtoend/fidelityparam_download.cpp
@@ -128,12 +128,14 @@
       "major": 0,
       "minor": 0
     },
+    "height_pixels": 0,
     "model": "",
     "product": "",
     "soc_manufacturer": "",
     "soc_model": "",
     "swap_total_bytes": 123,
-    "total_memory_bytes": 0
+    "total_memory_bytes": 0,
+    "width_pixels": 0
   },
   "name": "applications//apks/0"
 })";
diff --git a/test/tuningfork/endtoend/memory.cpp b/test/tuningfork/endtoend/memory.cpp
index 79c6303..5648f23 100644
--- a/test/tuningfork/endtoend/memory.cpp
+++ b/test/tuningfork/endtoend/memory.cpp
@@ -66,12 +66,14 @@
         "major": 0,
         "minor": 0
       },
+      "height_pixels": 0,
       "model": "",
       "product": "",
       "soc_manufacturer": "",
       "soc_model": "",
       "swap_total_bytes": 123,
-      "total_memory_bytes": 0
+      "total_memory_bytes": 0,
+      "width_pixels": 0
     },
     "game_sdk_info": {
       "session_id": "",
diff --git a/test/tuningfork/endtoend/time_based.cpp b/test/tuningfork/endtoend/time_based.cpp
index f126407..f64e70f 100644
--- a/test/tuningfork/endtoend/time_based.cpp
+++ b/test/tuningfork/endtoend/time_based.cpp
@@ -236,12 +236,14 @@
       "major": 0,
       "minor": 0
     },
+    "height_pixels": 0,
     "model": "",
     "product": "",
     "soc_manufacturer": "",
     "soc_model": "",
     "swap_total_bytes": 123,
-    "total_memory_bytes": 0
+    "total_memory_bytes": 0,
+    "width_pixels": 0
   },
   "game_sdk_info": {
     "session_id": "",
@@ -314,12 +316,14 @@
       "major": 0,
       "minor": 0
     },
+    "height_pixels": 0,
     "model": "",
     "product": "",
     "soc_manufacturer": "",
     "soc_model": "",
     "swap_total_bytes": 123,
-    "total_memory_bytes": 0
+    "total_memory_bytes": 0,
+    "width_pixels": 0
   },
   "game_sdk_info": {
     "session_id": "",
diff --git a/test/tuningfork/serialization_test.cpp b/test/tuningfork/serialization_test.cpp
index cedeb4c..bb2a604 100644
--- a/test/tuningfork/serialization_test.cpp
+++ b/test/tuningfork/serialization_test.cpp
@@ -50,7 +50,9 @@
     "SOC_MODEL" /*soc_model*/,
     "SOC_MANUFACTURER" /*soc_manufacturer*/,
     234 /*swap_total_bytes*/,
-    ANDROID_GAMESDK_PACKED_VERSION(2, 7, 0) /*swappy_version*/};
+    ANDROID_GAMESDK_PACKED_VERSION(2, 7, 0) /*swappy_version*/,
+    1024 /*height_pixels*/,
+    768 /*width_pixels*/};
 
 std::string test_device_info_ser = R"TF({
   "brand": "BRAND",
@@ -61,12 +63,14 @@
   "gles_version": {
     "major": 5, "minor": 21907
   },
+  "height_pixels": 1024,
   "model": "MODEL",
   "product": "PRODUCT",
   "soc_manufacturer": "SOC_MANUFACTURER",
   "soc_model": "SOC_MODEL",
   "swap_total_bytes": 234,
-  "total_memory_bytes": 2387
+  "total_memory_bytes": 2387,
+  "width_pixels": 768
 })TF";
 
 void CheckDeviceInfo(const RequestInfo& info) {
@@ -92,12 +96,14 @@
         "major": 5,
         "minor": 21907
       },
+      "height_pixels": 1024,
       "model": "MODEL",
       "product": "PRODUCT",
       "soc_manufacturer": "SOC_MANUFACTURER",
       "soc_model": "SOC_MODEL",
       "swap_total_bytes": 234,
-      "total_memory_bytes": 2387
+      "total_memory_bytes": 2387,
+      "width_pixels": 768
     },
     "game_sdk_info": {
       "session_id": "sess",
diff --git a/third_party/cube/app/src/main/cpp/cube.c b/third_party/cube/app/src/main/cpp/cube.c
index bcf3c9c..a7c629f 100644
--- a/third_party/cube/app/src/main/cpp/cube.c
+++ b/third_party/cube/app/src/main/cpp/cube.c
@@ -1580,6 +1580,9 @@
       tracer.startFrame = swappy_trace_test_startFrame;
       tracer.swapIntervalChanged = swappy_trace_test_swapIntervalChanged;
       SwappyVk_injectTracer(&tracer);
+      // Test uninject tracer function.
+      SwappyVk_uninjectTracer(&tracer);
+      SwappyVk_injectTracer(&tracer);
       demo->tracer_injected = true;
     }