Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Fix the logic to restart the Godot application #61332

Merged
merged 1 commit into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions COPYRIGHT.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ Comment: The Android Open Source Project
Copyright: 2002, Google Inc.
License: Apache-2.0

Files: ./platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java
Comment: ProcessPhoenix
Copyright: 2015, Jake Wharton
License: Apache-2.0

Files: ./platform/android/power_android.cpp
./platform/osx/power_osx.cpp
./platform/windows/power_windows.cpp
Expand Down
2 changes: 2 additions & 0 deletions misc/scripts/clang_format.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ while IFS= read -rd '' f; do
continue 2
elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/gl/EGLLogWrapper"* ]]; then
continue 2
elif [[ "$f" == "platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix"* ]]; then
continue 2
fi
python misc/scripts/copyright_headers.py "$f"
continue 2
Expand Down
5 changes: 0 additions & 5 deletions platform/android/export/export_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,6 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
}

manifest_text += _get_xr_features_tag(p_preset);
manifest_text += _get_instrumentation_tag(p_preset);
manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms));
manifest_text += "</manifest>\n";
String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
Expand Down Expand Up @@ -955,10 +954,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
encode_uint32(retain_data_on_uninstall, &p_manifest.write[iofs + 16]);
}

if (tname == "instrumentation" && attrname == "targetPackage") {
string_table.write[attr_value] = get_package_name(package_name);
}

if (tname == "activity" && attrname == "screenOrientation") {
encode_uint32(screen_orientation, &p_manifest.write[iofs + 16]);
}
Expand Down
13 changes: 0 additions & 13 deletions platform/android/export/gradle_export_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,19 +233,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
return manifest_xr_features;
}

String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
String package_name = p_preset->get("package/unique_name");
String manifest_instrumentation_text = vformat(
" <instrumentation\n"
" tools:node=\"replace\"\n"
" android:name=\".GodotInstrumentation\"\n"
" android:icon=\"@mipmap/icon\"\n"
" android:label=\"@string/godot_project_name_string\"\n"
" android:targetPackage=\"%s\" />\n",
package_name);
return manifest_instrumentation_text;
}

String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
bool uses_xr = xr_mode_index == XR_MODE_OVR || xr_mode_index == XR_MODE_OPENXR;
Expand Down
2 changes: 0 additions & 2 deletions platform/android/export/gradle_export_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset);

String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset);

String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset);

String _get_activity_tag(const Ref<EditorExportPreset> &p_preset);

String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);
Expand Down
13 changes: 7 additions & 6 deletions platform/android/java/lib/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@

<service android:name=".GodotDownloaderService" />

</application>
<activity
android:name=".utils.ProcessPhoenix"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:process=":phoenix"
android:exported="false"
/>

<instrumentation
android:name=".GodotInstrumentation"
android:icon="@mipmap/icon"
android:label="@string/godot_project_name_string"
android:targetPackage="org.godotengine.godot" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@

package org.godotengine.godot;

import android.content.ComponentName;
import org.godotengine.godot.utils.ProcessPhoenix;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
Expand Down Expand Up @@ -71,34 +72,29 @@ public void onCreate(Bundle savedInstanceState) {

@Override
public void onDestroy() {
Log.v(TAG, "Destroying Godot app...");
super.onDestroy();
onGodotForceQuit(godotFragment);
}

@Override
public final void onGodotForceQuit(Godot instance) {
if (instance == godotFragment) {
System.exit(0);
Log.v(TAG, "Force quitting Godot instance");
ProcessPhoenix.forceQuit(this);
}
}

@Override
public final void onGodotRestartRequested(Godot instance) {
if (instance == godotFragment) {
// HACK:
//
// Currently it's very hard to properly deinitialize Godot on Android to restart the game
// It's very hard to properly de-initialize Godot on Android to restart the game
// from scratch. Therefore, we need to kill the whole app process and relaunch it.
//
// Restarting only the activity, wouldn't be enough unless it did proper cleanup (including
// releasing and reloading native libs or resetting their state somehow and clearing statics).
//
// Using instrumentation is a way of making the whole app process restart, because Android
// will kill any process of the same package which was already running.
//
Bundle args = new Bundle();
args.putParcelable("intent", getIntent());
startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args);
Log.v(TAG, "Restarting Godot instance...");
ProcessPhoenix.triggerRebirth(this);
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// clang-format off

/* Third-party library.
* Upstream: https://github.com/JakeWharton/ProcessPhoenix
* Commit: 12cb27c2cc9c3fc555e97f2db89e571667de82c4
*/

/*
* Copyright (C) 2014 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.godotengine.godot.utils;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;

/**
* Process Phoenix facilitates restarting your application process. This should only be used for
* things like fundamental state changes in your debug builds (e.g., changing from staging to
* production).
* <p>
* Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance.
*/
public final class ProcessPhoenix extends Activity {
private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents";
private static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid";

/**
* Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default}
* activity as an intent.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context) {
triggerRebirth(context, getRestartIntent(context));
}

/**
* Call to restart the application process using the specified intents.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context, Intent... nextIntents) {
if (nextIntents.length < 1) {
throw new IllegalArgumentException("intents cannot be empty");
}
// create a new task for the first activity.
nextIntents[0].addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);

Intent intent = new Intent(context, ProcessPhoenix.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context.
intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents)));
intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid());
context.startActivity(intent);
}

// -- GODOT start --
/**
* Finish the activity and kill its process
*/
public static void forceQuit(Activity activity) {
forceQuit(activity, Process.myPid());
}

/**
* Finish the activity and kill its process
* @param activity
* @param pid
*/
public static void forceQuit(Activity activity, int pid) {
Process.killProcess(pid); // Kill original main process
activity.finish();
Runtime.getRuntime().exit(0); // Kill kill kill!
}

// -- GODOT end --

private static Intent getRestartIntent(Context context) {
String packageName = context.getPackageName();
Intent defaultIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if (defaultIntent != null) {
return defaultIntent;
}

throw new IllegalStateException("Unable to determine default activity for "
+ packageName
+ ". Does an activity specify the DEFAULT category in its intent filter?");
}

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// -- GODOT start --
ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS);
startActivities(intents.toArray(new Intent[intents.size()]));
forceQuit(this, getIntent().getIntExtra(KEY_MAIN_PROCESS_PID, -1));
// -- GODOT end --
}

/**
* Checks if the current process is a temporary Phoenix Process.
* This can be used to avoid initialisation of unused resources or to prevent running code that
* is not multi-process ready.
*
* @return true if the current process is a temporary Phoenix Process
*/
public static boolean isPhoenixProcess(Context context) {
int currentPid = Process.myPid();
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) {
return true;
}
}
}
return false;
}
}