Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 691e93e8 authored by Matthew Williams's avatar Matthew Williams
Browse files

TM TaskServiceContext implementation

Each task is run on the client from within a TaskServiceContext.
TSC tracks the state of execution of each task on the client.

Change-Id: I93c306a83c1115559f4e9675d9997dceae3f186a
parent 8974f249
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -34,14 +34,17 @@ interface ITaskCallback {
     * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
     *
     * @param taskId Unique integer used to identify this task.
     * @param ongoing True to indicate that the client is processing the task. False if the task is
     * complete
     */
    void acknowledgeStartMessage(int taskId);
    void acknowledgeStartMessage(int taskId, boolean ongoing);
    /**
     * Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
     *
     * @param taskId Unique integer used to identify this task.
     * @param rescheulde Whether or not to reschedule this task.
     */
    void acknowledgeStopMessage(int taskId);
    void acknowledgeStopMessage(int taskId, boolean reschedule);
    /*
     * Tell the task manager that the client is done with its execution, so that it can go on to
     * the next one and stop attributing wakelock time to us etc.
+39 −42
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package android.app.task;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -124,22 +123,20 @@ public abstract class TaskService extends Service {
            switch (msg.what) {
                case MSG_EXECUTE_TASK:
                    try {
                        TaskService.this.onStartTask(params);
                        boolean workOngoing = TaskService.this.onStartTask(params);
                        ackStartMessage(params, workOngoing);
                    } catch (Exception e) {
                        Log.e(TAG, "Error while executing task: " + params.getTaskId());
                        throw new RuntimeException(e);
                    } finally {
                        maybeAckMessageReceived(params, MSG_EXECUTE_TASK);
                    }
                    break;
                case MSG_STOP_TASK:
                    try {
                        TaskService.this.onStopTask(params);
                        boolean ret = TaskService.this.onStopTask(params);
                        ackStopMessage(params, ret);
                    } catch (Exception e) {
                        Log.e(TAG, "Application unable to handle onStopTask.", e);
                        throw new RuntimeException(e);
                    } finally {
                        maybeAckMessageReceived(params, MSG_STOP_TASK);
                    }
                    break;
                case MSG_TASK_FINISHED:
@@ -162,30 +159,34 @@ public abstract class TaskService extends Service {
            }
        }

        /**
         * Messages come in on the application's main thread, so rather than run the risk of
         * waiting for an app that may be doing something foolhardy, we ack to the system after
         * processing a message. This allows us to throw up an ANR dialogue as quickly as possible.
         * @param params id of the task we're acking.
         * @param state Information about what message we're acking.
         */
        private void maybeAckMessageReceived(TaskParams params, int state) {
        private void ackStartMessage(TaskParams params, boolean workOngoing) {
            final ITaskCallback callback = params.getCallback();
            final int taskId = params.getTaskId();
            if (callback != null) {
                try {
                    if (state == MSG_EXECUTE_TASK) {
                        callback.acknowledgeStartMessage(taskId);
                    } else if (state == MSG_STOP_TASK) {
                        callback.acknowledgeStopMessage(taskId);
                    }
                     callback.acknowledgeStartMessage(taskId, workOngoing);
                } catch(RemoteException e) {
                    Log.e(TAG, "System unreachable for starting task.");
                }
            } else {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, state + ": Attempting to ack a task that has already been" +
                            "processed.");
                    Log.d(TAG, "Attempting to ack a task that has already been processed.");
                }
            }
        }

        private void ackStopMessage(TaskParams params, boolean reschedule) {
            final ITaskCallback callback = params.getCallback();
            final int taskId = params.getTaskId();
            if (callback != null) {
                try {
                    callback.acknowledgeStopMessage(taskId, reschedule);
                } catch(RemoteException e) {
                    Log.e(TAG, "System unreachable for stopping task.");
                }
            } else {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Attempting to ack a task that has already been processed.");
                }
            }
        }
