Created
May 18, 2019 16:17
-
-
Save JonnyBurger/293d73770b587a78cd85609af0adca08 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/android/app/src/main/java/host/exp/exponent/GeofencingTaskConsumer.java b/android/app/src/main/java/host/exp/exponent/GeofencingTaskConsumer.java | |
index b6909fd..076f760 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/GeofencingTaskConsumer.java | |
+++ b/android/app/src/main/java/host/exp/exponent/GeofencingTaskConsumer.java | |
@@ -17,6 +17,7 @@ | |
import com.google.android.gms.location.LocationServices; | |
import java.util.ArrayList; | |
+import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
@@ -24,6 +25,8 @@ | |
public class GeofencingTaskConsumer extends TaskConsumer implements TaskConsumerInterface { | |
+ public static int VERSION = 1; | |
+ | |
private static final String TAG = "GeofencingTaskConsumer"; | |
private TaskInterface mTask; | |
@@ -99,22 +102,25 @@ public void didReceiveBroadcast(Intent intent) { | |
data.putPersistableBundle("region", region); | |
Context context = getContext().getApplicationContext(); | |
- getTaskManagerUtils().scheduleJob(context, mTask, data); | |
+ getTaskManagerUtils().scheduleJob(context, mTask, Collections.singletonList(data)); | |
} | |
} | |
} | |
@Override | |
public boolean didExecuteJob(JobService jobService, JobParameters params) { | |
- PersistableBundle data = params.getExtras().getPersistableBundle("data"); | |
- Bundle bundle = new Bundle(); | |
- Bundle region = new Bundle(); | |
+ List<PersistableBundle> data = getTaskManagerUtils().extractDataFromJobParams(params); | |
+ | |
+ for (PersistableBundle item : data) { | |
+ Bundle bundle = new Bundle(); | |
+ Bundle region = new Bundle(); | |
- region.putAll(data.getPersistableBundle("region")); | |
- bundle.putInt("eventType", data.getInt("eventType")); | |
- bundle.putBundle("region", region); | |
+ region.putAll(item.getPersistableBundle("region")); | |
+ bundle.putInt("eventType", item.getInt("eventType")); | |
+ bundle.putBundle("region", region); | |
- mTask.execute(bundle, null); | |
+ mTask.execute(bundle, null); | |
+ } | |
return true; | |
} | |
@@ -255,4 +261,4 @@ private static String getErrorString(int errorCode) { | |
} | |
//endregion | |
-} | |
+} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/LocationModule.java b/android/app/src/main/java/host/exp/exponent/LocationModule.java | |
index 2b40b64..a8b38da 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/LocationModule.java | |
+++ b/android/app/src/main/java/host/exp/exponent/LocationModule.java | |
@@ -116,9 +116,6 @@ public String getName() { | |
@Override | |
public void setModuleRegistry(ModuleRegistry moduleRegistry) { | |
- if (mUIManager != null) { | |
- //mUIManager.unregisterLifecycleEventListener(this); | |
- } | |
mEventEmitter = moduleRegistry.getModule(EventEmitter.class); | |
mUIManager = moduleRegistry.getModule(UIManager.class); | |
@@ -126,9 +123,6 @@ public void setModuleRegistry(ModuleRegistry moduleRegistry) { | |
mTaskManager = moduleRegistry.getModule(TaskManagerInterface.class); | |
mActivityProvider = moduleRegistry.getModule(ActivityProvider.class); | |
- if (mUIManager != null) { | |
- //mUIManager.registerLifecycleEventListener(this); | |
- } | |
} | |
//region Expo methods | |
@@ -139,7 +133,6 @@ public void getCurrentPositionAsync(final Map<String, Object> options, final Pro | |
final Long timeout = options.containsKey("timeout") ? ((Double) options.get("timeout")).longValue() : null; | |
final LocationRequest locationRequest = LocationHelpers.prepareLocationRequest(options); | |
boolean showUserSettingsDialog = !options.containsKey(SHOW_USER_SETTINGS_DIALOG_KEY) || (boolean) options.get(SHOW_USER_SETTINGS_DIALOG_KEY); | |
- final int promiseDone = 0; | |
// Check for permissions | |
if (isMissingPermissions()) { | |
@@ -151,9 +144,7 @@ public void getCurrentPositionAsync(final Map<String, Object> options, final Pro | |
timeoutObject.onTimeout(new TimeoutObject.TimeoutListener() { | |
@Override | |
public void onTimeout() { | |
- if (promiseDone == 0) { | |
- promise.reject(new LocationRequestTimeoutException()); | |
- } | |
+ promise.reject(new LocationRequestTimeoutException()); | |
} | |
}); | |
timeoutObject.start(); | |
@@ -166,9 +157,7 @@ public void onTimeout() { | |
@Override | |
public void onSuccess(Location location) { | |
if (location != null) { | |
- if (promiseDone == 0) { | |
- promise.resolve(LocationHelpers.locationToBundle(location, Bundle.class)); | |
- } | |
+ promise.resolve(LocationHelpers.locationToBundle(location, Bundle.class)); | |
timeoutObject.markDoneIfNotTimedOut(); | |
} | |
} | |
@@ -176,9 +165,7 @@ public void onSuccess(Location location) { | |
} | |
if (LocationHelpers.hasNetworkProviderEnabled(mContext) || !showUserSettingsDialog) { | |
- if (promiseDone == 0) { | |
- LocationHelpers.requestSingleLocation(this, locationRequest, timeoutObject, promise); | |
- } | |
+ LocationHelpers.requestSingleLocation(this, locationRequest, timeoutObject, promise); | |
} else { | |
// Pending requests can ask the user to turn on improved accuracy mode in user's settings. | |
addPendingLocationRequest(locationRequest, new LocationActivityResultListener() { | |
@@ -559,8 +546,6 @@ public void onFailure(@NonNull Exception e) { | |
try { | |
ResolvableApiException resolvable = (ResolvableApiException) e; | |
- | |
- //mUIManager.registerActivityEventListener(LocationModule.this); | |
resolvable.startResolutionForResult(activity, CHECK_SETTINGS_REQUEST_CODE); | |
} catch (IntentSender.SendIntentException sendEx) { | |
// Ignore the error. | |
@@ -770,7 +755,6 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, | |
return; | |
} | |
executePendingRequests(resultCode); | |
- //mUIManager.unregisterLifecycleEventListener(this); | |
} | |
@Override | |
@@ -798,4 +782,4 @@ public void onHostDestroy() { | |
} | |
//endregion | |
-} | |
+} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/LocationTaskConsumer.java b/android/app/src/main/java/host/exp/exponent/LocationTaskConsumer.java | |
index a5cf74b..254bce4 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/LocationTaskConsumer.java | |
+++ b/android/app/src/main/java/host/exp/exponent/LocationTaskConsumer.java | |
@@ -21,6 +21,10 @@ | |
import com.google.android.gms.location.LocationServices; | |
import com.google.android.gms.tasks.OnCompleteListener; | |
import com.google.android.gms.tasks.Task; | |
+import io.nlopez.smartlocation.location.config.LocationParams; | |
+ | |
+ | |
+import expo.modules.location.LocationHelpers; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
@@ -28,20 +32,14 @@ | |
import java.util.Map; | |
-public class LocationTaskConsumer extends TaskConsumer implements TaskConsumerInterface, LifecycleEventListener { | |
+public class LocationTaskConsumer extends TaskConsumer implements TaskConsumerInterface { | |
private static final String TAG = "LocationTaskConsumer"; | |
- private static final String FOREGROUND_SERVICE_KEY = "foregroundService"; | |
private static long sLastTimestamp = 0; | |
private TaskInterface mTask; | |
private PendingIntent mPendingIntent; | |
- private LocationTaskService mService; | |
private LocationRequest mLocationRequest; | |
private FusedLocationProviderClient mLocationClient; | |
- private Location mLastReportedLocation; | |
- private double mDeferredDistance = 0.0; | |
- private List<Location> mDeferredLocations = new ArrayList<>(); | |
- private boolean mIsHostPaused = true; | |
public LocationTaskConsumer(Context context, TaskManagerUtilsInterface taskManagerUtils) { | |
super(context, taskManagerUtils); | |
@@ -57,13 +55,11 @@ public String taskType() { | |
public void didRegister(TaskInterface task) { | |
mTask = task; | |
startLocationUpdates(); | |
- maybeStartForegroundService(); | |
} | |
@Override | |
public void didUnregister() { | |
stopLocationUpdates(); | |
- stopForegroundService(); | |
mTask = null; | |
mPendingIntent = null; | |
mLocationRequest = null; | |
@@ -73,13 +69,8 @@ public void didUnregister() { | |
@Override | |
public void setOptions(Map<String, Object> options) { | |
super.setOptions(options); | |
- | |
- // Restart location updates | |
stopLocationUpdates(); | |
startLocationUpdates(); | |
- | |
- // Restart foreground service if its option has changed. | |
- maybeStartForegroundService(); | |
} | |
@Override | |
@@ -88,54 +79,53 @@ public void didReceiveBroadcast(Intent intent) { | |
return; | |
} | |
+ Context context = getContext().getApplicationContext(); | |
LocationResult result = LocationResult.extractResult(intent); | |
if (result != null) { | |
+ List<PersistableBundle> data = new ArrayList<>(); | |
List<Location> locations = result.getLocations(); | |
- deferLocations(locations); | |
- maybeReportDeferredLocations(); | |
- } else { | |
- try { | |
- mLocationClient.getLastLocation().addOnCompleteListener(new OnCompleteListener<Location>() { | |
- @Override | |
- public void onComplete(@NonNull Task<Location> task) { | |
- Location location = task.getResult(); | |
- | |
- if (location != null) { | |
- Log.i(TAG, "get last location: " + location); | |
- deferLocations(Collections.singletonList(location)); | |
- maybeReportDeferredLocations(); | |
- } | |
- } | |
- }); | |
- } catch (SecurityException e) { | |
- Log.e(TAG, "Cannot get last location: " + e.getMessage()); | |
+ for (Location location : locations) { | |
+ long timestamp = location.getTime(); | |
+ | |
+ // Some devices may broadcast the same location multiple times (mostly twice) so we're filtering out these locations, | |
+ // so only one location at the specific timestamp can schedule a job. | |
+ if (timestamp > sLastTimestamp) { | |
+ PersistableBundle bundle = host.exp.exponent.LocationHelpers.locationToBundle(location, PersistableBundle.class); | |
+ | |
+ data.add(bundle); | |
+ sLastTimestamp = timestamp; | |
+ } | |
+ } | |
+ | |
+ if (data.size() > 0) { | |
+ // Schedule new job. | |
+ getTaskManagerUtils().scheduleJob(context, mTask, data); | |
} | |
} | |
} | |
@Override | |
public boolean didExecuteJob(final JobService jobService, final JobParameters params) { | |
- PersistableBundle data = params.getExtras().getPersistableBundle("data"); | |
- int length = data != null ? data.getInt("length", 0) : 0; | |
+ List<PersistableBundle> data = getTaskManagerUtils().extractDataFromJobParams(params); | |
+ Bundle bundleData = new Bundle(); | |
ArrayList<Bundle> locationBundles = new ArrayList<>(); | |
- for (int i = 0; i < length; i++) { | |
- PersistableBundle persistableLocationBundle = data.getPersistableBundle(String.valueOf(i)); | |
+ for (PersistableBundle persistableLocationBundle : data) { | |
Bundle locationBundle = new Bundle(); | |
Bundle coordsBundle = new Bundle(); | |
- if (persistableLocationBundle != null) { | |
- coordsBundle.putAll(persistableLocationBundle.getPersistableBundle("coords")); | |
- locationBundle.putAll(persistableLocationBundle); | |
- locationBundle.putBundle("coords", coordsBundle); | |
+ coordsBundle.putAll(persistableLocationBundle.getPersistableBundle("coords")); | |
+ locationBundle.putAll(persistableLocationBundle); | |
+ locationBundle.putBundle("coords", coordsBundle); | |
- locationBundles.add(locationBundle); | |
- } | |
+ locationBundles.add(locationBundle); | |
} | |
- executeTaskWithLocationBundles(locationBundles, new TaskExecutionCallback() { | |
+ bundleData.putParcelableArrayList("locations", locationBundles); | |
+ | |
+ mTask.execute(bundleData, null, new TaskExecutionCallback() { | |
@Override | |
public void onFinished(Map<String, Object> response) { | |
jobService.jobFinished(params, false); | |
@@ -161,7 +151,7 @@ private void startLocationUpdates() { | |
return; | |
} | |
- mLocationRequest = LocationHelpers.prepareLocationRequest(mTask.getOptions()); | |
+ mLocationRequest = prepareLocationRequest(); | |
mPendingIntent = preparePendingIntent(); | |
try { | |
@@ -179,166 +169,37 @@ private void stopLocationUpdates() { | |
} | |
} | |
- private void maybeStartForegroundService() { | |
- // Foreground service is available as of Android Oreo. | |
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { | |
- return; | |
- } | |
- | |
- ReadableArguments options = new MapArguments(mTask.getOptions()); | |
- final Context context = getContext(); | |
- boolean useForegroundService = options.containsKey(FOREGROUND_SERVICE_KEY); | |
- | |
- if (context == null) { | |
- Log.w(TAG, "Context not found when trying to start foreground service."); | |
- return; | |
- } | |
- | |
- // Service is already running, but the task has been registered again without `foregroundService` option. | |
- if (mService != null && !useForegroundService) { | |
- stopForegroundService(); | |
- return; | |
- } | |
- | |
- // Service is not running and the user don't want to start foreground service. | |
- if (!useForegroundService) { | |
- return; | |
- } | |
- | |
- // Foreground service is requested but not running. | |
- if (mService == null) { | |
- Intent serviceIntent = new Intent(context, LocationTaskService.class); | |
- Bundle extras = new Bundle(); | |
- final Bundle serviceOptions = options.getArguments(FOREGROUND_SERVICE_KEY).toBundle(); | |
- | |
- extras.putString("appId", mTask.getAppId()); | |
- extras.putString("taskName", mTask.getName()); | |
- serviceIntent.putExtras(extras); | |
- | |
- context.startForegroundService(serviceIntent); | |
- | |
- context.bindService(serviceIntent, new ServiceConnection() { | |
- @Override | |
- public void onServiceConnected(ComponentName name, IBinder service) { | |
- mService = ((LocationTaskService.ServiceBinder) service).getService(); | |
- mService.setParentContext(context); | |
- } | |
- | |
- @Override | |
- public void onServiceDisconnected(ComponentName name) { | |
- mService.stop(); | |
- mService = null; | |
- } | |
- }, Context.BIND_AUTO_CREATE); | |
- } else { | |
- // Restart the service with new service options. | |
- mService.startForeground(); | |
- } | |
- } | |
- | |
- private void stopForegroundService() { | |
- if (mService != null) { | |
- mService.stop(); | |
- } | |
- } | |
- | |
- private void deferLocations(List<Location> locations) { | |
- int size = mDeferredLocations.size(); | |
- Location lastLocation = size > 0 ? mDeferredLocations.get(size - 1) : mLastReportedLocation; | |
+ private LocationRequest prepareLocationRequest() { | |
+ Map<String, Object> options = mTask.getOptions(); | |
+ LocationParams locationParams = LocationHelpers.mapOptionsToLocationParams(options); | |
+ int accuracy = LocationHelpers.getAccuracyFromOptions(options); | |
- for (Location location : locations) { | |
- if (lastLocation != null) { | |
- mDeferredDistance += Math.abs(location.distanceTo(lastLocation)); | |
- } | |
- lastLocation = location; | |
- } | |
- mDeferredLocations.addAll(locations); | |
- } | |
- | |
- private void maybeReportDeferredLocations() { | |
- if (!shouldReportDeferredLocations()) { | |
- // Don't report locations yet - continue deferring them. | |
- return; | |
- } | |
- | |
- Context context = getContext().getApplicationContext(); | |
- PersistableBundle data = new PersistableBundle(); | |
- int length = 0; | |
- | |
- for (Location location : mDeferredLocations) { | |
- long timestamp = location.getTime(); | |
- | |
- // Some devices may broadcast the same location multiple times (mostly twice) so we're filtering out these locations, | |
- // so only one location at the specific timestamp can schedule a job. | |
- if (timestamp > sLastTimestamp) { | |
- PersistableBundle bundle = LocationHelpers.locationToBundle(location, PersistableBundle.class); | |
- data.putPersistableBundle(Integer.valueOf(length).toString(), bundle); | |
- | |
- sLastTimestamp = timestamp; | |
- ++length; | |
- } | |
- } | |
- | |
- data.putInt("length", length); | |
- | |
- if (length > 0) { | |
- // Save last reported location, reset the distance and clear a list of locations. | |
- mLastReportedLocation = mDeferredLocations.get(mDeferredLocations.size() - 1); | |
- mDeferredDistance = 0.0; | |
- mDeferredLocations.clear(); | |
- | |
- // Schedule new job. | |
- getTaskManagerUtils().scheduleJob(context, mTask, data); | |
- } | |
- } | |
- | |
- private boolean shouldReportDeferredLocations() { | |
- if (mDeferredLocations.size() == 0) { | |
- return false; | |
- } | |
- if (!mIsHostPaused) { | |
- // Don't defer location updates when the activity is in foreground state. | |
- return true; | |
- } | |
- | |
- Location oldestLocation = mLastReportedLocation != null ? mLastReportedLocation : mDeferredLocations.get(0); | |
- Location newestLocation = mDeferredLocations.get(mDeferredLocations.size() - 1); | |
- Arguments options = new MapHelper(mTask.getOptions()); | |
- double distance = options.getDouble("deferredUpdatesDistance"); | |
- long interval = options.getLong("deferredUpdatesInterval"); | |
- | |
- return newestLocation.getTime() - oldestLocation.getTime() >= interval && mDeferredDistance >= distance; | |
+ return new LocationRequest() | |
+ .setFastestInterval(locationParams.getInterval()) | |
+ .setInterval(locationParams.getInterval()) | |
+ .setSmallestDisplacement(locationParams.getDistance()) | |
+ .setPriority(mapAccuracyToPriority(accuracy)); | |
} | |
private PendingIntent preparePendingIntent() { | |
return getTaskManagerUtils().createTaskIntent(getContext(), mTask); | |
} | |
- private void executeTaskWithLocationBundles(ArrayList<Bundle> locationBundles, TaskExecutionCallback callback) { | |
- if (locationBundles.size() > 0) { | |
- Bundle data = new Bundle(); | |
- data.putParcelableArrayList("locations", locationBundles); | |
- mTask.execute(data, null, callback); | |
- } else { | |
- callback.onFinished(null); | |
+ private int mapAccuracyToPriority(int accuracy) { | |
+ switch (accuracy) { | |
+ case LocationModule.ACCURACY_BEST_FOR_NAVIGATION: | |
+ case LocationModule.ACCURACY_HIGHEST: | |
+ case LocationModule.ACCURACY_HIGH: | |
+ return LocationRequest.PRIORITY_HIGH_ACCURACY; | |
+ case LocationModule.ACCURACY_BALANCED: | |
+ default: | |
+ return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; | |
+ case LocationModule.ACCURACY_LOW: | |
+ return LocationRequest.PRIORITY_LOW_POWER; | |
+ case LocationModule.ACCURACY_LOWEST: | |
+ return LocationRequest.PRIORITY_NO_POWER; | |
} | |
} | |
- @Override | |
- public void onHostResume() { | |
- mIsHostPaused = false; | |
- maybeReportDeferredLocations(); | |
- } | |
- | |
- @Override | |
- public void onHostPause() { | |
- mIsHostPaused = true; | |
- } | |
- | |
- @Override | |
- public void onHostDestroy() { | |
- mIsHostPaused = true; | |
- } | |
- | |
//endregion | |
} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/TaskConsumerInterface.java b/android/app/src/main/java/host/exp/exponent/TaskConsumerInterface.java | |
index 6b3d655..322e31a 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/TaskConsumerInterface.java | |
+++ b/android/app/src/main/java/host/exp/exponent/TaskConsumerInterface.java | |
@@ -38,5 +38,9 @@ | |
*/ | |
boolean didCancelJob(JobService jobService, JobParameters params); | |
+ /** | |
+ * Called when registering already registered task with different options. | |
+ */ | |
void setOptions(Map<String, Object> options); | |
-} | |
+ | |
+} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/TaskManagerInternalModule.java b/android/app/src/main/java/host/exp/exponent/TaskManagerInternalModule.java | |
index 0395931..5fbfc34 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/TaskManagerInternalModule.java | |
+++ b/android/app/src/main/java/host/exp/exponent/TaskManagerInternalModule.java | |
@@ -63,7 +63,7 @@ public void unregisterTask(String taskName, Class consumerClass) throws Exceptio | |
} | |
@Override | |
- public void executeTaskWithBody(Bundle body) { | |
+ public synchronized void executeTaskWithBody(Bundle body) { | |
if (mEventsQueue != null) { | |
// `startObserving` on TaskManagerModule wasn't called yet - add event body to the queue. | |
mEventsQueue.add(body); | |
@@ -82,7 +82,7 @@ public boolean taskHasConsumerOfClass(String taskName, Class consumerClass) { | |
} | |
@Override | |
- public void flushQueuedEvents() { | |
+ public synchronized void flushQueuedEvents() { | |
// Execute any events that came before this call. | |
if (mEventsQueue != null) { | |
for (Bundle body : mEventsQueue) { | |
diff --git a/android/app/src/main/java/host/exp/exponent/TaskManagerUtils.java b/android/app/src/main/java/host/exp/exponent/TaskManagerUtils.java | |
index a7dbe38..b7584ff 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/TaskManagerUtils.java | |
+++ b/android/app/src/main/java/host/exp/exponent/TaskManagerUtils.java | |
@@ -5,78 +5,234 @@ | |
import android.app.job.JobInfo; | |
import android.app.job.JobScheduler; | |
import android.content.ComponentName; | |
+import android.app.job.JobParameters; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.net.Uri; | |
import android.os.Bundle; | |
import android.os.Parcelable; | |
import android.os.PersistableBundle; | |
+import android.support.v4.util.ArraySet; | |
import android.util.Log; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Map; | |
+import java.util.Set; | |
+import java.util.Collections; | |
+import java.util.Comparator; | |
public class TaskManagerUtils implements TaskManagerUtilsInterface { | |
+ // Key that every job created by the task manager must contain in its extras bundle. | |
+ private static final String EXTRAS_REQUIRED_KEY = "expo.modules.taskManager"; | |
+ | |
+ // Request code number used for pending intents created by this module. | |
+ private static final int PENDING_INTENT_REQUEST_CODE = 5055; | |
+ | |
private static final int DEFAULT_OVERRIDE_DEADLINE = 60 * 1000; // 1 minute | |
- // make intent and job ids locally-unique | |
- private static Integer sCurrentIntentId = 2137; | |
- private static Integer sCurrentJobId = 2137; | |
+ private static final Set<TaskInterface> sTasksReschedulingJob = new ArraySet<>(); | |
- // Set of job IDs that are scheduled but not started yet. | |
- private static final List<Integer> sPendingJobIds = new ArrayList<>(); | |
+ //region TaskManagerUtilsInterface | |
+ @Override | |
public PendingIntent createTaskIntent(Context context, TaskInterface task) { | |
- Integer intentId = sCurrentIntentId++; | |
- String appId = task.getAppId(); | |
- String taskName = task.getName(); | |
+ return createTaskIntent(context, task, PendingIntent.FLAG_UPDATE_CURRENT); | |
+ } | |
+ | |
+ @Override | |
+ public void cancelTaskIntent(Context context, String appId, String taskName) { | |
+ PendingIntent pendingIntent = createTaskIntent(context, appId, taskName, PendingIntent.FLAG_NO_CREATE); | |
+ | |
+ if (pendingIntent != null) { | |
+ pendingIntent.cancel(); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public void scheduleJob(Context context, TaskInterface task, List<PersistableBundle> data) { | |
+ updateOrScheduleJob(context, task, data); | |
+ } | |
+ | |
+ @Override | |
+ public void cancelScheduledJob(Context context, int jobId) { | |
+ JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); | |
+ | |
+ if (jobScheduler != null) { | |
+ jobScheduler.cancel(jobId); | |
+ } else { | |
+ Log.e(this.getClass().getName(), "Job scheduler not found!"); | |
+ } | |
+ } | |
+ | |
+ @Override | |
+ public List<PersistableBundle> extractDataFromJobParams(JobParameters params) { | |
+ PersistableBundle extras = params.getExtras(); | |
+ List<PersistableBundle> data = new ArrayList<>(); | |
+ int dataSize = extras.getInt("dataSize", 0); | |
+ | |
+ for (int i = 0; i < dataSize; i++) { | |
+ data.add(extras.getPersistableBundle(String.valueOf(i))); | |
+ } | |
+ return data; | |
+ } | |
+ | |
+ //endregion TaskManagerUtilsInterface | |
+ //region static helpers | |
+ | |
+ static boolean notifyTaskJobCancelled(TaskInterface task) { | |
+ boolean isRescheduled = sTasksReschedulingJob.contains(task); | |
+ | |
+ if (isRescheduled) { | |
+ sTasksReschedulingJob.remove(task); | |
+ } | |
+ return isRescheduled; | |
+ } | |
+ | |
+ //endregion static helpers | |
+ //region private helpers | |
+ | |
+ private void updateOrScheduleJob(Context context, TaskInterface task, List<PersistableBundle> data) { | |
+ JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); | |
+ | |
+ if (jobScheduler == null) { | |
+ Log.e(this.getClass().getName(), "Job scheduler not found!"); | |
+ return; | |
+ } | |
+ | |
+ List<JobInfo> pendingJobs = jobScheduler.getAllPendingJobs(); | |
+ | |
+ Collections.sort(pendingJobs, new Comparator<JobInfo>() { | |
+ @Override | |
+ public int compare(JobInfo a, JobInfo b) { | |
+ return Integer.compare(a.getId(), b.getId()); | |
+ } | |
+ }); | |
+ | |
+ // We will be looking for the lowest number that is not being used yet. | |
+ int newJobId = 0; | |
+ | |
+ for (JobInfo jobInfo : pendingJobs) { | |
+ int jobId = jobInfo.getId(); | |
+ | |
+ if (isJobInfoRelatedToTask(jobInfo, task)) { | |
+ JobInfo mergedJobInfo = createJobInfoByAddingData(jobInfo, data); | |
+ | |
+ // Add the task to the list of rescheduled tasks. | |
+ sTasksReschedulingJob.add(task); | |
+ | |
+ try { | |
+ // Cancel jobs with the same ID to let them be rescheduled. | |
+ jobScheduler.cancel(jobId); | |
+ | |
+ // Reschedule job for given task. | |
+ jobScheduler.schedule(mergedJobInfo); | |
+ } catch (IllegalStateException e) { | |
+ Log.e(this.getClass().getName(), "Unable to reschedule a job: " + e.getMessage()); | |
+ } | |
+ return; | |
+ } | |
+ if (newJobId == jobId) { | |
+ newJobId++; | |
+ } | |
+ } | |
+ | |
+ try { | |
+ // Given task doesn't have any pending jobs yet, create a new JobInfo and schedule it then. | |
+ JobInfo jobInfo = createJobInfo(context, task, newJobId, data); | |
+ jobScheduler.schedule(jobInfo); | |
+ } catch (IllegalStateException e) { | |
+ Log.e(this.getClass().getName(), "Unable to schedule a new job: " + e.getMessage()); | |
+ } | |
+ } | |
+ | |
+ private JobInfo createJobInfoByAddingData(JobInfo jobInfo, List<PersistableBundle> data) { | |
+ PersistableBundle mergedExtras = jobInfo.getExtras(); | |
+ int dataSize = mergedExtras.getInt("dataSize", 0); | |
+ | |
+ if (data != null) { | |
+ mergedExtras.putInt("dataSize", dataSize + data.size()); | |
+ | |
+ for (int i = 0; i < data.size(); i++) { | |
+ mergedExtras.putPersistableBundle(String.valueOf(dataSize + i), data.get(i)); | |
+ } | |
+ } | |
+ return createJobInfo(jobInfo.getId(), jobInfo.getService(), mergedExtras); | |
+ } | |
+ | |
+ private PendingIntent createTaskIntent(Context context, String appId, String taskName, int flags) { | |
+ if (context == null) { | |
+ return null; | |
+ } | |
+ | |
Intent intent = new Intent(TaskBroadcastReceiver.INTENT_ACTION, null, context, TaskBroadcastReceiver.class); | |
Uri dataUri = new Uri.Builder() | |
- .appendQueryParameter("intentId", intentId.toString()) | |
.appendQueryParameter("appId", appId) | |
.appendQueryParameter("taskName", taskName) | |
.build(); | |
intent.setData(dataUri); | |
- return PendingIntent.getBroadcast(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT); | |
+ return PendingIntent.getBroadcast(context, PENDING_INTENT_REQUEST_CODE, intent, flags); | |
} | |
- public void scheduleJob(Context context, JobInfo jobInfo) { | |
- JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); | |
+ private PendingIntent createTaskIntent(Context context, TaskInterface task, int flags) { | |
+ String appId = task.getAppId(); | |
+ String taskName = task.getName(); | |
- int jobId = jobInfo.getId(); | |
+ return createTaskIntent(context, appId, taskName, flags); | |
+ } | |
- if (jobScheduler != null) { | |
- // Cancel jobs with the same ID to let them be rescheduled. | |
- jobScheduler.cancel(jobId); | |
+ private JobInfo createJobInfo(int jobId, ComponentName jobService, PersistableBundle extras) { | |
+ return new JobInfo.Builder(jobId, jobService) | |
+ .setExtras(extras) | |
+ .setMinimumLatency(0) | |
+ .setOverrideDeadline(DEFAULT_OVERRIDE_DEADLINE) | |
+ .build(); | |
+ } | |
- // Schedule a job. | |
- try { | |
- jobScheduler.schedule(jobInfo); | |
- } catch (IllegalStateException err) { | |
- jobScheduler.cancelAll(); | |
- } | |
- addToPendingJobs(jobId); | |
+ private JobInfo createJobInfo(Context context, TaskInterface task, int jobId, List<PersistableBundle> data) { | |
+ return createJobInfo(jobId, new ComponentName(context, TaskJobService.class), createExtrasForTask(task, data)); | |
+ } | |
+ | |
+ private PersistableBundle createExtrasForTask(TaskInterface task, List<PersistableBundle> data) { | |
+ PersistableBundle extras = new PersistableBundle(); | |
+ extras.putInt(EXTRAS_REQUIRED_KEY, 1); | |
+ extras.putString("appId", task.getAppId()); | |
+ extras.putString("taskName", task.getName()); | |
+ | |
+ if (data != null) { | |
+ extras.putInt("dataSize", data.size()); | |
+ | |
+ for (int i = 0; i < data.size(); i++) { | |
+ extras.putPersistableBundle(String.valueOf(i), data.get(i)); | |
+ } | |
} else { | |
- Log.e(this.getClass().getName(), "Job scheduler not found!"); | |
+ extras.putInt("dataSize", 0); | |
} | |
+ | |
+ return extras; | |
} | |
- public void scheduleJob(Context context, TaskInterface task, PersistableBundle data) { | |
- Integer jobId = sCurrentJobId++; | |
- PersistableBundle extras = createExtrasForTask(task, data); | |
- JobInfo jobInfo = createJobInfo(context, jobId, extras); | |
+ private boolean isJobInfoRelatedToTask(JobInfo jobInfo, TaskInterface task) { | |
+ PersistableBundle extras = jobInfo.getExtras(); | |
+ String appId = task.getAppId(); | |
+ String taskName = task.getName(); | |
- scheduleJob(context, jobInfo); | |
+ if (extras.containsKey(EXTRAS_REQUIRED_KEY)) { | |
+ return appId.equals(extras.getString("appId", "")) && taskName.equals(extras.getString("taskName", "")); | |
+ } | |
+ return false; | |
} | |
+ //endregion private helpers | |
+ //region converting map to bundle | |
+ | |
@SuppressWarnings("unchecked") | |
- public static Bundle mapToBundle(Map<String, Object> map) { | |
+ static Bundle mapToBundle(Map<String, Object> map) { | |
Bundle bundle = new Bundle(); | |
for (Map.Entry<String, Object> entry : map.entrySet()) { | |
@@ -112,7 +268,7 @@ public static Bundle mapToBundle(Map<String, Object> map) { | |
} | |
@SuppressWarnings("unchecked") | |
- public static double[] listToDoubleArray(List<Object> list) { | |
+ private static double[] listToDoubleArray(List<Object> list) { | |
double[] doubles = new double[list.size()]; | |
for (int i = 0; i < list.size(); i++) { | |
doubles[i] = (Double) list.get(i); | |
@@ -149,36 +305,5 @@ public static Bundle mapToBundle(Map<String, Object> map) { | |
return arrayList; | |
} | |
- private static void addToPendingJobs(int jobId) { | |
- if (!sPendingJobIds.contains(jobId)) { | |
- sPendingJobIds.add(jobId); | |
- } | |
- } | |
- | |
- public static void removeFromPendingJobs(int jobId) { | |
- sPendingJobIds.remove((Integer) jobId); | |
- } | |
- | |
- //region private | |
- | |
- private JobInfo createJobInfo(Context context, int jobId, PersistableBundle extras) { | |
- return new JobInfo.Builder(jobId, new ComponentName(context, TaskJobService.class)) | |
- .setExtras(extras) | |
- .setMinimumLatency(0) | |
- .setOverrideDeadline(DEFAULT_OVERRIDE_DEADLINE) | |
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) | |
- .build(); | |
- } | |
- | |
- private PersistableBundle createExtrasForTask(TaskInterface task, PersistableBundle data) { | |
- PersistableBundle extras = new PersistableBundle(); | |
- | |
- extras.putString("appId", task.getAppId()); | |
- extras.putString("taskName", task.getName()); | |
- extras.putPersistableBundle("data", data); | |
- | |
- return extras; | |
- } | |
- | |
- //endregion | |
-} | |
+ //endregion converting map to bundle | |
+} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/TaskManagerUtilsInterface.java b/android/app/src/main/java/host/exp/exponent/TaskManagerUtilsInterface.java | |
index 58be5d8..ea4cdc6 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/TaskManagerUtilsInterface.java | |
+++ b/android/app/src/main/java/host/exp/exponent/TaskManagerUtilsInterface.java | |
@@ -2,15 +2,35 @@ | |
import android.app.PendingIntent; | |
import android.app.job.JobInfo; | |
+import android.app.job.JobParameters; | |
import android.content.Context; | |
import android.os.PersistableBundle; | |
import java.util.List; | |
public interface TaskManagerUtilsInterface { | |
+ /** | |
+ * Creates pending intent that represents the task containing all its params. | |
+ */ | |
PendingIntent createTaskIntent(Context context, TaskInterface task); | |
- void scheduleJob(Context context, JobInfo jobInfoArg); | |
+ /** | |
+ * Cancels pending intent for given task. | |
+ */ | |
+ void cancelTaskIntent(Context context, String appId, String taskName); | |
- void scheduleJob(Context context, TaskInterface task, PersistableBundle extras); | |
-} | |
+ /** | |
+ * Schedules a job for given task and with given list of extra data. | |
+ */ | |
+ void scheduleJob(Context context, TaskInterface task, List<PersistableBundle> data); | |
+ | |
+ /** | |
+ * Cancels scheduled job with given identifier. | |
+ */ | |
+ void cancelScheduledJob(Context context, int jobId); | |
+ | |
+ /** | |
+ * Extracts data list from job parameters. | |
+ */ | |
+ List<PersistableBundle> extractDataFromJobParams(JobParameters params); | |
+} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/TaskService.java b/android/app/src/main/java/host/exp/exponent/TaskService.java | |
index 293ac86..7601911 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/TaskService.java | |
+++ b/android/app/src/main/java/host/exp/exponent/TaskService.java | |
@@ -179,6 +179,7 @@ public Bundle getTaskOptions(String taskName, String appId) { | |
return tasks; | |
} | |
+ | |
@Override | |
public void notifyTaskFinished(String taskName, final String appId, Map<String, Object> response) { | |
String eventId = (String) response.get("eventId"); | |
@@ -281,9 +282,6 @@ public boolean handleJob(final JobService jobService, final JobParameters params | |
TaskConsumerInterface consumer = getTaskConsumer(taskName, appId); | |
- // remove job ID from pending jobs | |
- TaskManagerUtils.removeFromPendingJobs(params.getJobId()); | |
- | |
if (consumer == null) { | |
Log.w(TAG, "Task or consumer not found."); | |
return false; | |
@@ -309,9 +307,6 @@ public boolean cancelJob(JobService jobService, JobParameters params) { | |
TaskConsumerInterface consumer = getTaskConsumer(taskName, appId); | |
- // remove job ID from pending jobs | |
- TaskManagerUtils.removeFromPendingJobs(params.getJobId()); | |
- | |
if (consumer == null) { | |
return false; | |
} | |
@@ -737,4 +732,4 @@ private static Class unversionedClassForClass(Class versionedClass) { | |
} | |
//endregion | |
-} | |
+} | |
\ No newline at end of file | |
diff --git a/android/app/src/main/java/host/exp/exponent/generated/DetachBuildConstants.java b/android/app/src/main/java/host/exp/exponent/generated/DetachBuildConstants.java | |
index d07e432..7294b2e 100644 | |
--- a/android/app/src/main/java/host/exp/exponent/generated/DetachBuildConstants.java | |
+++ b/android/app/src/main/java/host/exp/exponent/generated/DetachBuildConstants.java | |
@@ -3,6 +3,6 @@ | |
// This file is auto-generated. Please don't rename! | |
public class DetachBuildConstants { | |
- public static final String DEVELOPMENT_URL = "exp87235a6a39874b039c6d0aa017fee548://192.168.0.187:19000"; | |
+ public static final String DEVELOPMENT_URL = "exp87235a6a39874b039c6d0aa017fee548://192.168.0.186:19000"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment