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

Commit 5ba2922d authored by Joe Antonetti's avatar Joe Antonetti
Browse files

Pull HandoffActivityData from Running Activities

This change adds a new internal API on ActivityTaskManagerService, allowing services to request HandoffActivityData for a task. For now, this API will only pull HandoffActivityData from the topmost Activity while it is running. Future CLs in this stack will enhance this functionality.

Test: Added unit tests for ActivityTaskManagerService. Due to the structure of ActivityThread, unit testing isn't really possible. The full flow will be tested in CTS tests once wired up fully.
Bug: 400970610
Flag: android.companion.enable_task_continuity
Change-Id: I555b7d55b0f1d1048b52bbfe06a7793b2a13948b
parent 5b3df579
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.HandoffActivityData;
import android.app.HandoffActivityDataRequestInfo;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
@@ -1129,6 +1131,11 @@ public final class ActivityThread extends ClientTransactionHandler
        int flags;
    }

    static final class RequestHandoffActivityData {
        List<IBinder> activityTokens;
        IBinder requestToken;
    }

    // A list of receivers and an index into the receiver to be processed next.
    static final class ReceiverList {
        List<ReceiverInfo> receivers;
@@ -2116,6 +2123,21 @@ public final class ActivityThread extends ClientTransactionHandler
            sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
        }

        /**
         * Requests the data needed to start a handoff activity.
         *
         * @param requestToken The token of the request, used by ActivityTaskManager to identify
         *                     the request.
         * @param activityTokens The tokens of the activities to hand off.
         */
        @Override
        public void requestHandoffActivityData(IBinder requestToken, List<IBinder> activityTokens) {
            RequestHandoffActivityData cmd = new RequestHandoffActivityData();
            cmd.activityTokens = activityTokens;
            cmd.requestToken = requestToken;
            sendMessage(H.REQUEST_HANDOFF_ACTIVITY_DATA, cmd);
        }

        @Override
        public void setCoreSettings(Bundle coreSettings) {
            sendMessage(H.SET_CORE_SETTINGS, coreSettings);
@@ -2513,6 +2535,7 @@ public final class ActivityThread extends ClientTransactionHandler
        public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;

        public static final int TIMEOUT_SERVICE_FOR_TYPE = 172;
        public static final int REQUEST_HANDOFF_ACTIVITY_DATA = 173;

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
@@ -2568,6 +2591,7 @@ public final class ActivityThread extends ClientTransactionHandler
                    case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
                    case PING: return "PING";
                    case TIMEOUT_SERVICE_FOR_TYPE: return "TIMEOUT_SERVICE_FOR_TYPE";
                    case REQUEST_HANDOFF_ACTIVITY_DATA: return "REQUEST_HANDOFF_ACTIVITY_DATA";
                }
            }
            return Integer.toString(code);