@@ -203,12 +204,14 @@ public abstract class TaskService extends Service {
     *
     * @param params Parameters specifying info about this task, including the extras bundle you
     *               optionally provided at task-creation time.
     * @return True if your service needs to process the work (on a separate thread). False if
     * there's no more work to be done for this task.
     */
    public abstract void onStartTask(TaskParams params);
    public abstract boolean onStartTask(TaskParams params);

    /**
     * This method is called if your task should be stopped even before you've called
     * {@link #taskFinished(TaskParams, boolean)}.
     * This method is called if the system has determined that you must stop execution of your task
     * even before you've had a chance to call {@link #taskFinished(TaskParams, boolean)}.
     *
     * <p>This will happen if the requirements specified at schedule time are no longer met. For
     * example you may have requested WiFi with
@@ -217,33 +220,27 @@ public abstract class TaskService extends Service {
     * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
     * idle maintenance window. You are solely responsible for the behaviour of your application
     * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
     * repercussion is that the system will cease to hold a wakelock for you.</p>
     *
     * <p>After you've done your clean-up you are still expected to call
     * {@link #taskFinished(TaskParams, boolean)} this will inform the TaskManager that all is well, and
     * allow you to reschedule your task as it is probably uncompleted. Until you call
     * taskFinished() you will not receive any newly scheduled tasks with the given task id as the
     * TaskManager will consider the task to be in an error state.</p>
     * immediate repercussion is that the system will cease holding a wakelock for you.</p>
     *
     * @param params Parameters specifying info about this task.
     * @return True to indicate to the TaskManager whether you'd like to reschedule this task based
     * on the criteria provided at task creation-time. False to drop the task. Regardless of the
     * value returned, your task must stop executing.
     * on the retry criteria provided at task creation-time. False to drop the task. Regardless of
     * the value returned, your task must stop executing.
     */
    public abstract boolean onStopTask(TaskParams params);

    /**
     * Callback to inform the TaskManager you have completed execution. This can be called from any
     * Callback to inform the TaskManager you've finished executing. This can be called from any
     * thread, as it will ultimately be run on your application's main thread. When the system
     * receives this message it will release the wakelock being held.
     * <p>
     *     You can specify post-execution behaviour to the scheduler here with <code>needsReschedule
     *     </code>. This will apply a back-off timer to your task based on the default, or what was
     *     set with {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The
     *     original requirements are always honoured even for a backed-off task.
     *     Note that a task running in idle mode will not be backed-off. Instead what will happen
     *     is the task will be re-added to the queue and re-executed within a future idle
     *     maintenance window.
     *     You can specify post-execution behaviour to the scheduler here with
     *     <code>needsReschedule </code>. This will apply a back-off timer to your task based on
     *     the default, or what was set with
     *     {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The original
     *     requirements are always honoured even for a backed-off task. Note that a task running in
     *     idle mode will not be backed-off. Instead what will happen is the task will be re-added
     *     to the queue and re-executed within a future idle maintenance window.
     * </p>
     *
     * @param params Parameters specifying system-provided info about this task, this was given to
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.server.task;

/**
 * Used for communication between {@link com.android.server.task.TaskServiceContext} and the
 * {@link com.android.server.task.TaskManagerService}.
 */
public interface TaskCompletedListener {

    /**
     * Callback for when a task is completed.
     * @param needsReschedule Whether the implementing class should reschedule this task.
     */
    public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule);

    /**
     * Callback for when the implementing class needs to clean up the
     * {@link com.android.server.task.TaskServiceContext}. The scheduler can get this callback
     * several times if the TaskServiceContext got into a bad state (for e.g. the client crashed
     * and it needs to clean up).
     */
    public void onAllTasksCompleted(int serviceToken);
}
+105 −26
Original line number Diff line number Diff line
@@ -17,16 +17,15 @@
package com.android.server.task;

import android.content.Context;
import android.content.Task;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.SparseArray;

import com.android.server.task.controllers.TaskStatus;

import java.util.ArrayList;
import java.util.List;

/**
 * Responsible for taking tasks representing work to be performed by a client app, and determining
 * based on the criteria specified when that task should be run against the client application's
@@ -34,25 +33,29 @@ import java.util.List;
 * @hide
 */
