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

Commit 46038f2a authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Add data transfer JobService APIs."

parents 5bf86ebc 7a5d66b5
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
@@ -29,6 +29,25 @@ import android.app.job.JobWorkItem;
 * {@hide}
 */
interface IJobCallback {
    /**
     * Immediate callback to the system after sending a data transfer download progress request
     * signal; used to quickly detect ANR.
     *
     * @param jobId Unique integer used to identify this job.
     * @param workId Unique integer used to identify a specific work item.
     * @param transferredBytes How much data has been downloaded, in bytes.
     */
    void acknowledgeGetTransferredDownloadBytesMessage(int jobId, int workId,
            long transferredBytes);
    /**
     * Immediate callback to the system after sending a data transfer upload progress request
     * signal; used to quickly detect ANR.
     *
     * @param jobId Unique integer used to identify this job.
     * @param workId Unique integer used to identify a specific work item.
     * @param transferredBytes How much data has been uploaded, in bytes.
     */
    void acknowledgeGetTransferredUploadBytesMessage(int jobId, int workId, long transferredBytes);
    /**
     * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
     *
@@ -65,4 +84,24 @@ interface IJobCallback {
     */
    @UnsupportedAppUsage
    void jobFinished(int jobId, boolean reschedule);
    /*
     * Inform JobScheduler of a change in the estimated transfer payload.
     *
     * @param jobId Unique integer used to identify this job.
     * @param item The particular JobWorkItem this progress is associated with, if any.
     * @param downloadBytes How many bytes the app expects to download.
     * @param uploadBytes How many bytes the app expects to upload.
     */
    void updateEstimatedNetworkBytes(int jobId, in JobWorkItem item,
            long downloadBytes, long uploadBytes);
    /*
     * Update JobScheduler of how much data the job has successfully transferred.
     *
     * @param jobId Unique integer used to identify this job.
     * @param item The particular JobWorkItem this progress is associated with, if any.
     * @param transferredDownloadBytes The number of bytes that have successfully been downloaded.
     * @param transferredUploadBytes The number of bytes that have successfully been uploaded.
     */
    void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
            long transferredDownloadBytes, long transferredUploadBytes);
}
+5 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app.job;

import android.app.job.JobParameters;
import android.app.job.JobWorkItem;

/**
 * Interface that the framework uses to communicate with application code that implements a
@@ -31,4 +32,8 @@ oneway interface IJobService {
    /** Stop execution of application's job. */
    @UnsupportedAppUsage
    void stopJob(in JobParameters jobParams);
    /** Update JS of how much data has been downloaded. */
    void getTransferredDownloadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
    /** Update JS of how much data has been uploaded. */
    void getTransferredUploadBytes(in JobParameters jobParams, in JobWorkItem jobWorkItem);
}
+13 −0
Original line number Diff line number Diff line
@@ -22,8 +22,11 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.ClipData;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;

@@ -93,6 +96,16 @@ import java.util.List;
 */
@SystemService(Context.JOB_SCHEDULER_SERVICE)
public abstract class JobScheduler {
    /**
     * Whether to throw an exception when an app doesn't properly implement all the necessary
     * data transfer APIs.
     *
     * @hide
     */
    @ChangeId
    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    public static final long THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION = 255371817L;