@@ -2889,6 +2913,12 @@ public final class ActivityThread extends ClientTransactionHandler
                case FINISH_INSTRUMENTATION_WITHOUT_RESTART:
                    handleFinishInstrumentationWithoutRestart();
                    break;
                case REQUEST_HANDOFF_ACTIVITY_DATA:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                            "handleHandoffActivityDataRequest");
                    handleHandoffActivityDataRequest((RequestHandoffActivityData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
            }
            long messageElapsedTimeMs = SystemClock.uptimeMillis() - messageStartUptimeMs;
            Object obj = msg.obj;
@@ -4606,6 +4636,32 @@ public final class ActivityThread extends ClientTransactionHandler
        deliverNewIntents(r, intents);
    }

    private void handleHandoffActivityDataRequest(RequestHandoffActivityData cmd) {
        final HandoffActivityDataRequestInfo requestInfo = new HandoffActivityDataRequestInfo(true);
        final List<HandoffActivityData> handoffActivityData = new ArrayList<>();
        for (IBinder activityToken : cmd.activityTokens) {
            final ActivityClientRecord r = mActivities.get(activityToken);
            if (r != null) {
                final HandoffActivityData dataForActivity =
                        r.activity.onHandoffActivityDataRequested(requestInfo);
                if (dataForActivity == null) {
                    Log.w(TAG, "HandoffActivityData is null for " + r.activity.getComponentName());
                }
                handoffActivityData.add(dataForActivity);
            } else {
                Log.w(TAG, "Attempted to fetch HandoffActivityData for non-existent activity.");
                handoffActivityData.add(null);
            }
        }

        IActivityTaskManager mgr = ActivityTaskManager.getService();
        try {
            mgr.reportHandoffActivityData(cmd.requestToken, handoffActivityData);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
        // Filling for autofill has a few differences:
        // - it does not need an AssistContent
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.app;

import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Defines failure codes for handoff task data requests.
 * @hide
 */
public final class HandoffFailureCode {

    private HandoffFailureCode() {}

    /** @hide */
    @IntDef(prefix = { "HANDOFF_FAILURE_" }, value = {
            HANDOFF_FAILURE_TIMEOUT,
            HANDOFF_FAILURE_UNSUPPORTED_DEVICE,
            HANDOFF_FAILURE_UNKNOWN_TASK,
            HANDOFF_FAILURE_INTERNAL_ERROR,
            HANDOFF_FAILURE_EMPTY_TASK,
            HANDOFF_FAILURE_UNSUPPORTED_TASK
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface FailureCode {}

    /** The handoff task data request timed out. */
    public static final int HANDOFF_FAILURE_TIMEOUT = 1;
    /** The device does not support handoff. */
    public static final int HANDOFF_FAILURE_UNSUPPORTED_DEVICE = 2;
    /** The handoff task data request was a task that no longer exists. */
    public static final int HANDOFF_FAILURE_UNKNOWN_TASK = 3;
    /** An internal error occurred while handling the handoff task data request. For example,
     * the app process died during a handoff request. */
    public static final int HANDOFF_FAILURE_INTERNAL_ERROR = 4;
    /** The handoff task data request was for an empty task. */
    public static final int HANDOFF_FAILURE_EMPTY_TASK = 5;
    /** The handoff task data request was for a task whose top activity does not support handoff. */
    public static final int HANDOFF_FAILURE_UNSUPPORTED_TASK = 6;
}
 No newline at end of file
+13 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.ActivityTaskManager;
import android.app.ApplicationErrorReport;
import android.app.ContentProviderHolder;
import android.app.GrantedUriPermission;
import android.app.HandoffActivityData;
import android.app.IApplicationThread;
import android.app.IActivityClientController;
import android.app.IActivityController;
@@ -426,4 +427,16 @@ interface IActivityTaskManager {
    * @hide
    */
    void moveRootTaskToDisplayOnTopOrBottom(int taskId, int displayId, boolean onTop);

    /**
     * Reports HandoffActivityData for a given set of activities back to
     * ActivityTaskManager.
     *
     * @param requestToken A request token used by ActivityTaskManager to
     * identify the request.
     * @param data A list of HandoffActivityData objects containing the data for the requested
     * activities in the same order as the request.
     * @hide
     */
    void reportHandoffActivityData(in IBinder requestToken, in List<HandoffActivityData> data);
}
+2 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.app;

import android.app.ActivityOptions.SceneTransitionInfo;
import android.app.ContentProviderHolder;
import android.app.HandoffActivityData;
import android.app.IInstrumentationWatcher;
import android.app.IUiAutomationConnection;
import android.app.ProfilerInfo;
@@ -188,4 +189,5 @@ oneway interface IApplicationThread {
    void schedulePing(in RemoteCallback pong);
    void getExecutableMethodFileOffsets(in MethodDescriptor methodDescriptor,
            in IOffsetCallback resultCallback);
    void requestHandoffActivityData(in IBinder requestToken, in List<IBinder> activityTokens);
}
+45 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.app;

import android.app.HandoffActivityData;

/** @hide */
oneway interface IHandoffTaskDataReceiver {

    /*
    * Called when the handoff task data request has succeeded.
    *
    * @param taskId The id of the task that the request was made for.
    * @param handoffActivityData A list of {@link HandoffActivityData} for this
    * task, retaining the order of the activities in the task with the topmost
    * activity at index 0. If the topmost activity of the task did not return
    * any HandoffActivityData, this list will be empty.
    */
    void onHandoffTaskDataRequestSucceeded(
        in int taskId,
        in List<HandoffActivityData> handoffActivityData);

    /*
    * Called when the handoff task data request has failed.
    *
    * @param taskId The id of the task that the request was made for.
    * @param resultCode The result code of the failure, represented as an {@link HandoffFailureCode}
    */
    void onHandoffTaskDataRequestFailed(in int taskId, in int resultCode);

}
Loading