public class TaskManagerService extends com.android.server.SystemService
        implements StateChangedListener {
        implements StateChangedListener, TaskCompletedListener {
    static final String TAG = "TaskManager";

    /** Master list of tasks. */
    private final TaskList mTaskList;
    private final TaskStore mTasks;

    /** Check the pending queue and start any tasks. */
    static final int MSG_RUN_PENDING = 0;
    /** Initiate the stop task flow. */
    static final int MSG_STOP_TASK = 1;
    /** */
    static final int MSG_CHECK_TASKS = 2;

    /**
     * Track Services that have currently active or pending tasks. The index is provided by
     * {@link TaskStatus#getServiceToken()}
     */
    private final SparseArray<TaskServiceContext> mPendingTaskServices =
    private final SparseArray<TaskServiceContext> mActiveServices =
            new SparseArray<TaskServiceContext>();

    private final TaskHandler mHandler;

    private class TaskHandler extends Handler {
        /** Check the pending queue and start any tasks. */
        static final int MSG_RUN_PENDING = 0;
        /** Initiate the stop task flow. */
        static final int MSG_STOP_TASK = 1;

        public TaskHandler(Looper looper) {
            super(looper);
@@ -66,24 +69,45 @@ public class TaskManagerService extends com.android.server.SystemService
                    break;
                case MSG_STOP_TASK:

                    break;
                case MSG_CHECK_TASKS:
                    checkTasks();
                    break;
            }
        }

        /**
         * Helper to post a message to this handler that will run through the pending queue and
         * start any tasks it can.
         * Called when we need to run through the list of all tasks and start/stop executing one or
         * more of them.
         */
        void sendRunPendingTasksMessage() {
            Message m = Message.obtain(this, MSG_RUN_PENDING);
            m.sendToTarget();
        private void checkTasks() {
            synchronized (mTasks) {
                final SparseArray<TaskStatus> tasks = mTasks.getTasks();
                for (int i = 0; i < tasks.size(); i++) {
                    TaskStatus ts = tasks.valueAt(i);
                    if (ts.isReady() && ! isCurrentlyActive(ts)) {
                        assignTaskToServiceContext(ts);
                    }
                }
            }

        void sendOnStopMessage(TaskStatus taskStatus) {

        }
    }

    /**
     * Entry point from client to schedule the provided task.
     * This will add the task to the
     * @param task Task object containing execution parameters
     * @param userId The id of the user this task is for.
     * @param uId The package identifier of the application this task is for.
     * @param canPersistTask Whether or not the client has the appropriate permissions for persisting
     *                    of this task.
     * @return Result of this operation. See <code>TaskManager#RESULT_*</code> return codes.
     */
    public int schedule(Task task, int userId, int uId, boolean canPersistTask) {
        TaskStatus taskStatus = mTasks.addNewTaskForUser(task, userId, uId, canPersistTask);
        return 0;
    }

    /**
     * Initializes the system service.
     * <p>
@@ -95,7 +119,7 @@ public class TaskManagerService extends com.android.server.SystemService
     */
    public TaskManagerService(Context context) {
        super(context);
        mTaskList = new TaskList();
        mTasks = new TaskStore(context);
        mHandler = new TaskHandler(context.getMainLooper());
    }

@@ -104,25 +128,80 @@ public class TaskManagerService extends com.android.server.SystemService

    }

    // StateChangedListener implementations.

    /**
     * Offboard work to our handler thread as quickly as possible, b/c this call is probably being
     * Off-board work to our handler thread as quickly as possible, b/c this call is probably being
     * made on the main thread.
     * For now this takes the task and if it's ready to run it will run it. In future we might not
     * provide the task, so that the StateChangedListener has to run through its list of tasks to
     * see which are ready. This will further decouple the controllers from the execution logic.
     * @param taskStatus The state of the task which has changed.
     */
    @Override
    public void onTaskStateChanged(TaskStatus taskStatus) {
        if (taskStatus.isReady()) {
        postCheckTasksMessage();

        } else {
            if (mPendingTaskServices.get(taskStatus.getServiceToken()) != null) {
                // The task is either pending or being executed, which we have to cancel.
    }

    @Override
    public void onTaskDeadlineExpired(TaskStatus taskStatus) {

    }

    // TaskCompletedListener implementations.

    /**
     * A task just finished executing. We fetch the
     * {@link com.android.server.task.controllers.TaskStatus} from the store and depending on
     * whether we want to reschedule we readd it to the controllers.
     * @param serviceToken key for the service context in {@link #mActiveServices}.
     * @param taskId Id of the task that is complete.
     * @param needsReschedule Whether the implementing class should reschedule this task.
     */
    @Override
    public void onTaskCompleted(int serviceToken, int taskId, boolean needsReschedule) {
        final TaskServiceContext serviceContext = mActiveServices.get(serviceToken);
        if (serviceContext == null) {
            Log.e(TAG, "Task completed for invalid service context; " + serviceToken);
            return;
        }

    }

    @Override
    public void onTaskDeadlineExpired(TaskStatus taskStatus) {
    public void onClientExecutionCompleted(int serviceToken) {
        
    }

    private void assignTaskToServiceContext(TaskStatus ts) {
        TaskServiceContext serviceContext =
                mActiveServices.get(ts.getServiceToken());
        if (serviceContext == null) {
            serviceContext = new TaskServiceContext(this, mHandler.getLooper(), ts);
            mActiveServices.put(ts.getServiceToken(), serviceContext);
        }
        serviceContext.addPendingTask(ts);
    }

    /**
     * @param ts TaskStatus we are querying against.
     * @return Whether or not the task represented by the status object is currently being run or
     * is pending.
     */
    private boolean isCurrentlyActive(TaskStatus ts) {
        TaskServiceContext serviceContext = mActiveServices.get(ts.getServiceToken());
        if (serviceContext == null) {
            return false;
        }
        return serviceContext.hasTaskPending(ts);
    }

    /**
     * Post a message to {@link #mHandler} to run through the list of tasks and start/stop any that
     * are eligible.
     */
    private void postCheckTasksMessage() {
        mHandler.obtainMessage(MSG_CHECK_TASKS).sendToTarget();
    }
}
+440 −19

File changed.

Preview size limit exceeded, changes collapsed.

Loading