    /** @hide */
    @IntDef(prefix = { "RESULT_" }, value = {
            RESULT_FAILURE,
+185 −0
Original line number Diff line number Diff line
@@ -16,7 +16,13 @@

package android.app.job;

import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;

import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
import android.os.IBinder;

@@ -72,6 +78,28 @@ public abstract class JobService extends Service {
                public boolean onStopJob(JobParameters params) {
                    return JobService.this.onStopJob(params);
                }

                @Override
                @BytesLong
                public long getTransferredDownloadBytes(@NonNull JobParameters params,
                        @Nullable JobWorkItem item) {
                    if (item == null) {
                        return JobService.this.getTransferredDownloadBytes();
                    } else {
                        return JobService.this.getTransferredDownloadBytes(item);
                    }
                }

                @Override
                @BytesLong
                public long getTransferredUploadBytes(@NonNull JobParameters params,
                        @Nullable JobWorkItem item) {
                    if (item == null) {
                        return JobService.this.getTransferredUploadBytes();
                    } else {
                        return JobService.this.getTransferredUploadBytes(item);
                    }
                }
            };
        }
        return mEngine.getBinder();
@@ -171,4 +199,161 @@ public abstract class JobService extends Service {
     * to end the job entirely.  Regardless of the value returned, your job must stop executing.
     */
    public abstract boolean onStopJob(JobParameters params);

    /**
     * Update how much data this job will transfer. This method can
     * be called multiple times within the first 30 seconds after
     * {@link #onStartJob(JobParameters)} has been called. Only
     * one call will be heeded after that time has passed.
     *
     * This method (or an overload) must be called within the first
     * 30 seconds for a data transfer job if a payload size estimate
     * was not provided at the time of scheduling.
     *
     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
     */
    public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
        mEngine.updateEstimatedNetworkBytes(params, null, downloadBytes, uploadBytes);
    }

    /**
     * Update how much data will transfer for the JobWorkItem. This
     * method can be called multiple times within the first 30 seconds
     * after {@link #onStartJob(JobParameters)} has been called.
     * Only one call will be heeded after that time has passed.
     *
     * This method (or an overload) must be called within the first
     * 30 seconds for a data transfer job if a payload size estimate
     * was not provided at the time of scheduling.
     *
     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
     */
    public final void updateEstimatedNetworkBytes(@NonNull JobParameters params,
            @NonNull JobWorkItem jobWorkItem,
            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
        mEngine.updateEstimatedNetworkBytes(params, jobWorkItem, downloadBytes, uploadBytes);
    }

    /**
     * Tell JobScheduler how much data has successfully been transferred for the data transfer job.
     */
    public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
            @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
        mEngine.updateTransferredNetworkBytes(params, null,
                transferredDownloadBytes, transferredUploadBytes);
    }

    /**
     * Tell JobScheduler how much data has been transferred for the data transfer
     * {@link JobWorkItem}.
     */
    public final void updateTransferredNetworkBytes(@NonNull JobParameters params,
            @NonNull JobWorkItem item,
            @BytesLong long transferredDownloadBytes, @BytesLong long transferredUploadBytes) {
        mEngine.updateTransferredNetworkBytes(params, item,
                transferredDownloadBytes, transferredUploadBytes);
    }

