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

Commit 928dde80 authored by Evan Chen's avatar Evan Chen
Browse files

Introduce APIs to support CDM data sync

- Adds a new `requestAction()` API. This method binds the target
  companion app's service to ensure it can perform background work, and
  then dispatches the action request.
- Implements `setOnDevicePresenceEventListener` and a corresponding
  broadcast mechanism. This allows other system services to listen for
  detailed `DevicePresenceEvent`s from companion apps.
- Implements 'setOnActionResultListener' to allow system to get
  callback when apps notify ActionResult to CDM.
- Add a new `notifyActionResult`to allow apps to notify result to
  CDM
- Deprecated notifydeviceAppeared/notifyDeviceDisappeared system APIs,
  introudced a new system API called notifyDevicePresence to replace
  the deprecated one.
- Added ActionRequest
- Added ActionResult
- Added new events in DevicePresenceEvent.

This provides the foundation for the data sync feature, allowing the
system to safely manage the companion app's lifecycle while it performs
requested background tasks.

Test: CTS
Bug: 422195296
Flag: android.companion.enable_data_sync
Change-Id: I60390c65e6e5704e4807d4aa016da71df0c3e273
parent f1ebbcd3
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -10249,6 +10249,29 @@ package android.appwidget {
package android.companion {
  @FlaggedApi("android.companion.enable_data_sync") public final class ActionRequest implements android.os.Parcelable {
    method public int describeContents();
    method public int getAction();
    method public int getOperation();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.companion.ActionRequest> CREATOR;
    field public static final int OP_ACTIVATE = 0; // 0x0
    field public static final int OP_DEACTIVATE = 1; // 0x1
    field public static final int REQUEST_NEARBY_ADVERTISING = 1; // 0x1
    field public static final int REQUEST_NEARBY_SCANNING = 0; // 0x0
    field public static final int REQUEST_TRANSPORT = 2; // 0x2
  }
  @FlaggedApi("android.companion.enable_data_sync") public final class ActionResult implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public android.companion.ActionRequest getActionRequest();
    method public int getResultCode();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.companion.ActionResult> CREATOR;
    field public static final int RESULT_FAILED = 1; // 0x1
    field public static final int RESULT_SUCCESS = 0; // 0x0
  }
  public final class AssociatedDevice implements android.os.Parcelable {
    method public int describeContents();
    method @Nullable public android.bluetooth.le.ScanResult getBleDevice();
@@ -10384,6 +10407,7 @@ package android.companion {
    ctor public CompanionDeviceService();
    method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
    method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
    method @FlaggedApi("android.companion.enable_data_sync") @MainThread public void onActionRequested(@NonNull android.companion.AssociationInfo, @NonNull android.companion.ActionRequest);
    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
    method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
    method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
@@ -10432,6 +10456,8 @@ package android.companion {
    field public static final int EVENT_BT_DISCONNECTED = 3; // 0x3
    field public static final int EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
    field public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
    field @FlaggedApi("android.companion.enable_data_sync") public static final int EVENT_SELF_MANAGED_NEARBY = 7; // 0x7
    field @FlaggedApi("android.companion.enable_data_sync") public static final int EVENT_SELF_MANAGED_NOT_NEARBY = 8; // 0x8
    field public static final int NO_ASSOCIATION = -1; // 0xffffffff
  }
+17 −2
Original line number Diff line number Diff line
@@ -3470,6 +3470,11 @@ package android.apphibernation {
package android.companion {
  public static final class ActionResult.Builder {
    ctor public ActionResult.Builder(@NonNull android.companion.ActionRequest, int);
    method @NonNull public android.companion.ActionResult build();
  }
  public final class AssociationInfo implements android.os.Parcelable {
    method @NonNull public String getPackageName();
  }
@@ -3481,8 +3486,10 @@ package android.companion {
    method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations();
    method @FlaggedApi("android.companion.association_verification") @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_COMPANION_INFO) public android.companion.AssociationInfo getAssociationByDeviceId(@NonNull android.companion.DeviceId);
    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
    method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int);
    method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int);
    method @FlaggedApi("android.companion.enable_data_sync") @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyActionResult(int, @NonNull android.companion.ActionResult);
    method @Deprecated @FlaggedApi("android.companion.enable_data_sync") @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int);
    method @Deprecated @FlaggedApi("android.companion.enable_data_sync") @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int);
    method @FlaggedApi("android.companion.enable_data_sync") @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDevicePresence(int, @NonNull android.companion.DevicePresenceEvent);
    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
  }
@@ -3490,6 +3497,14 @@ package android.companion {
    method public void onAssociationsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
  }
  @FlaggedApi("android.companion.enable_data_sync") public static final class DevicePresenceEvent.Builder {
    ctor public DevicePresenceEvent.Builder();
    method @NonNull public android.companion.DevicePresenceEvent build();
    method @NonNull public android.companion.DevicePresenceEvent.Builder setAssociationId(int);
    method @NonNull public android.companion.DevicePresenceEvent.Builder setEvent(int);
    method @NonNull public android.companion.DevicePresenceEvent.Builder setUuid(@NonNull android.os.ParcelUuid);
  }
  public final class ObservingDevicePresenceRequest implements android.os.Parcelable {
    method @FlaggedApi("android.companion.association_verification") @Nullable public android.companion.DeviceId getDeviceId();
  }
