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

Commit 3b9de3e2 authored by Vladimir Komsiyski's avatar Vladimir Komsiyski
Browse files

API for listening for automated apps.

Requires the caller to hold ROLE_HOME

Keeping the API close to LauncherApps.Callback and exposing it via
the extensions library.

Caveats:
 - Currently we only have UIDs in VDM, not package names. Multiple
   packages may share the same UID but this is very rare and hardly
   a short-term blocker

Flag: android.companion.virtualdevice.flags.computer_control_access
Bug: 440353845
Bug: 440355882
Test: atest & manual & presubmit
Change-Id: I469cf38b49eecb063687a5593889791b3948c0df
parent f759f9c9
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IAutomatedPackageListener;
import android.companion.virtual.computercontrol.IComputerControlSessionCallback;
import android.content.AttributionSource;

@@ -80,6 +81,16 @@ interface IVirtualDeviceManager {
     */
    void unregisterVirtualDeviceListener(in IVirtualDeviceListener listener);

    /**
     * Registers a listener to receive notifications for automated packages.
     */
    void registerAutomatedPackageListener(in IAutomatedPackageListener listener);

    /**
     * Unregisters a previously registered listener.
     */
    void unregisterAutomatedPackageListener(in IAutomatedPackageListener listener);

    /**
     * Returns the ID of the device which owns the display with the given ID.
     */
+91 −0
Original line number Diff line number Diff line
@@ -34,13 +34,16 @@ import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
import android.companion.virtual.camera.VirtualCamera;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtual.computercontrol.AutomatedPackageListener;
import android.companion.virtual.computercontrol.ComputerControlSession;
import android.companion.virtual.computercontrol.ComputerControlSessionParams;
import android.companion.virtual.computercontrol.IAutomatedPackageListener;
import android.companion.virtual.computercontrol.IComputerControlSessionCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualdevice.flags.Flags;
@@ -186,6 +189,10 @@ public final class VirtualDeviceManager {
    @GuardedBy("mVirtualDeviceListeners")
    private final List<VirtualDeviceListenerDelegate> mVirtualDeviceListeners = new ArrayList<>();

    @GuardedBy("mAutomatedPackageListeners")
    private final List<AutomatedPackageListenerDelegate> mAutomatedPackageListeners =
            new ArrayList<>();

    /** @hide */
    public VirtualDeviceManager(
            @Nullable IVirtualDeviceManager service, @NonNull Context context) {
@@ -365,6 +372,66 @@ public final class VirtualDeviceManager {
        }
    }

    /**
     * Registers a listener to receive notifications when the set of automated apps changes.
     *
     * @param executor The executor where the listener is executed on.
     * @param listener The listener to add.
     * @throws SecurityException if the caller does not hold the {@link RoleManager#ROLE_HOME} role.
     * @see #unregisterAutomatedPackageListener
     * @hide
     */
    public void registerAutomatedPackageListener(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull AutomatedPackageListener listener) {
        if (mService == null) {
            Log.w(TAG, "Failed to register listener; no virtual device manager service.");
            return;
        }
        final AutomatedPackageListenerDelegate delegate =
                new AutomatedPackageListenerDelegate(Objects.requireNonNull(executor),
                        Objects.requireNonNull(listener));
        synchronized (mAutomatedPackageListeners) {
            try {
                mService.registerAutomatedPackageListener(delegate);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            mAutomatedPackageListeners.add(delegate);
        }
    }

    /**
     * Unregisters a listener previously registered with {@link #registerAutomatedPackageListener}.
     *
     * @param listener The listener to unregister.
     * @throws SecurityException if the caller does not hold the {@link RoleManager#ROLE_HOME} role.
     * @see #registerAutomatedPackageListener
     * @hide
     */
    public void unregisterAutomatedPackageListener(@NonNull AutomatedPackageListener listener) {
        if (mService == null) {
            Log.w(TAG, "Failed to unregister listener; no virtual device manager service.");
            return;
        }
        Objects.requireNonNull(listener);
        synchronized (mAutomatedPackageListeners) {
            final Iterator<AutomatedPackageListenerDelegate> it =
                    mAutomatedPackageListeners.iterator();
            while (it.hasNext()) {
                final AutomatedPackageListenerDelegate delegate = it.next();
                if (delegate.mListener == listener) {
                    try {
                        mService.unregisterAutomatedPackageListener(delegate);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                    it.remove();
                }
            }
        }
    }

    /**
     * Returns the device policy for the given virtual device and policy type.
     *
@@ -1482,4 +1549,28 @@ public final class VirtualDeviceManager {
            }
        }
    }

    /**
     * A wrapper for {@link AutomatedPackageListener} that executes callbacks on the given executor.
     */
    private static class AutomatedPackageListenerDelegate extends IAutomatedPackageListener.Stub {
        private final AutomatedPackageListener mListener;
        private final Executor mExecutor;

        private AutomatedPackageListenerDelegate(
                Executor executor, AutomatedPackageListener listener) {
            mExecutor = executor;
            mListener = listener;
        }

        @Override
        public void onAutomatedPackagesChanged(
                @NonNull String automatingPackage,
                @NonNull List<String> automatedPackages,
                @NonNull UserHandle user) {
            Binder.withCleanCallingIdentity(() ->
                    mExecutor.execute(() -> mListener.onAutomatedPackagesChanged(
                            automatingPackage, automatedPackages, user)));
        }
    }
}
+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.companion.virtual.computercontrol;

import android.annotation.NonNull;
import android.os.UserHandle;

import java.util.List;

/**
 * Listener to get notified when the packages being automated within a
 * {@link ComputerControlSession} changes.
 *
 * @hide
 */
// TODO(b/442624418): Move to LauncherApps.Callback
public interface AutomatedPackageListener {
    /**
     * Called when the set of automated packages for a specific user and session owner has changed.
     *
     * @param automatingPackage The name of the package that owns the {@link ComputerControlSession}
     * @param automatedPackages The names of the packages that are being automated. May be empty,
     *   indicating that automation has stopped for all previously automated packages for this
     *   session owner and user.
     * @param user The UserHandle of the profile of the automated packages.
     */
    void onAutomatedPackagesChanged(
            @NonNull String automatingPackage,
            @NonNull List<String> automatedPackages,
            @NonNull UserHandle user);
}
+33 −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.virtual.computercontrol;

import android.os.UserHandle;

/**
 * Listener to get notified when automated packages change.
 *
 * @hide
 */
oneway interface IAutomatedPackageListener {

    /** Called when the set of automated packages has changed. */
    void onAutomatedPackagesChanged(
            in String automatingPackage,
            in List<String> automatedPackages,
            in UserHandle user);
}
 No newline at end of file
+43 −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 com.android.extensions.computercontrol;

import android.os.UserHandle;

import androidx.annotation.NonNull;

import java.util.List;

/**
 * Listener to get notified when the packages being automated within a
 * {@link ComputerControlSession} changes.
 */
public interface AutomatedPackageListener {
    /**
     * Called when the set of automated packages for a specific user and session owner has changed.
     *
     * @param automatingPackage The name of the package that owns the {@link ComputerControlSession}
     * @param automatedPackages The names of the packages that are being automated. May be empty,
     *   indicating that automation has stopped for all previously automated packages for this
     *   session owner and user.
     * @param user The UserHandle of the profile of the automated packages.
     */
    void onAutomatedPackagesChanged(
            @NonNull String automatingPackage,
            @NonNull List<String> automatedPackages,
            @NonNull UserHandle user);
}
Loading