    /**
     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
     * will call this if the job has specified positive estimated download bytes and
     * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
     * hasn't been called recently.
     *
     * <p>
     * This must be implemented for all data transfer jobs.
     *
     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
     * @see JobInfo#NETWORK_BYTES_UNKNOWN
     */
    // TODO(255371817): specify the actual time JS will wait for progress before requesting
    @BytesLong
    public long getTransferredDownloadBytes() {
        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
            // Regular jobs don't have to implement this and JobScheduler won't call this API for
            // non-data transfer jobs.
            throw new RuntimeException("Not implemented. Must override in a subclass.");
        }
        return 0;
    }

    /**
     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
     * will call this if the job has specified positive estimated upload bytes and
     * {@link #updateTransferredNetworkBytes(JobParameters, long, long)}
     * hasn't been called recently.
     *
     * <p>
     * This must be implemented for all data transfer jobs.
     *
     * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
     * @see JobInfo#NETWORK_BYTES_UNKNOWN
     */
    // TODO(255371817): specify the actual time JS will wait for progress before requesting
    @BytesLong
    public long getTransferredUploadBytes() {
        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
            // Regular jobs don't have to implement this and JobScheduler won't call this API for
            // non-data transfer jobs.
            throw new RuntimeException("Not implemented. Must override in a subclass.");
        }
        return 0;
    }

    /**
     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
     * will call this if the job has specified positive estimated download bytes and
     * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
     * hasn't been called recently and the job has
     * {@link JobWorkItem JobWorkItems} that have been
     * {@link JobParameters#dequeueWork dequeued} but not
     * {@link JobParameters#completeWork(JobWorkItem) completed}.
     *
     * <p>
     * This must be implemented for all data transfer jobs.
     *
     * @see JobInfo#NETWORK_BYTES_UNKNOWN
     */
    // TODO(255371817): specify the actual time JS will wait for progress before requesting
    @BytesLong
    public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
        if (item == null) {
            return getTransferredDownloadBytes();
        }
        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
            // Regular jobs don't have to implement this and JobScheduler won't call this API for
            // non-data transfer jobs.
            throw new RuntimeException("Not implemented. Must override in a subclass.");
        }
        return 0;
    }

    /**
     * Get the number of bytes the app has successfully downloaded for this job. JobScheduler
     * will call this if the job has specified positive estimated upload bytes and
     * {@link #updateTransferredNetworkBytes(JobParameters, JobWorkItem, long, long)}
     * hasn't been called recently and the job has
     * {@link JobWorkItem JobWorkItems} that have been
     * {@link JobParameters#dequeueWork dequeued} but not
     * {@link JobParameters#completeWork(JobWorkItem) completed}.
     *
     * <p>
     * This must be implemented for all data transfer jobs.
     *
     * @see JobInfo#NETWORK_BYTES_UNKNOWN
     */
    // TODO(255371817): specify the actual time JS will wait for progress before requesting
    @BytesLong
    public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
        if (item == null) {
            return getTransferredUploadBytes();
        }
        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
            // Regular jobs don't have to implement this and JobScheduler won't call this API for
            // non-data transfer jobs.
            throw new RuntimeException("Not implemented. Must override in a subclass.");
        }
        return 0;
    }
}
+220 −5
Original line number Diff line number Diff line
@@ -16,7 +16,13 @@

package android.app.job;

import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;

import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
@@ -25,6 +31,8 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.os.SomeArgs;

import java.lang.ref.WeakReference;

/**
@@ -51,6 +59,20 @@ public abstract class JobServiceEngine {
     * Message that the client has completed execution of this job.
     */
    private static final int MSG_JOB_FINISHED = 2;
    /**
     * Message that will result in a call to
     * {@link #getTransferredDownloadBytes(JobParameters, JobWorkItem)}.
     */
    private static final int MSG_GET_TRANSFERRED_DOWNLOAD_BYTES = 3;
    /**
     * Message that will result in a call to
     * {@link #getTransferredUploadBytes(JobParameters, JobWorkItem)}.
     */
    private static final int MSG_GET_TRANSFERRED_UPLOAD_BYTES = 4;
    /** Message that the client wants to update JobScheduler of the data transfer progress. */
    private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
    /** Message that the client wants to update JobScheduler of the estimated transfer size. */
    private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;

    private final IJobService mBinder;