+19 −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.companion;

parcelable ActionRequest;
 No newline at end of file
+201 −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.companion;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * Represents a request for a companion app to perform a specific action.
 *
 * @see CompanionDeviceService#onActionRequested(AssociationInfo, ActionRequest)
 */
@FlaggedApi(Flags.FLAG_ENABLE_DATA_SYNC)
public final class ActionRequest implements Parcelable {
    /** @hide */
    @IntDef(prefix = {"OP_"}, value = {
            OP_ACTIVATE,
            OP_DEACTIVATE
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Operation {}

    /**
     * An operation to request that the action be activated.
     */
    public static final int OP_ACTIVATE = 0;

    /**
     * An operation to request that the action be deactivated.
     */
    public static final int OP_DEACTIVATE = 1;

    /** @hide */
    @IntDef(prefix = {"REQUEST_"}, value = {
            REQUEST_NEARBY_SCANNING,
            REQUEST_NEARBY_ADVERTISING,
            REQUEST_TRANSPORT,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RequestAction {}

    /**
     * An action that signals a request for the companion app to change its nearby scanning state.
     * <p>
     * When the system sends this request, the app receives a callback to
     * {@link CompanionDeviceService#onActionRequested(AssociationInfo, ActionRequest)}
     * with this constant.
     */
    public static final int REQUEST_NEARBY_SCANNING = 0;

    /**
     * An action that signals a request for the companion app to change its nearby
     * advertising state.
     * <p>
     * When the system sends this request, the app receives a callback to
     * {@link CompanionDeviceService#onActionRequested(AssociationInfo, ActionRequest)}
     * with this constant.
     */
    public static final int REQUEST_NEARBY_ADVERTISING = 1;

    /**
     * An action that signals a request for the companion app to attach or detach its system data
     * transport.
     * <p>
     * When the system sends this request, the app receives a callback to
     * {@link CompanionDeviceService#onActionRequested(AssociationInfo, ActionRequest)}
     * with this constant.
     */
    public static final int REQUEST_TRANSPORT = 2;

    private final @RequestAction int mAction;
    private final @Operation int mOperation;

    private ActionRequest(@RequestAction int action, @Operation int operation) {
        this.mAction = action;
        this.mOperation = operation;
    }

    /**
     * @return The action being requested, such as
     *         {@link #REQUEST_NEARBY_SCANNING}.
     */
    public @RequestAction int getAction() {
        return mAction;
    }

    /**
     * @return The operation to perform, either {@link #OP_ACTIVATE} or {@link #OP_DEACTIVATE}.
     */
    public @Operation int getOperation() {
        return mOperation;
    }

    @Override
    public String toString() {
        return "ActionRequest{"
                + "mAction=" + mAction
                + ", mOperation="
                + mOperation + '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ActionRequest that = (ActionRequest) o;
        return mAction == that.mAction && mOperation == that.mOperation;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mAction, mOperation);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(mAction);
        dest.writeInt(mOperation);
    }

    public static final @NonNull Creator<ActionRequest> CREATOR = new Creator<>() {
        @Override
        public ActionRequest createFromParcel(Parcel in) {
            return new ActionRequest(in);
        }

        @Override
        public ActionRequest[] newArray(int size) {
            return new ActionRequest[size];
        }
    };

    private ActionRequest(Parcel in) {
        mAction = in.readInt();
        mOperation = in.readInt();
    }

    /**
     * @param action The action to convert to a string.
     * @hide
     */
    public static String actionToString(int action) {
        return switch (action) {
            case ActionRequest.REQUEST_NEARBY_SCANNING -> "REQUEST_NEARBY_SCANNING";
            case REQUEST_NEARBY_ADVERTISING -> "REQUEST_NEARBY_ADVERTISING";
            case ActionRequest.REQUEST_TRANSPORT -> "REQUEST_TRANSPORT";
            default -> "UNKNOWN_ACTION: " + action;
        };
    }

    /**
     * A builder for creating {@link ActionRequest} instances for system only.
     * @hide
     */
    public static final class Builder {
        private @RequestAction int mAction;
        private @Operation int mOperation;

        /**
         * @param action The action to request.
         * @param operation The operation to perform, either {@link #OP_ACTIVATE} or
         *                  {@link #OP_DEACTIVATE}.
         */
        public Builder(@RequestAction int action, @Operation int operation) {
            this.mAction = action;
            this.mOperation = operation;
        }
        /**
         * Builds the {@link ActionRequest} object.
         */
        @NonNull
        public ActionRequest build() {
            return new ActionRequest(mAction, mOperation);
        }
    }
}
+19 −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.companion;

 parcelable ActionResult;
 No newline at end of file
Loading