1 package expo.modules.taskManager;
2 
3 import android.content.Context;
4 import android.os.Bundle;
5 
6 import org.unimodules.core.ModuleRegistry;
7 import org.unimodules.core.interfaces.InternalModule;
8 import org.unimodules.core.interfaces.LifecycleEventListener;
9 import org.unimodules.core.interfaces.services.EventEmitter;
10 import org.unimodules.core.interfaces.services.UIManager;
11 import org.unimodules.interfaces.taskManager.TaskConsumerInterface;
12 import org.unimodules.interfaces.taskManager.TaskManagerInterface;
13 import org.unimodules.interfaces.taskManager.TaskServiceInterface;
14 
15 import java.lang.ref.WeakReference;
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.List;
19 import java.util.Map;
20 
21 import expo.modules.interfaces.constants.ConstantsInterface;
22 
23 public class TaskManagerInternalModule implements InternalModule, TaskManagerInterface, LifecycleEventListener {
24   private UIManager mUIManager;
25   private EventEmitter mEventEmitter;
26   private ConstantsInterface mConstants;
27   private TaskServiceInterface mTaskService;
28   private WeakReference<Context> mContextRef;
29   private List<Bundle> mEventsQueue = new ArrayList<>();
30 
31   public TaskManagerInternalModule(Context context) {
32     mContextRef = new WeakReference<>(context);
33   }
34 
35   //region InternalModule
36 
37   @Override
38   public List<Class> getExportedInterfaces() {
39     return Collections.singletonList(TaskManagerInterface.class);
40   }
41 
42   //endregion
43   //region ModuleRegistryConsumer
44 
45   @Override
46   public void onCreate(ModuleRegistry moduleRegistry) {
47     mUIManager = moduleRegistry.getModule(UIManager.class);
48     mEventEmitter = moduleRegistry.getModule(EventEmitter.class);
49     mConstants = moduleRegistry.getModule(ConstantsInterface.class);
50     mTaskService = moduleRegistry.getSingletonModule("TaskService", TaskServiceInterface.class);
51 
52     // Register in TaskService.
53     mTaskService.setTaskManager(this, getAppScopeKey(), getAppUrl());
54 
55     mUIManager.registerLifecycleEventListener(this);
56   }
57 
58   @Override
59   public void onDestroy() {
60     mUIManager.unregisterLifecycleEventListener(this);
61     mTaskService.setTaskManager(null, getAppScopeKey(), getAppUrl());
62   }
63 
64   //endregion
65   //region TaskManagerInterface
66 
67   @Override
68   public void registerTask(String taskName, Class consumerClass, Map<String, Object> options) throws Exception {
69     checkTaskService();
70     mTaskService.registerTask(taskName, getAppScopeKey(), getAppUrl(), consumerClass, options);
71   }
72 
73   @Override
74   public void unregisterTask(String taskName, Class consumerClass) throws Exception {
75     checkTaskService();
76     mTaskService.unregisterTask(taskName, getAppScopeKey(), consumerClass);
77   }
78 
79   @Override
80   public synchronized void executeTaskWithBody(Bundle body) {
81     if (mEventsQueue != null) {
82       // `startObserving` on TaskManagerModule wasn't called yet - add event body to the queue.
83       mEventsQueue.add(body);
84     } else {
85       // Manager is already being observed by JS app, so we can execute the event immediately.
86       mEventEmitter.emit(TaskManagerInterface.EVENT_NAME, body);
87     }
88   }
89 
90   @Override
91   public boolean taskHasConsumerOfClass(String taskName, Class consumerClass) {
92     if (mTaskService == null) {
93       return false;
94     }
95     return mTaskService.taskHasConsumerOfClass(taskName, getAppScopeKey(), consumerClass);
96   }
97 
98   @Override
99   public synchronized void flushQueuedEvents() {
100     // Execute any events that came before this call.
101     if (mEventsQueue != null) {
102       for (Bundle body : mEventsQueue) {
103         mEventEmitter.emit(TaskManagerInterface.EVENT_NAME, body);
104       }
105       mEventsQueue = null;
106     }
107   }
108 
109   @Override
110   public String getAppScopeKey() {
111     if (mConstants != null) {
112       return mConstants.getAppScopeKey();
113     }
114     return null;
115   }
116   //endregion
117   //region LifecycleEventListener
118 
119   @Override
120   public void onHostResume() {
121     if (!isRunningInHeadlessMode()) {
122       List<TaskConsumerInterface> taskConsumers = mTaskService.getTaskConsumers(getAppScopeKey());
123       for (TaskConsumerInterface taskConsumer : taskConsumers) {
124         if (taskConsumer instanceof LifecycleEventListener) {
125           ((LifecycleEventListener) taskConsumer).onHostResume();
126         }
127       }
128     }
129   }
130 
131   @Override
132   public void onHostPause() {
133     if (!isRunningInHeadlessMode()) {
134       List<TaskConsumerInterface> taskConsumers = mTaskService.getTaskConsumers(getAppScopeKey());
135       for (TaskConsumerInterface taskConsumer : taskConsumers) {
136         if (taskConsumer instanceof LifecycleEventListener) {
137           ((LifecycleEventListener) taskConsumer).onHostPause();
138         }
139       }
140     }
141   }
142 
143   @Override
144   public void onHostDestroy() {
145     if (!isRunningInHeadlessMode()) {
146       List<TaskConsumerInterface> taskConsumers = mTaskService.getTaskConsumers(getAppScopeKey());
147       for (TaskConsumerInterface taskConsumer : taskConsumers) {
148         if (taskConsumer instanceof LifecycleEventListener) {
149           ((LifecycleEventListener) taskConsumer).onHostDestroy();
150         }
151       }
152     }
153   }
154 
155   //endregion
156   //region helpers
157 
158   private boolean isRunningInHeadlessMode() {
159     return mTaskService.isStartedByHeadlessLoader(getAppScopeKey());
160   }
161 
162   private String getAppUrl() {
163     // If Constants module is available and experienceUrl is provided, just return it.
164     if (mConstants != null) {
165       String experienceUrl = (String) mConstants.getConstants().get("experienceUrl");
166       if (experienceUrl != null) {
167         return experienceUrl;
168       }
169     }
170 
171     // Fallback to package name? It should work well for RN apps with just one JS app inside.
172     Context context = mContextRef.get();
173     if (context != null) {
174       return context.getPackageName();
175     }
176 
177     return null;
178   }
179 
180   private void checkTaskService() throws IllegalStateException {
181     if (mTaskService == null) {
182       throw new IllegalStateException("Unable to find TaskService singleton module in module registry.");
183     }
184   }
185 
186   //endregion
187 }
188