@@ -67,6 +89,32 @@ public abstract class JobServiceEngine {
            mService = new WeakReference<>(service);
        }

        @Override
        public void getTransferredDownloadBytes(@NonNull JobParameters jobParams,
                @Nullable JobWorkItem jobWorkItem) throws RemoteException {
            JobServiceEngine service = mService.get();
            if (service != null) {
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = jobParams;
                args.arg2 = jobWorkItem;
                service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_DOWNLOAD_BYTES, args)
                        .sendToTarget();
            }
        }

        @Override
        public void getTransferredUploadBytes(@NonNull JobParameters jobParams,
                @Nullable JobWorkItem jobWorkItem) throws RemoteException {
            JobServiceEngine service = mService.get();
            if (service != null) {
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = jobParams;
                args.arg2 = jobWorkItem;
                service.mHandler.obtainMessage(MSG_GET_TRANSFERRED_UPLOAD_BYTES, args)
                        .sendToTarget();
            }
        }

        @Override
        public void startJob(JobParameters jobParams) throws RemoteException {
            JobServiceEngine service = mService.get();
@@ -98,9 +146,9 @@ public abstract class JobServiceEngine {

        @Override
        public void handleMessage(Message msg) {
            final JobParameters params = (JobParameters) msg.obj;
            switch (msg.what) {
                case MSG_EXECUTE_JOB:
                case MSG_EXECUTE_JOB: {
                    final JobParameters params = (JobParameters) msg.obj;
                    try {
                        boolean workOngoing = JobServiceEngine.this.onStartJob(params);
                        ackStartMessage(params, workOngoing);
@@ -109,7 +157,9 @@ public abstract class JobServiceEngine {
                        throw new RuntimeException(e);
                    }
                    break;
                case MSG_STOP_JOB:
                }
                case MSG_STOP_JOB: {
                    final JobParameters params = (JobParameters) msg.obj;
                    try {
                        boolean ret = JobServiceEngine.this.onStopJob(params);
                        ackStopMessage(params, ret);
@@ -118,7 +168,9 @@ public abstract class JobServiceEngine {
                        throw new RuntimeException(e);
                    }
                    break;
                case MSG_JOB_FINISHED:
                }
                case MSG_JOB_FINISHED: {
                    final JobParameters params = (JobParameters) msg.obj;
                    final boolean needsReschedule = (msg.arg2 == 1);
                    IJobCallback callback = params.getCallback();
                    if (callback != null) {
@@ -132,12 +184,110 @@ public abstract class JobServiceEngine {
                        Log.e(TAG, "finishJob() called for a nonexistent job id.");
                    }
                    break;
                }
                case MSG_GET_TRANSFERRED_DOWNLOAD_BYTES: {
                    final SomeArgs args = (SomeArgs) msg.obj;
                    final JobParameters params = (JobParameters) args.arg1;
                    final JobWorkItem item = (JobWorkItem) args.arg2;
                    try {
                        long ret = JobServiceEngine.this.getTransferredDownloadBytes(params, item);
                        ackGetTransferredDownloadBytesMessage(params, item, ret);
                    } catch (Exception e) {
                        Log.e(TAG, "Application unable to handle getTransferredDownloadBytes.", e);
                        throw new RuntimeException(e);
                    }
                    args.recycle();
                    break;
                }
                case MSG_GET_TRANSFERRED_UPLOAD_BYTES: {
                    final SomeArgs args = (SomeArgs) msg.obj;
                    final JobParameters params = (JobParameters) args.arg1;
                    final JobWorkItem item = (JobWorkItem) args.arg2;
                    try {
                        long ret = JobServiceEngine.this.getTransferredUploadBytes(params, item);
                        ackGetTransferredUploadBytesMessage(params, item, ret);
                    } catch (Exception e) {
                        Log.e(TAG, "Application unable to handle getTransferredUploadBytes.", e);
                        throw new RuntimeException(e);
                    }
                    args.recycle();
                    break;
                }
                case MSG_UPDATE_TRANSFERRED_NETWORK_BYTES: {
                    final SomeArgs args = (SomeArgs) msg.obj;
                    final JobParameters params = (JobParameters) args.arg1;
                    IJobCallback callback = params.getCallback();
                    if (callback != null) {
                        try {
                            callback.updateTransferredNetworkBytes(params.getJobId(),
                                    (JobWorkItem) args.arg2, args.argl1, args.argl2);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Error updating data transfer progress to system:"
                                    + " binder has gone away.");
                        }
                    } else {
                        Log.e(TAG, "updateDataTransferProgress() called for a nonexistent job id.");
                    }
                    args.recycle();
                    break;
                }
                case MSG_UPDATE_ESTIMATED_NETWORK_BYTES: {
                    final SomeArgs args = (SomeArgs) msg.obj;
                    final JobParameters params = (JobParameters) args.arg1;
                    IJobCallback callback = params.getCallback();
                    if (callback != null) {
                        try {
                            callback.updateEstimatedNetworkBytes(params.getJobId(),
                                    (JobWorkItem) args.arg2, args.argl1, args.argl2);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Error updating estimated transfer size to system:"
                                    + " binder has gone away.");
                        }
                    } else {
                        Log.e(TAG,
                                "updateEstimatedNetworkBytes() called for a nonexistent job id.");
                    }
                    args.recycle();
                    break;
                }
                default:
                    Log.e(TAG, "Unrecognised message received.");
                    break;
            }
        }

        private void ackGetTransferredDownloadBytesMessage(@NonNull JobParameters params,
                @Nullable JobWorkItem item, long progress) {
            final IJobCallback callback = params.getCallback();
            final int jobId = params.getJobId();
            final int workId = item == null ? -1 : item.getWorkId();
            if (callback != null) {
                try {
                    callback.acknowledgeGetTransferredDownloadBytesMessage(jobId, workId, progress);
                } catch (RemoteException e) {
                    Log.e(TAG, "System unreachable for returning progress.");
                }
            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Attempting to ack a job that has already been processed.");
            }
        }

        private void ackGetTransferredUploadBytesMessage(@NonNull JobParameters params,
                @Nullable JobWorkItem item, long progress) {
            final IJobCallback callback = params.getCallback();
            final int jobId = params.getJobId();
            final int workId = item == null ? -1 : item.getWorkId();
            if (callback != null) {
                try {
                    callback.acknowledgeGetTransferredUploadBytesMessage(jobId, workId, progress);
                } catch (RemoteException e) {
                    Log.e(TAG, "System unreachable for returning progress.");
                }
            } else if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Attempting to ack a job that has already been processed.");
            }
        }

        private void ackStartMessage(JobParameters params, boolean workOngoing) {
            final IJobCallback callback = params.getCallback();
            final int jobId = params.getJobId();
@@ -213,4 +363,69 @@ public abstract class JobServiceEngine {
        m.arg2 = needsReschedule ? 1 : 0;
        m.sendToTarget();
    }

    /**
     * Engine's request to get how much data has been downloaded.
     *
     * @see JobService#getTransferredDownloadBytes()
     */
    @BytesLong
    public long getTransferredDownloadBytes(@NonNull JobParameters params,
            @Nullable JobWorkItem item) {
        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
            throw new RuntimeException("Not implemented. Must override in a subclass.");
        }
        return 0;
    }

    /**
     * Engine's request to get how much data has been uploaded.
     *
     * @see JobService#getTransferredUploadBytes()
     */
    @BytesLong
    public long getTransferredUploadBytes(@NonNull JobParameters params,
            @Nullable JobWorkItem item) {
        if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
            throw new RuntimeException("Not implemented. Must override in a subclass.");
        }
        return 0;
    }

    /**
     * Call in to engine to report data transfer progress.
     *
     * @see JobService#updateTransferredNetworkBytes(JobParameters, long, long)
     */
    public void updateTransferredNetworkBytes(@NonNull JobParameters params,
            @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
        if (params == null) {
            throw new NullPointerException("params");
        }
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = params;
        args.arg2 = item;
        args.argl1 = downloadBytes;
        args.argl2 = uploadBytes;
        mHandler.obtainMessage(MSG_UPDATE_TRANSFERRED_NETWORK_BYTES, args).sendToTarget();
    }

    /**
     * Call in to engine to report data transfer progress.
     *
     * @see JobService#updateEstimatedNetworkBytes(JobParameters, JobWorkItem, long, long)
     */
    public void updateEstimatedNetworkBytes(@NonNull JobParameters params,
            @NonNull JobWorkItem item,
            @BytesLong long downloadBytes, @BytesLong long uploadBytes) {
        if (params == null) {
            throw new NullPointerException("params");
        }
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = params;
        args.arg2 = item;
        args.argl1 = downloadBytes;
        args.argl2 = uploadBytes;
        mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
    }
}
 No newline at end of file
Loading