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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ };