From d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9 Mon Sep 17 00:00:00 2001 From: Eric Erfanian Date: Wed, 15 Mar 2017 14:41:07 -0700 Subject: Update Dialer source from latest green build. * Refactor voicemail component * Add new enriched calling components Test: treehugger, manual aosp testing Change-Id: I521a0f86327d4b42e14d93927c7d613044ed5942 --- .../android/voicemailomtp/scheduling/BaseTask.java | 206 ----------- .../voicemailomtp/scheduling/BlockerTask.java | 55 --- .../scheduling/MinimalIntervalPolicy.java | 69 ---- .../android/voicemailomtp/scheduling/Policy.java | 36 -- .../voicemailomtp/scheduling/PostponePolicy.java | 69 ---- .../voicemailomtp/scheduling/RetryPolicy.java | 117 ------ .../com/android/voicemailomtp/scheduling/Task.java | 133 ------- .../scheduling/TaskSchedulerService.java | 392 --------------------- 8 files changed, 1077 deletions(-) delete mode 100644 java/com/android/voicemailomtp/scheduling/BaseTask.java delete mode 100644 java/com/android/voicemailomtp/scheduling/BlockerTask.java delete mode 100644 java/com/android/voicemailomtp/scheduling/MinimalIntervalPolicy.java delete mode 100644 java/com/android/voicemailomtp/scheduling/Policy.java delete mode 100644 java/com/android/voicemailomtp/scheduling/PostponePolicy.java delete mode 100644 java/com/android/voicemailomtp/scheduling/RetryPolicy.java delete mode 100644 java/com/android/voicemailomtp/scheduling/Task.java delete mode 100644 java/com/android/voicemailomtp/scheduling/TaskSchedulerService.java (limited to 'java/com/android/voicemailomtp/scheduling') 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 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 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 - * retryLimit times and with a retryDelayMillis 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. - * - *

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. - * - *

A short sleep will continue to held the wake lock and use {@link - * Handler#postDelayed(Runnable, long)} to wait for the next task. - * - *

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 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 getTasks() { - Assert.isMainThread(); - return mTasks; - } - - /** - * Create an intent that will queue the task - */ - public static Intent createIntent(Context context, Class 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; - } - } -} -- cgit v1.2.3