summaryrefslogtreecommitdiff
path: root/java/com/android/voicemailomtp/scheduling
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/voicemailomtp/scheduling')
-rw-r--r--java/com/android/voicemailomtp/scheduling/BaseTask.java206
-rw-r--r--java/com/android/voicemailomtp/scheduling/BlockerTask.java55
-rw-r--r--java/com/android/voicemailomtp/scheduling/MinimalIntervalPolicy.java69
-rw-r--r--java/com/android/voicemailomtp/scheduling/Policy.java36
-rw-r--r--java/com/android/voicemailomtp/scheduling/PostponePolicy.java69
-rw-r--r--java/com/android/voicemailomtp/scheduling/RetryPolicy.java117
-rw-r--r--java/com/android/voicemailomtp/scheduling/Task.java133
-rw-r--r--java/com/android/voicemailomtp/scheduling/TaskSchedulerService.java392
8 files changed, 0 insertions, 1077 deletions
diff --git a/java/com/android/voicemailomtp/scheduling/BaseTask.java b/java/com/android/voicemailomtp/scheduling/BaseTask.java
deleted file mode 100644
index 8097bb4dc..000000000
--- a/java/com/android/voicemailomtp/scheduling/BaseTask.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.SystemClock;
-import android.support.annotation.CallSuper;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.WorkerThread;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.SubscriptionManager;
-import com.android.voicemailomtp.Assert;
-import com.android.voicemailomtp.NeededForTesting;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Provides common utilities for task implementations, such as execution time and managing {@link
- * Policy}
- */
-public abstract class BaseTask implements Task {
-
- private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "extra_phone_account_handle";
-
- private Context mContext;
-
- private int mId;
- private PhoneAccountHandle mPhoneAccountHandle;
-
- private boolean mHasStarted;
- private volatile boolean mHasFailed;
-
- @NonNull
- private final List<Policy> mPolicies = new ArrayList<>();
-
- private long mExecutionTime;
-
- private static Clock sClock = new Clock();
-
- protected BaseTask(int id) {
- mId = id;
- mExecutionTime = getTimeMillis();
- }
-
- /**
- * Modify the task ID to prevent arbitrary task from executing. Can only be called before {@link
- * #onCreate(Context, Intent, int, int)} returns.
- */
- @MainThread
- public void setId(int id) {
- Assert.isMainThread();
- mId = id;
- }
-
- @MainThread
- public boolean hasStarted() {
- Assert.isMainThread();
- return mHasStarted;
- }
-
- @MainThread
- public boolean hasFailed() {
- Assert.isMainThread();
- return mHasFailed;
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public PhoneAccountHandle getPhoneAccountHandle() {
- return mPhoneAccountHandle;
- }
- /**
- * Should be call in the constructor or {@link Policy#onCreate(BaseTask, Intent, int, int)} will
- * be missed.
- */
- @MainThread
- public BaseTask addPolicy(Policy policy) {
- Assert.isMainThread();
- mPolicies.add(policy);
- return this;
- }
-
- /**
- * Indicate the task has failed. {@link Policy#onFail()} will be triggered once the execution
- * ends. This mechanism is used by policies for actions such as determining whether to schedule
- * a retry. Must be call inside {@link #onExecuteInBackgroundThread()}
- */
- @WorkerThread
- public void fail() {
- Assert.isNotMainThread();
- mHasFailed = true;
- }
-
- @MainThread
- public void setExecutionTime(long timeMillis) {
- Assert.isMainThread();
- mExecutionTime = timeMillis;
- }
-
- public long getTimeMillis() {
- return sClock.getTimeMillis();
- }
-
- /**
- * Creates an intent that can be used to restart the current task. Derived class should build
- * their intent upon this.
- */
- public Intent createRestartIntent() {
- return createIntent(getContext(), this.getClass(), mPhoneAccountHandle);
- }
-
- /**
- * Creates an intent that can be used to start the {@link TaskSchedulerService}. Derived class
- * should build their intent upon this.
- */
- public static Intent createIntent(Context context, Class<? extends BaseTask> task,
- PhoneAccountHandle phoneAccountHandle) {
- Intent intent = TaskSchedulerService.createIntent(context, task);
- intent.putExtra(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- return intent;
- }
-
- @Override
- public TaskId getId() {
- return new TaskId(mId, mPhoneAccountHandle);
- }
-
- @Override
- @CallSuper
- public void onCreate(Context context, Intent intent, int flags, int startId) {
- mContext = context;
- mPhoneAccountHandle = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE);
- for (Policy policy : mPolicies) {
- policy.onCreate(this, intent, flags, startId);
- }
- }
-
- @Override
- public long getReadyInMilliSeconds() {
- return mExecutionTime - getTimeMillis();
- }
-
- @Override
- @CallSuper
- public void onBeforeExecute() {
- for (Policy policy : mPolicies) {
- policy.onBeforeExecute();
- }
- mHasStarted = true;
- }
-
- @Override
- @CallSuper
- public void onCompleted() {
- if (mHasFailed) {
- for (Policy policy : mPolicies) {
- policy.onFail();
- }
- }
-
- for (Policy policy : mPolicies) {
- policy.onCompleted();
- }
- }
-
- @Override
- public void onDuplicatedTaskAdded(Task task) {
- for (Policy policy : mPolicies) {
- policy.onDuplicatedTaskAdded();
- }
- }
-
- @NeededForTesting
- static class Clock {
-
- public long getTimeMillis() {
- return SystemClock.elapsedRealtime();
- }
- }
-
- /**
- * Used to replace the clock with an deterministic clock
- */
- @NeededForTesting
- static void setClockForTesting(Clock clock) {
- sClock = clock;
- }
-}
diff --git a/java/com/android/voicemailomtp/scheduling/BlockerTask.java b/java/com/android/voicemailomtp/scheduling/BlockerTask.java
deleted file mode 100644
index 55ad9a7fd..000000000
--- a/java/com/android/voicemailomtp/scheduling/BlockerTask.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Context;
-import android.content.Intent;
-
-import com.android.voicemailomtp.VvmLog;
-
-/**
- * Task to block another task of the same ID from being queued for a certain amount of time.
- */
-public class BlockerTask extends BaseTask {
-
- private static final String TAG = "BlockerTask";
-
- public static final String EXTRA_TASK_ID = "extra_task_id";
- public static final String EXTRA_BLOCK_FOR_MILLIS = "extra_block_for_millis";
-
- public BlockerTask() {
- super(TASK_INVALID);
- }
-
- @Override
- public void onCreate(Context context, Intent intent, int flags, int startId) {
- super.onCreate(context, intent, flags, startId);
- setId(intent.getIntExtra(EXTRA_TASK_ID, TASK_INVALID));
- setExecutionTime(getTimeMillis() + intent.getIntExtra(EXTRA_BLOCK_FOR_MILLIS, 0));
- }
-
- @Override
- public void onExecuteInBackgroundThread() {
- // Do nothing.
- }
-
- @Override
- public void onDuplicatedTaskAdded(Task task) {
- VvmLog
- .v(TAG, task.toString() + "blocked, " + getReadyInMilliSeconds() + "millis remaining");
- }
-}
diff --git a/java/com/android/voicemailomtp/scheduling/MinimalIntervalPolicy.java b/java/com/android/voicemailomtp/scheduling/MinimalIntervalPolicy.java
deleted file mode 100644
index bef449b30..000000000
--- a/java/com/android/voicemailomtp/scheduling/MinimalIntervalPolicy.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Intent;
-
-import com.android.voicemailomtp.scheduling.Task.TaskId;
-
-/**
- * If a task with this policy succeeds, a {@link BlockerTask} with the same {@link TaskId} of the
- * task will be queued immediately, preventing the same task from running for a certain amount of
- * time.
- */
-public class MinimalIntervalPolicy implements Policy {
-
- BaseTask mTask;
- TaskId mId;
- int mBlockForMillis;
-
- public MinimalIntervalPolicy(int blockForMillis) {
- mBlockForMillis = blockForMillis;
- }
-
- @Override
- public void onCreate(BaseTask task, Intent intent, int flags, int startId) {
- mTask = task;
- mId = mTask.getId();
- }
-
- @Override
- public void onBeforeExecute() {
-
- }
-
- @Override
- public void onCompleted() {
- if (!mTask.hasFailed()) {
- Intent intent = mTask
- .createIntent(mTask.getContext(), BlockerTask.class, mId.phoneAccountHandle);
- intent.putExtra(BlockerTask.EXTRA_TASK_ID, mId.id);
- intent.putExtra(BlockerTask.EXTRA_BLOCK_FOR_MILLIS, mBlockForMillis);
- mTask.getContext().startService(intent);
- }
- }
-
- @Override
- public void onFail() {
-
- }
-
- @Override
- public void onDuplicatedTaskAdded() {
-
- }
-}
diff --git a/java/com/android/voicemailomtp/scheduling/Policy.java b/java/com/android/voicemailomtp/scheduling/Policy.java
deleted file mode 100644
index 4a475d2ed..000000000
--- a/java/com/android/voicemailomtp/scheduling/Policy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Intent;
-
-/**
- * A set of listeners managed by {@link BaseTask} for common behaviors such as retrying. Call {@link
- * BaseTask#addPolicy(Policy)} to add a policy.
- */
-public interface Policy {
-
- void onCreate(BaseTask task, Intent intent, int flags, int startId);
-
- void onBeforeExecute();
-
- void onCompleted();
-
- void onFail();
-
- void onDuplicatedTaskAdded();
-}
diff --git a/java/com/android/voicemailomtp/scheduling/PostponePolicy.java b/java/com/android/voicemailomtp/scheduling/PostponePolicy.java
deleted file mode 100644
index 27a82f0ef..000000000
--- a/java/com/android/voicemailomtp/scheduling/PostponePolicy.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Intent;
-
-import com.android.voicemailomtp.VvmLog;
-
-/**
- * A task with Postpone policy will not be executed immediately. It will wait for a while and if a
- * duplicated task is queued during the duration, the task will be postponed further. The task will
- * only be executed if no new task was added in postponeMillis. Useful to batch small tasks in quick
- * succession together.
- */
-public class PostponePolicy implements Policy {
-
- private static final String TAG = "PostponePolicy";
-
- private final int mPostponeMillis;
- private BaseTask mTask;
-
- public PostponePolicy(int postponeMillis) {
- mPostponeMillis = postponeMillis;
- }
-
- @Override
- public void onCreate(BaseTask task, Intent intent, int flags, int startId) {
- mTask = task;
- mTask.setExecutionTime(mTask.getTimeMillis() + mPostponeMillis);
- }
-
- @Override
- public void onBeforeExecute() {
- // Do nothing
- }
-
- @Override
- public void onCompleted() {
- // Do nothing
- }
-
- @Override
- public void onFail() {
- // Do nothing
- }
-
- @Override
- public void onDuplicatedTaskAdded() {
- if (mTask.hasStarted()) {
- return;
- }
- VvmLog.d(TAG, "postponing " + mTask);
- mTask.setExecutionTime(mTask.getTimeMillis() + mPostponeMillis);
- }
-}
diff --git a/java/com/android/voicemailomtp/scheduling/RetryPolicy.java b/java/com/android/voicemailomtp/scheduling/RetryPolicy.java
deleted file mode 100644
index 463657483..000000000
--- a/java/com/android/voicemailomtp/scheduling/RetryPolicy.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Intent;
-import android.telecom.PhoneAccountHandle;
-
-import com.android.voicemailomtp.VoicemailStatus;
-import com.android.voicemailomtp.VvmLog;
-
-/**
- * A task with this policy will automatically re-queue itself if {@link BaseTask#fail()} has been
- * called during {@link BaseTask#onExecuteInBackgroundThread()}. A task will be retried at most
- * <code>retryLimit</code> times and with a <code>retryDelayMillis</code> interval in between.
- */
-public class RetryPolicy implements Policy {
-
- private static final String TAG = "RetryPolicy";
- private static final String EXTRA_RETRY_COUNT = "extra_retry_count";
-
- private final int mRetryLimit;
- private final int mRetryDelayMillis;
-
- private BaseTask mTask;
-
- private int mRetryCount;
- private boolean mFailed;
-
- private VoicemailStatus.DeferredEditor mVoicemailStatusEditor;
-
- public RetryPolicy(int retryLimit, int retryDelayMillis) {
- mRetryLimit = retryLimit;
- mRetryDelayMillis = retryDelayMillis;
- }
-
- private boolean hasMoreRetries() {
- return mRetryCount < mRetryLimit;
- }
-
- /**
- * Error status should only be set if retries has exhausted or the task is successful. Status
- * writes to this editor will be deferred until the task has ended, and will only be committed
- * if the task is successful or there are no retries left.
- */
- public VoicemailStatus.Editor getVoicemailStatusEditor() {
- return mVoicemailStatusEditor;
- }
-
- @Override
- public void onCreate(BaseTask task, Intent intent, int flags, int startId) {
- mTask = task;
- mRetryCount = intent.getIntExtra(EXTRA_RETRY_COUNT, 0);
- if (mRetryCount > 0) {
- VvmLog.d(TAG, "retry #" + mRetryCount + " for " + mTask + " queued, executing in "
- + mRetryDelayMillis);
- mTask.setExecutionTime(mTask.getTimeMillis() + mRetryDelayMillis);
- }
- PhoneAccountHandle phoneAccountHandle = task.getPhoneAccountHandle();
- if (phoneAccountHandle == null) {
- VvmLog.e(TAG,
- "null phone account for phoneAccountHandle " + task.getPhoneAccountHandle());
- // This should never happen, but continue on if it does. The status write will be
- // discarded.
- }
- mVoicemailStatusEditor = VoicemailStatus
- .deferredEdit(task.getContext(), phoneAccountHandle);
- }
-
- @Override
- public void onBeforeExecute() {
-
- }
-
- @Override
- public void onCompleted() {
- if (!mFailed || !hasMoreRetries()) {
- if (!mFailed) {
- VvmLog.d(TAG, mTask.toString() + " completed successfully");
- }
- if (!hasMoreRetries()) {
- VvmLog.d(TAG, "Retry limit for " + mTask + " reached");
- }
- VvmLog.i(TAG, "committing deferred status: " + mVoicemailStatusEditor.getValues());
- mVoicemailStatusEditor.deferredApply();
- return;
- }
- VvmLog.i(TAG, "discarding deferred status: " + mVoicemailStatusEditor.getValues());
- Intent intent = mTask.createRestartIntent();
- intent.putExtra(EXTRA_RETRY_COUNT, mRetryCount + 1);
-
- mTask.getContext().startService(intent);
- }
-
- @Override
- public void onFail() {
- mFailed = true;
- }
-
- @Override
- public void onDuplicatedTaskAdded() {
-
- }
-}
diff --git a/java/com/android/voicemailomtp/scheduling/Task.java b/java/com/android/voicemailomtp/scheduling/Task.java
deleted file mode 100644
index 61c35396b..000000000
--- a/java/com/android/voicemailomtp/scheduling/Task.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.annotation.MainThread;
-import android.support.annotation.WorkerThread;
-import android.telecom.PhoneAccountHandle;
-
-import java.util.Objects;
-
-/**
- * A task for {@link TaskSchedulerService} to execute. Since the task is sent through a intent to
- * the scheduler, The task must be constructable with the intent. Specifically, It must have a
- * constructor with zero arguments, and have all relevant data packed inside the intent. Use {@link
- * TaskSchedulerService#createIntent(Context, Class)} to create a intent that will construct the
- * Task.
- *
- * <p>Only {@link #onExecuteInBackgroundThread()} is run on the worker thread.
- */
-public interface Task {
-
- /**
- * TaskId to indicate it has not be set. If a task does not provide a default TaskId it should
- * be set before {@link Task#onCreate(Context, Intent, int, int) returns}
- */
- int TASK_INVALID = -1;
-
- /**
- * TaskId to indicate it should always be queued regardless of duplicates. {@link
- * Task#onDuplicatedTaskAdded(Task)} will never be called on tasks with this TaskId.
- */
- int TASK_ALLOW_DUPLICATES = -2;
-
- int TASK_UPLOAD = 1;
- int TASK_SYNC = 2;
- int TASK_ACTIVATION = 3;
-
- /**
- * Used to differentiate between types of tasks. If a task with the same TaskId is already in
- * the queue the new task will be rejected.
- */
- class TaskId {
-
- /**
- * Indicates the operation type of the task.
- */
- public final int id;
- /**
- * Same operation for a different phoneAccountHandle is allowed. phoneAccountHandle is used
- * to differentiate phone accounts in multi-SIM scenario. For example, each SIM can queue a
- * sync task for their own.
- */
- public final PhoneAccountHandle phoneAccountHandle;
-
- public TaskId(int id, PhoneAccountHandle phoneAccountHandle) {
- this.id = id;
- this.phoneAccountHandle = phoneAccountHandle;
- }
-
- @Override
- public boolean equals(Object object) {
- if (!(object instanceof TaskId)) {
- return false;
- }
- TaskId other = (TaskId) object;
- return id == other.id && phoneAccountHandle.equals(other.phoneAccountHandle);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, phoneAccountHandle);
- }
- }
-
- TaskId getId();
-
- @MainThread
- void onCreate(Context context, Intent intent, int flags, int startId);
-
- /**
- * @return number of milliSeconds the scheduler should wait before running this task. A value
- * less than {@link TaskSchedulerService#READY_TOLERANCE_MILLISECONDS} will be considered ready.
- * If no tasks are ready, the scheduler will sleep for this amount of time before doing another
- * check (it will still wake if a new task is added). The first task in the queue that is ready
- * will be executed.
- */
- @MainThread
- long getReadyInMilliSeconds();
-
- /**
- * Called on the main thread when the scheduler is about to send the task into the worker
- * thread, calling {@link #onExecuteInBackgroundThread()}
- */
- @MainThread
- void onBeforeExecute();
-
- /**
- * The actual payload of the task, executed on the worker thread.
- */
- @WorkerThread
- void onExecuteInBackgroundThread();
-
- /**
- * Called on the main thread when {@link #onExecuteInBackgroundThread()} has finished or thrown
- * an uncaught exception. The task is already removed from the queue at this point, and a same
- * task can be queued again.
- */
- @MainThread
- void onCompleted();
-
- /**
- * Another task with the same TaskId has been added. Necessary data can be retrieved from the
- * other task, and after this returns the task will be discarded.
- */
- @MainThread
- void onDuplicatedTaskAdded(Task task);
-}
diff --git a/java/com/android/voicemailomtp/scheduling/TaskSchedulerService.java b/java/com/android/voicemailomtp/scheduling/TaskSchedulerService.java
deleted file mode 100644
index 90b50e913..000000000
--- a/java/com/android/voicemailomtp/scheduling/TaskSchedulerService.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.voicemailomtp.scheduling;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.SystemClock;
-import android.support.annotation.MainThread;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import com.android.voicemailomtp.Assert;
-import com.android.voicemailomtp.NeededForTesting;
-import com.android.voicemailomtp.VvmLog;
-import com.android.voicemailomtp.scheduling.Task.TaskId;
-import java.util.ArrayDeque;
-import java.util.Queue;
-
-/**
- * A service to queue and run {@link Task} on a worker thread. Only one task will be ran at a time,
- * and same task cannot exist in the queue at the same time. The service will be started when a
- * intent is received, and stopped when there are no more tasks in the queue.
- */
-public class TaskSchedulerService extends Service {
-
- private static final String TAG = "VvmTaskScheduler";
-
- private static final String ACTION_WAKEUP = "action_wakeup";
-
- private static final int READY_TOLERANCE_MILLISECONDS = 100;
-
- /**
- * Threshold to determine whether to do a short or long sleep when a task is scheduled in the
- * future.
- *
- * <p>A short sleep will continue to held the wake lock and use {@link
- * Handler#postDelayed(Runnable, long)} to wait for the next task.
- *
- * <p>A long sleep will release the wake lock and set a {@link AlarmManager} alarm. The alarm is
- * exact and will wake up the device. Note: as this service is run in the telephony process it
- * does not seem to be restricted by doze or sleep, it will fire exactly at the moment. The
- * unbundled version should take doze into account.
- */
- private static final int SHORT_SLEEP_THRESHOLD_MILLISECONDS = 60_000;
- /**
- * When there are no more tasks to be run the service should be stopped. But when all tasks has
- * finished there might still be more tasks in the message queue waiting to be processed,
- * especially the ones submitted in {@link Task#onCompleted()}. Wait for a while before stopping
- * the service to make sure there are no pending messages.
- */
- private static final int STOP_DELAY_MILLISECONDS = 5_000;
- private static final String EXTRA_CLASS_NAME = "extra_class_name";
-
- private static final String WAKE_LOCK_TAG = "TaskSchedulerService_wakelock";
-
- // The thread to run tasks on
- private volatile WorkerThreadHandler mWorkerThreadHandler;
-
- private Context mContext = this;
- /**
- * Used by tests to turn task handling into a single threaded process by calling {@link
- * Handler#handleMessage(Message)} directly
- */
- private MessageSender mMessageSender = new MessageSender();
-
- private MainThreadHandler mMainThreadHandler;
-
- private WakeLock mWakeLock;
-
- /**
- * Main thread only, access through {@link #getTasks()}
- */
- private final Queue<Task> mTasks = new ArrayDeque<>();
- private boolean mWorkerThreadIsBusy = false;
-
- private final Runnable mStopServiceWithDelay = new Runnable() {
- @Override
- public void run() {
- VvmLog.d(TAG, "Stopping service");
- stopSelf();
- }
- };
- /**
- * Should attempt to run the next task when a task has finished or been added.
- */
- private boolean mTaskAutoRunDisabledForTesting = false;
-
- @VisibleForTesting
- final class WorkerThreadHandler extends Handler {
-
- public WorkerThreadHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- @WorkerThread
- public void handleMessage(Message msg) {
- Assert.isNotMainThread();
- Task task = (Task) msg.obj;
- try {
- VvmLog.v(TAG, "executing task " + task);
- task.onExecuteInBackgroundThread();
- } catch (Throwable throwable) {
- VvmLog.e(TAG, "Exception while executing task " + task + ":", throwable);
- }
-
- Message schedulerMessage = mMainThreadHandler.obtainMessage();
- schedulerMessage.obj = task;
- mMessageSender.send(schedulerMessage);
- }
- }
-
- @VisibleForTesting
- final class MainThreadHandler extends Handler {
-
- public MainThreadHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- @MainThread
- public void handleMessage(Message msg) {
- Assert.isMainThread();
- Task task = (Task) msg.obj;
- getTasks().remove(task);
- task.onCompleted();
- mWorkerThreadIsBusy = false;
- maybeRunNextTask();
- }
- }
-
- @Override
- @MainThread
- public void onCreate() {
- super.onCreate();
- mWakeLock = getSystemService(PowerManager.class)
- .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
- mWakeLock.setReferenceCounted(false);
- HandlerThread thread = new HandlerThread("VvmTaskSchedulerService");
- thread.start();
-
- mWorkerThreadHandler = new WorkerThreadHandler(thread.getLooper());
- mMainThreadHandler = new MainThreadHandler(Looper.getMainLooper());
- }
-
- @Override
- public void onDestroy() {
- mWorkerThreadHandler.getLooper().quit();
- mWakeLock.release();
- }
-
- @Override
- @MainThread
- public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
- Assert.isMainThread();
- // maybeRunNextTask() will release the wakelock either by entering a long sleep or stopping
- // the service.
- mWakeLock.acquire();
- if (ACTION_WAKEUP.equals(intent.getAction())) {
- VvmLog.d(TAG, "woke up by AlarmManager");
- } else {
- Task task = createTask(intent, flags, startId);
- if (task == null) {
- VvmLog.e(TAG, "cannot create task form intent");
- } else {
- addTask(task);
- }
- }
- maybeRunNextTask();
- // STICKY means the service will be automatically restarted will the last intent if it is
- // killed.
- return START_NOT_STICKY;
- }
-
- @MainThread
- @VisibleForTesting
- void addTask(Task task) {
- Assert.isMainThread();
- if (task.getId().id == Task.TASK_INVALID) {
- throw new AssertionError("Task id was not set to a valid value before adding.");
- }
- if (task.getId().id != Task.TASK_ALLOW_DUPLICATES) {
- Task oldTask = getTask(task.getId());
- if (oldTask != null) {
- oldTask.onDuplicatedTaskAdded(task);
- return;
- }
- }
- mMainThreadHandler.removeCallbacks(mStopServiceWithDelay);
- getTasks().add(task);
- maybeRunNextTask();
- }
-
- @MainThread
- @Nullable
- private Task getTask(TaskId taskId) {
- Assert.isMainThread();
- for (Task task : getTasks()) {
- if (task.getId().equals(taskId)) {
- return task;
- }
- }
- return null;
- }
-
- @MainThread
- private Queue<Task> getTasks() {
- Assert.isMainThread();
- return mTasks;
- }
-
- /**
- * Create an intent that will queue the <code>task</code>
- */
- public static Intent createIntent(Context context, Class<? extends Task> task) {
- Intent intent = new Intent(context, TaskSchedulerService.class);
- intent.putExtra(EXTRA_CLASS_NAME, task.getName());
- return intent;
- }
-
- @VisibleForTesting
- @MainThread
- @Nullable
- Task createTask(@Nullable Intent intent, int flags, int startId) {
- Assert.isMainThread();
- if (intent == null) {
- return null;
- }
- String className = intent.getStringExtra(EXTRA_CLASS_NAME);
- VvmLog.d(TAG, "create task:" + className);
- if (className == null) {
- throw new IllegalArgumentException("EXTRA_CLASS_NAME expected");
- }
- try {
- Task task = (Task) Class.forName(className).newInstance();
- task.onCreate(mContext, intent, flags, startId);
- return task;
- } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- @MainThread
- private void maybeRunNextTask() {
- Assert.isMainThread();
- if (mWorkerThreadIsBusy) {
- return;
- }
- if (mTaskAutoRunDisabledForTesting) {
- // If mTaskAutoRunDisabledForTesting is true, runNextTask() must be explicitly called
- // to run the next task.
- return;
- }
-
- runNextTask();
- }
-
- @VisibleForTesting
- @MainThread
- void runNextTask() {
- Assert.isMainThread();
- // The current alarm is no longer valid, a new one will be set up if required.
- getSystemService(AlarmManager.class).cancel(getWakeupIntent());
- if (getTasks().isEmpty()) {
- prepareStop();
- return;
- }
- Long minimalWaitTime = null;
- for (Task task : getTasks()) {
- long waitTime = task.getReadyInMilliSeconds();
- if (waitTime < READY_TOLERANCE_MILLISECONDS) {
- task.onBeforeExecute();
- Message message = mWorkerThreadHandler.obtainMessage();
- message.obj = task;
- mWorkerThreadIsBusy = true;
- mMessageSender.send(message);
- return;
- } else {
- if (minimalWaitTime == null || waitTime < minimalWaitTime) {
- minimalWaitTime = waitTime;
- }
- }
- }
- VvmLog.d(TAG, "minimal wait time:" + minimalWaitTime);
- if (!mTaskAutoRunDisabledForTesting && minimalWaitTime != null) {
- // No tasks are currently ready. Sleep until the next one should be.
- // If a new task is added during the sleep the service will wake immediately.
- sleep(minimalWaitTime);
- }
- }
-
- private void sleep(long timeMillis) {
- if (timeMillis < SHORT_SLEEP_THRESHOLD_MILLISECONDS) {
- mMainThreadHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- maybeRunNextTask();
- }
- }, timeMillis);
- return;
- }
-
- // Tasks does not have a strict timing requirement, use AlarmManager.set() so the OS could
- // optimize the battery usage. As this service currently run in the telephony process the
- // OS give it privileges to behave the same as setExact(), but set() is the targeted
- // behavior once this is unbundled.
- getSystemService(AlarmManager.class).set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + timeMillis,
- getWakeupIntent());
- mWakeLock.release();
- VvmLog.d(TAG, "Long sleep for " + timeMillis + " millis");
- }
-
- private PendingIntent getWakeupIntent() {
- Intent intent = new Intent(ACTION_WAKEUP, null, this, getClass());
- return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- }
-
- private void prepareStop() {
- VvmLog.d(TAG,
- "No more tasks, stopping service if no task are added in "
- + STOP_DELAY_MILLISECONDS + " millis");
- mMainThreadHandler.postDelayed(mStopServiceWithDelay, STOP_DELAY_MILLISECONDS);
- }
-
- static class MessageSender {
-
- public void send(Message message) {
- message.sendToTarget();
- }
- }
-
- @NeededForTesting
- void setContextForTest(Context context) {
- mContext = context;
- }
-
- @NeededForTesting
- void setTaskAutoRunDisabledForTest(boolean value) {
- mTaskAutoRunDisabledForTesting = value;
- }
-
- @NeededForTesting
- void setMessageSenderForTest(MessageSender sender) {
- mMessageSender = sender;
- }
-
- @NeededForTesting
- void clearTasksForTest() {
- mTasks.clear();
- }
-
- @Override
- @Nullable
- public IBinder onBind(Intent intent) {
- return new LocalBinder();
- }
-
- @NeededForTesting
- class LocalBinder extends Binder {
-
- @NeededForTesting
- public TaskSchedulerService getService() {
- return TaskSchedulerService.this;
- }
- }
-}