diff --git a/MobileNativeCode/Source/MobileNativeCode/MobileNativeCode_UPL_Android.xml b/MobileNativeCode/Source/MobileNativeCode/MobileNativeCode_UPL_Android.xml
index d48395d..24fdf4a 100644
--- a/MobileNativeCode/Source/MobileNativeCode/MobileNativeCode_UPL_Android.xml
+++ b/MobileNativeCode/Source/MobileNativeCode/MobileNativeCode_UPL_Android.xml
@@ -113,15 +113,27 @@ https://docs.unrealengine.com/en-US/SharingAndReleasing/Mobile/UnrealPluginLangu
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/AppLifecycleObserver.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/AppLifecycleObserver.java
new file mode 100644
index 0000000..7f84d0f
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/AppLifecycleObserver.java
@@ -0,0 +1,95 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.lifecycle.ProcessLifecycleOwner;
+import android.util.Log;
+
+@Keep
+public class AppLifecycleObserver implements LifecycleObserver {
+ // Class tag for logging
+ @Keep
+ private static final String TAG = "AppLifecycleObserver";
+
+ @Keep
+ private static boolean isAppForeground = false;
+
+ @Keep
+ private static AppLifecycleObserver instance;
+
+ @Keep
+ AppLifecycleObserver() {
+ Log.d(TAG, "Init AppLifecycleObserver");
+ }
+
+ // First launch application
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ public void onCreateEvent() {
+ Log.d(TAG, "Call onCreateEvent");
+ }
+
+ // The application is opened or returned from the background
+ @Keep
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ public void onStartEvent() {
+ Log.d(TAG, "Call onStartEvent");
+ isAppForeground = true;
+ }
+
+ // The application is no longer visible on the screen (but still running in the background)
+ @Keep
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ public void onPauseEvent() {
+ Log.d(TAG, "Call onPauseEvent");
+ }
+
+ // The application
+ @Keep
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResumeEvent() {
+ Log.d(TAG, "Call onResumeEvent");
+ }
+
+ // The application is no longer visible on the screen (but still running in the background)
+ @Keep
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ public void onStopEvent() {
+ Log.d(TAG, "Call onStopEvent");
+ isAppForeground = false;
+ }
+
+ // The application is destroyed
+ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ public void onDestroyEvent() {
+ Log.d(TAG, "Call onDestroyEvent");
+ }
+
+ @Keep
+ public static boolean getIsAppForeground() {
+ return isAppForeground;
+ }
+
+ @Keep
+ public static void register() {
+ if (instance == null) {
+ instance = new AppLifecycleObserver();
+ ProcessLifecycleOwner.get().getLifecycle().addObserver(instance);
+ Log.d(TAG, "AppLifecycleObserver registered.");
+ } else {
+ Log.d(TAG, "AppLifecycleObserver has been registered, don't need register anymore.");
+ }
+ }
+
+ @Keep
+ public static void unregister() {
+ if (instance != null) {
+ ProcessLifecycleOwner.get().getLifecycle().removeObserver(instance);
+ instance = null;
+ Log.d(TAG, "AppLifecycleObserver unregistered.");
+ } else {
+ Log.d(TAG, "AppLifecycleObserver has been null, don't need unregister anymore.");
+ }
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/AutoStartManager.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/AutoStartManager.java
new file mode 100644
index 0000000..1bb3eb6
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/AutoStartManager.java
@@ -0,0 +1,52 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+@Keep
+public class AutoStartManager {
+ @Keep
+ private static final String TAG = "AutoStartManager";
+
+ @Keep
+ private static final String PREFS_NAME = "AutoStartPrefs";
+
+ @Keep
+ private static final String KEY_AUTO_START = "AUTO_START";
+
+ @Keep
+ public static void enableAutoStart(Activity activity) {
+ setAutoStart(activity.getApplicationContext(), true);
+ }
+
+ @Keep
+ public static void disableAutoStart(Activity activity) {
+ setAutoStart(activity.getApplicationContext(), false);
+ }
+
+ @Keep
+ public static boolean isAutoStartEnable(Activity activity) {
+ return isAutoStart(activity.getApplicationContext());
+ }
+
+ @Keep
+ public static boolean isAutoStart(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ return prefs.getBoolean(KEY_AUTO_START, false);
+ }
+
+ @Keep
+ private static void setAutoStart(Context context, boolean enabled) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ prefs.edit().putBoolean(KEY_AUTO_START, enabled).apply();
+ Log.d(TAG, "AutoStart set to: " + enabled);
+ }
+
+ // public static void lockTask(Activity activity) { ... }
+ // public static void hideSystemUI(Activity activity) { ... }
+ // public static void scheduleReboot(...) { ... }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/BootReceiver.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/BootReceiver.java
new file mode 100644
index 0000000..7a3c29f
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/BootReceiver.java
@@ -0,0 +1,31 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+@Keep
+public class BootReceiver extends BroadcastReceiver {
+ @Keep
+ private static final String TAG = "BootReceiver";
+
+ @Keep
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Call onReceive");
+
+ if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ boolean isAutoStartEnable = AutoStartManager.isAutoStart(context);
+
+ Log.d(TAG, "Boot completed. Auto start: " + isAutoStartEnable);
+
+ if (isAutoStartEnable) {
+ Intent serviceIntent = new Intent(context, BootService.class);
+ context.startForegroundService(serviceIntent);
+ Log.d(TAG, "Started BootService");
+ }
+ }
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/BootService.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/BootService.java
new file mode 100644
index 0000000..1d35ddf
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/BootService.java
@@ -0,0 +1,193 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import androidx.core.app.NotificationCompat;
+import android.app.Service;
+import android.app.PendingIntent;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.IBinder;
+import android.util.Log;
+
+@Keep
+public class BootService extends Service {
+ @Keep
+ private static final String TAG = "BootService";
+
+ @Keep
+ private static final String CHANNEL_ID = "BootChannel";
+
+ @Keep
+ public static boolean appIsRun = false;
+
+ @Keep
+ private ScreenStateReceiver screenStateReceiver;
+
+ @Keep
+ public static void minimizeApp(Context context) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
+ @Keep
+ public static void restoreApp(Context context) {
+ Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
+ if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(launchIntent);
+ }
+ }
+
+ // Static method to call resend Oculus Menu Intent
+ @Keep
+ public static void resetSystemMenu(BootService service) {
+ Log.d(TAG, "Call resetSystemMenu");
+ new Thread(() -> {
+ Log.d(TAG, "Start run minimizeApp");
+ minimizeApp(service);
+ Log.d(TAG, "Finish run minimizeApp");
+
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ Log.d(TAG, "Start run restoreApp");
+ restoreApp(service);
+ Log.d(TAG, "Finish run restoreApp");
+
+ service.stopSelf();
+ }).start();
+ }
+
+ // Start the service in the foreground with a notification
+ @Keep
+ private void startForegroundNotification() {
+ Log.d(TAG, "Call startForegroundNotification");
+
+ // Create notification channel
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "Boot Service Channel", // Channel name
+ NotificationManager.IMPORTANCE_LOW // Importance level
+ );
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ // Create a PendingIntent to launch MainActivity when notification is tapped
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_IMMUTABLE);
+
+ // Decode a bitmap from resource (replace with your own resource ID as needed)
+ Bitmap icon = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_dialog_info);
+
+ // Build the notification using NotificationCompat.Builder
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
+ .setContentTitle(getText(android.R.string.dialog_alert_title)) // Placeholder title
+ .setContentText(getText(android.R.string.dialog_alert_title)) // Placeholder content
+ .setSmallIcon(android.R.drawable.ic_dialog_info)
+ .setContentIntent(pendingIntent)
+ .setLargeIcon(icon)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+
+ // Start foreground service to prevent being killed by the system
+ startForeground(1, builder.build());
+ Log.d(TAG, "Finish startForegroundNotification()");
+ }
+
+ @Keep
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Log.d(TAG, "Call onCreate");
+
+ IntentFilter screenStateReceiverFilter = new IntentFilter();
+ screenStateReceiverFilter.addAction("com.oculus.intent.action.MOUNT_STATE_CHANGED");
+
+ // The ScreenStateReceiver class (defined in ScreenStateReceiver.java) will handle the broadcast
+ screenStateReceiver = new ScreenStateReceiver(this);
+ getApplicationContext().registerReceiver(screenStateReceiver, screenStateReceiverFilter);
+
+ // Start the foreground notification to ensure the service is not killed by the system
+ startForegroundNotification();
+ }
+
+ @Keep
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "Call onStartCommand");
+
+ new Thread(() -> {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ Log.d(TAG, "Start to launch app " + getApplicationContext().getPackageName());
+
+ Intent launchIntent = getPackageManager().getLaunchIntentForPackage(getApplicationContext().getPackageName());
+ if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(launchIntent);
+ BootService.appIsRun = true;
+ Log.d(TAG, "App " + getApplicationContext().getPackageName() + " launched successfully");
+ if (screenStateReceiver.isRun) {
+ stopSelf();
+ }
+ } else {
+ Log.d(TAG, "Launch intent is null");
+ }
+
+ }).start();
+
+ return START_STICKY;
+ }
+
+ @Keep
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Keep
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "Call onDestroy");
+
+ // Unregister screenStateReceiver
+ try {
+ if (screenStateReceiver != null) {
+ getApplicationContext().unregisterReceiver(screenStateReceiver);
+ screenStateReceiver = null;
+ Log.d(TAG, "Unregister screenStateReceiver");
+ } else {
+ Log.d(TAG, "screenStateReceiver null");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error unregistering screenStateReceiver", e);
+ }
+
+ // Stop the foreground service
+ try {
+ stopForeground(true);
+ Log.d(TAG, "Stopped foreground service");
+ } catch (Exception e) {
+ Log.e(TAG, "Error stopping foreground service", e);
+ }
+
+ super.onDestroy();
+ Log.d(TAG, "Finish onDestroy");
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/LogJava.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/LogJava.java
new file mode 100644
index 0000000..69a88e6
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/LogJava.java
@@ -0,0 +1,12 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.util.Log;
+
+@Keep
+public class LogJava {
+ @Keep
+ public static void logJava(String tag, String inString) {
+ Log.d(tag, inString);
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/MainActivity.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/MainActivity.java
new file mode 100644
index 0000000..9cd297a
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/MainActivity.java
@@ -0,0 +1,6 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+
+@Keep
+public class MainActivity { }
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/OverlayPermissionHelper.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/OverlayPermissionHelper.java
new file mode 100644
index 0000000..939bc64
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/OverlayPermissionHelper.java
@@ -0,0 +1,34 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.Settings;
+import android.util.Log;
+
+@Keep
+public class OverlayPermissionHelper {
+ @Keep
+ private static final String TAG = "OverlayPermissionHelper";
+
+ @Keep
+ public static boolean isOverlayPermissionGranted(Activity activity) {
+ return Settings.canDrawOverlays(activity.getApplicationContext());
+ }
+
+ @Keep
+ public static void checkAndRequestOverlayPermission(Activity activity) {
+ Log.d(TAG, "Call checkAndRequestOverlayPermission");
+
+ if (!Settings.canDrawOverlays(activity.getApplicationContext())) {
+ Log.d(TAG, "Requesting SYSTEM_ALERT_WINDOW permission in " + activity.getApplicationContext().getPackageName());
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.getApplicationContext().getPackageName()));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activity.getApplicationContext().startActivity(intent);
+ } else {
+ Log.d(TAG, "App " + activity.getApplicationContext().getPackageName() + " had permission SYSTEM_ALERT_WINDOW. Don't request anymore");
+ }
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/ScreenStateReceiver.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/ScreenStateReceiver.java
new file mode 100644
index 0000000..2cc5be3
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/ScreenStateReceiver.java
@@ -0,0 +1,50 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+@Keep
+public class ScreenStateReceiver extends BroadcastReceiver {
+ @Keep
+ private static final String TAG = "ScreenStateReceiver";
+
+ @Keep
+ final BootService bootService;
+
+ //Run this receiver only one time
+ @Keep
+ public boolean isRun = false;
+
+ @Keep
+ ScreenStateReceiver(BootService paramBootService) {
+ this.bootService = paramBootService;
+ }
+
+ @Keep
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Call onReceive");
+
+ if (intent == null || !intent.getAction().equals("com.oculus.intent.action.MOUNT_STATE_CHANGED")) {
+ Log.d(TAG, "Not com.oculus.intent.action.MOUNT_STATE_CHANGED");
+ return;
+ }
+
+ boolean isMounted = intent.getBooleanExtra("state", false);
+ Log.d(TAG, "Mount state changed: " + isMounted);
+
+ if (isMounted && !isRun) {
+ if (BootService.appIsRun) {
+ isRun = true;
+ BootService.resetSystemMenu(bootService);
+ } else {
+ isRun = true;
+ Log.d(TAG, "App is not run. Don't use " + TAG + " anymore");
+ }
+
+ }
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/ShutdownReceiver.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/ShutdownReceiver.java
new file mode 100644
index 0000000..41e54da
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/ShutdownReceiver.java
@@ -0,0 +1,29 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+@Keep
+class ShutdownReceiver extends BroadcastReceiver {
+ // Class tag for logging
+ @Keep
+ private static final String TAG = "ShutdownReceiver";
+
+ @Keep
+ final WakeUpService wakeUpService;
+
+ @Keep
+ ShutdownReceiver(WakeUpService paramWakeUpService) {
+ this.wakeUpService = paramWakeUpService;
+ }
+
+ @Keep
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Call onReceive");
+ WakeUpService.checkStopWakeUpService(this.wakeUpService);
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpManager.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpManager.java
new file mode 100644
index 0000000..44f73bd
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpManager.java
@@ -0,0 +1,23 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.app.Activity;
+import android.util.Log;
+
+@Keep
+public class WakeUpManager {
+ @Keep
+ private static final String TAG = "WakeUpManager";
+
+ @Keep
+ public static void enableWakeUp(final Activity activity) {
+ Log.d(TAG, "Enabling Wake Up");
+ WakeUpService.startService(activity);
+ }
+
+ @Keep
+ public static void disableWakeUp(final Activity activity) {
+ Log.d(TAG, "Disabling Wake Up");
+ WakeUpService.stopService(activity);
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpReceiver.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpReceiver.java
new file mode 100644
index 0000000..aa973ad
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpReceiver.java
@@ -0,0 +1,29 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+@Keep
+class WakeUpReceiver extends BroadcastReceiver {
+ // Class tag for logging
+ @Keep
+ private static final String TAG = "WakeUpReceiver";
+
+ @Keep
+ final WakeUpService wakeUpService;
+
+ @Keep
+ WakeUpReceiver(WakeUpService paramWakeUpService) {
+ this.wakeUpService = paramWakeUpService;
+ }
+
+ @Keep
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Call onReceive");
+ WakeUpService.onReceive(this.wakeUpService, System.currentTimeMillis() + 1000L);
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpService.java b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpService.java
new file mode 100644
index 0000000..e161d77
--- /dev/null
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/Android/Java/WakeUpService.java
@@ -0,0 +1,232 @@
+package com.Plugins.MobileNativeCode;
+
+import androidx.annotation.Keep;
+import androidx.core.app.NotificationCompat;
+import android.support.annotation.Keep;
+import android.app.AlarmManager;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.IBinder;
+import android.util.Log;
+
+@Keep
+public class WakeUpService extends Service {
+ // Class tag for logging
+ @Keep
+ private static final String TAG = "WakeUpService";
+
+ // AlarmManager instance for scheduling alarms
+ @Keep
+ private AlarmManager alarmManager;
+
+ // WakeUpReceiver instance for listening DEVICE_IDLE_MODE_CHANGED
+ @Keep
+ private WakeUpReceiver wakeUpReceiver;
+
+ // ShutdownReceiver instance for listening ACTION_SHUTDOWN and ACTION_SCREEN_OFF
+ @Keep
+ private ShutdownReceiver shutdownReceiver;
+
+ // Static method to schedule next alarm; called from file WakeUpReceiver.java
+ @Keep
+ public static void onReceive(WakeUpService service, long triggerTime) {
+ service.scheduleAlarm(triggerTime);
+ }
+
+ // Static method to stop WakeUpService; called from file ShutdownReceiver.java
+ @Keep
+ public static void checkStopWakeUpService(WakeUpService service) {
+ if (!AppLifecycleObserver.getIsAppForeground()) {
+ service.stopSelf();
+ Log.d(TAG, "WakeUpService stopped because the app is in the background.");
+ } else {
+ Log.d(TAG, "WakeUpService is still running because the app is in the foreground.");
+ }
+ }
+
+ // Start the service in the foreground with a notification
+ @Keep
+ private void startForegroundNotification() {
+ Log.d(TAG, "Call startForegroundNotification");
+ String channelId = "WakeUpChannel";
+
+ // Create notification channel
+ NotificationChannel channel = new NotificationChannel(
+ channelId,
+ "Wake Up Service Channel", // Channel name
+ NotificationManager.IMPORTANCE_LOW // Importance level
+ );
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager != null) {
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ // Create a PendingIntent to launch MainActivity when notification is tapped
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_IMMUTABLE);
+
+ // Decode a bitmap from resource (replace with your own resource ID as needed)
+ Bitmap icon = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_dialog_info);
+
+ // Build the notification using NotificationCompat.Builder
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelId)
+ .setContentTitle(getText(android.R.string.dialog_alert_title)) // Placeholder title
+ .setContentText(getText(android.R.string.dialog_alert_title)) // Placeholder content
+ .setSmallIcon(android.R.drawable.ic_dialog_info)
+ .setContentIntent(pendingIntent)
+ .setLargeIcon(icon)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+
+ // Start foreground service to prevent being killed by the system
+ startForeground(1, builder.build());
+ Log.d(TAG, "Finish startForegroundNotification");
+ }
+
+ // Schedule an alarm clock with the specified trigger time
+ @Keep
+ private void scheduleAlarm(long triggerTime) {
+ Log.d(TAG, "Call scheduleAlarm");
+ // Create an intent to launch MainActivity when the alarm is triggered
+ Intent intentMain = new Intent(getApplicationContext(), MainActivity.class);
+ PendingIntent pendingIntentMain = PendingIntent.getActivity(
+ getApplicationContext(), 0, intentMain, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerTime, pendingIntentMain);
+
+ // Create an intent to restart this service when the alarm goes off
+ Intent intentService = new Intent(getApplicationContext(), WakeUpService.class);
+ PendingIntent pendingIntentService = PendingIntent.getService(
+ getApplicationContext(), 1, intentService, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ alarmManager.setAlarmClock(alarmInfo, pendingIntentService);
+ Log.d(TAG, "Finish scheduleAlarm");
+ }
+
+ // Static method to start the service
+ @Keep
+ public static void startService(Context context) {
+ context.startService(new Intent(context, WakeUpService.class));
+ }
+
+ // Static method to stop the service
+ @Keep
+ public static void stopService(Context context) {
+ context.stopService(new Intent(context, WakeUpService.class));
+ }
+
+ @Keep
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Keep
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "Call onCreate");
+
+ AppLifecycleObserver.register();
+
+ // Initialize AlarmManager
+ alarmManager = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
+
+ // Register a BroadcastReceiver to listen for DEVICE_IDLE_MODE_CHANGED action
+ IntentFilter wakeUpReceiverFilter = new IntentFilter();
+ wakeUpReceiverFilter.addAction("android.os.action.DEVICE_IDLE_MODE_CHANGED");
+
+ // The WakeUpReceiver class (defined in WakeUpReceiver.java) will handle the broadcast
+ wakeUpReceiver = new WakeUpReceiver(this);
+ getApplicationContext().registerReceiver(wakeUpReceiver, wakeUpReceiverFilter);
+
+ // Register a BroadcastReceiver to listen for ACTION_SHUTDOWN and ACTION_SCREEN_OFF action
+ IntentFilter shutdownReceiverFilter = new IntentFilter();
+ shutdownReceiverFilter.addAction(Intent.ACTION_SHUTDOWN); // Listen to Event Shutdown Device
+ shutdownReceiverFilter.addAction(Intent.ACTION_SCREEN_OFF); //Listen to Event Turn off Screen
+
+ // The ShutdownReceiver class (defined in ShutdownReceiver.java) will handle the broadcast
+ shutdownReceiver = new ShutdownReceiver(this);
+ getApplicationContext().registerReceiver(shutdownReceiver, shutdownReceiverFilter);
+
+ // Start the foreground notification to ensure the service is not killed by the system
+ startForegroundNotification();
+ Log.d(TAG, "Finish onCreate()");
+ }
+
+ @Keep
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "Call onStartCommand");
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Keep
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "Call onDestroy");
+
+ AppLifecycleObserver.unregister();
+
+ try {
+ // Unregister wakeUpReceiver
+ try {
+ if (wakeUpReceiver != null) {
+ getApplicationContext().unregisterReceiver(wakeUpReceiver);
+ wakeUpReceiver = null;
+ Log.d(TAG, "Unregister wakeUpReceiver");
+ } else {
+ Log.d(TAG, "wakeUpReceiver null");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error unregistering wakeUpReceiver", e);
+ }
+
+ // Unregister shutdownReceiver
+ try {
+ if (shutdownReceiver != null) {
+ getApplicationContext().unregisterReceiver(shutdownReceiver);
+ shutdownReceiver = null;
+ Log.d(TAG, "Unregister shutdownReceiver");
+ } else {
+ Log.d(TAG, "shutdownReceiver null");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error unregistering shutdownReceiver", e);
+ }
+
+ // Cancel alarm if set
+ try {
+ if (alarmManager != null) {
+ Intent intentService = new Intent(getApplicationContext(), WakeUpService.class);
+ PendingIntent pendingIntentService = PendingIntent.getService(
+ getApplicationContext(), 1, intentService, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ alarmManager.cancel(pendingIntentService);
+ Log.d(TAG, "alarmManager cancelled");
+ } else {
+ Log.d(TAG, "alarmManager null");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error cancelling alarmManager", e);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error in onDestroy", e);
+ }
+
+ // Stop the foreground service
+ try {
+ stopForeground(true);
+ Log.d(TAG, "Stopped foreground service");
+ } catch (Exception e) {
+ Log.e(TAG, "Error stopping foreground service", e);
+ }
+
+ super.onDestroy();
+ Log.d(TAG, "Finish onDestroy");
+ }
+}
diff --git a/MobileNativeCode/Source/MobileNativeCode/Private/MobileNativeCodeBlueprint.cpp b/MobileNativeCode/Source/MobileNativeCode/Private/MobileNativeCodeBlueprint.cpp
index 159a19f..6082401 100644
--- a/MobileNativeCode/Source/MobileNativeCode/Private/MobileNativeCodeBlueprint.cpp
+++ b/MobileNativeCode/Source/MobileNativeCode/Private/MobileNativeCodeBlueprint.cpp
@@ -259,3 +259,103 @@ void UMobileNativeCodeBlueprint::ExampleMyJavaObject(FString& JavaBundle)
#endif //Android
}
+
+
+// #~~~~~~~~~~~~~~~~~~~~~~~~~~~ Begin Wake Up ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+void UMobileNativeCodeBlueprint::EnableWakeUp()
+{
+#if PLATFORM_ANDROID
+ AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/WakeUpManager",
+ "enableWakeUp",
+ "",
+ true
+ );
+#endif
+}
+
+void UMobileNativeCodeBlueprint::DisableWakeUp()
+{
+#if PLATFORM_ANDROID
+ AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/WakeUpManager",
+ "disableWakeUp",
+ "",
+ true
+ );
+#endif
+}
+// #~~~~~~~~~~~~~~~~~~~~~~~~~~~ End Wake Up ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+// #~~~~~~~~~~~~~~~~~~~~~~~~~~~ Begin Auto Start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+void UMobileNativeCodeBlueprint::CheckAndRequestOverlayPermission()
+{
+#if PLATFORM_ANDROID
+ AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/OverlayPermissionHelper",
+ "checkAndRequestOverlayPermission",
+ "",
+ true
+ );
+#endif
+}
+
+bool UMobileNativeCodeBlueprint::IsAutoStartEnable()
+{
+ bool bIsAutoStartEnable = false;
+#if PLATFORM_ANDROID
+ bIsAutoStartEnable = AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/AutoStartManager",
+ "isAutoStartEnable",
+ "",
+ true
+ );
+#endif
+ return bIsAutoStartEnable;
+}
+
+void UMobileNativeCodeBlueprint::SetAutoStart(bool bIsAutoStart)
+{
+#if PLATFORM_ANDROID
+ if (bIsAutoStart)
+ {
+ AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/AutoStartManager",
+ "enableAutoStart",
+ "",
+ true
+ );
+ }
+ else
+ {
+ AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/AutoStartManager",
+ "disableAutoStart",
+ "",
+ true
+ );
+ }
+
+#endif
+}
+// #~~~~~~~~~~~~~~~~~~~~~~~~~~~ End Auto Start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+// #~~~~~~~~~~~~~~~~~~~~~~~~~~~ Begin Debug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+void UMobileNativeCodeBlueprint::LogJava(FString Tag, FString InString)
+{
+#if PLATFORM_ANDROID
+ AndroidUtils::CallJavaCode(
+ "com/Plugins/MobileNativeCode/LogJava",
+ "logJava",
+ "",
+ false,
+ Tag,
+ InString
+ );
+#endif
+}
+// #~~~~~~~~~~~~~~~~~~~~~~~~~~~ End Debug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/MobileNativeCode/Source/MobileNativeCode/Public/MobileNativeCodeBlueprint.h b/MobileNativeCode/Source/MobileNativeCode/Public/MobileNativeCodeBlueprint.h
index 7b48530..fde6792 100644
--- a/MobileNativeCode/Source/MobileNativeCode/Public/MobileNativeCodeBlueprint.h
+++ b/MobileNativeCode/Source/MobileNativeCode/Public/MobileNativeCodeBlueprint.h
@@ -71,4 +71,50 @@ class MOBILENATIVECODE_API UMobileNativeCodeBlueprint : public UBlueprintFunctio
*/
UFUNCTION(BlueprintCallable, Category = "MobileNativeCode Category")
static void ExampleMyJavaObject(FString& JavaBundle);
+
+ // #~~~~~~~~~~~~~~~~~~~~~~~~~~~ Begin Wake Up ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ /**
+ * Enable Wake Lock. Keep Device Always Wake
+ */
+ UFUNCTION(BlueprintCallable, Category = "MobileNativeCodeBlueprint")
+ static void EnableWakeUp();
+
+ /**
+ * Disable Wake Lock. Don't Keep Device Wake
+ */
+ UFUNCTION(BlueprintCallable, Category = "MobileNativeCodeBlueprint")
+ static void DisableWakeUp();
+ // #~~~~~~~~~~~~~~~~~~~~~~~~~~~ End Wake Up ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+ // #~~~~~~~~~~~~~~~~~~~~~~~~~~~ Begin Auto Start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~;
+ /**
+ * Check And Request Overlay Permission
+ */
+ UFUNCTION(BlueprintCallable, Category = "MobileNativeCodeBlueprint")
+ static void CheckAndRequestOverlayPermission();
+
+ /**
+ * Is Auto Start Enable?
+ */
+ UFUNCTION(BlueprintCallable, Category = "MobileNativeCodeBlueprint")
+ static bool IsAutoStartEnable();
+
+ /**
+ * Set Auto Start
+ */
+ UFUNCTION(BlueprintCallable, Category = "MobileNativeCodeBlueprint")
+ static void SetAutoStart(bool bIsAutoStart);
+ // #~~~~~~~~~~~~~~~~~~~~~~~~~~~ End Auto Start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+
+ // #~~~~~~~~~~~~~~~~~~~~~~~~~~~ Begin Debug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ /**
+ * Log in java
+ */
+ UFUNCTION(BlueprintCallable, Category = "MobileNativeCodeBlueprint")
+ static void LogJava(FString Tag = "LogJava", FString InString = "");
+ // #~~~~~~~~~~~~~~~~~~~~~~~~~~~ End Debug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
};