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

Commit 8b0ef791 authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov Committed by Android (Google) Code Review
Browse files

Merge "Add simulate-device-(dis)appeared CDM shell commands" into tm-dev

parents 845fb509 0197bcfd
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -732,9 +732,12 @@ public class CompanionDeviceManagerService extends SystemService {
                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                throws RemoteException {
            enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
            new CompanionDeviceShellCommand(
                    CompanionDeviceManagerService.this, mAssociationStore)
                    .exec(this, in, out, err, args, callback, resultReceiver);

            final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
                    CompanionDeviceManagerService.this,
                    mAssociationStore,
                    mDevicePresenceMonitor);
            cmd.exec(this, in, out, err, args, callback, resultReceiver);
        }

        @Override
+46 −10
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.os.ShellCommand;
import android.util.Log;
import android.util.Slog;

import com.android.server.companion.presence.CompanionDevicePresenceMonitor;

import java.io.PrintWriter;
import java.util.List;

@@ -29,20 +31,24 @@ class CompanionDeviceShellCommand extends ShellCommand {

    private final CompanionDeviceManagerService mService;
    private final AssociationStore mAssociationStore;
    private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;

    CompanionDeviceShellCommand(CompanionDeviceManagerService service,
            AssociationStore associationStore) {
            AssociationStore associationStore,
            CompanionDevicePresenceMonitor devicePresenceMonitor) {
        mService = service;
        mAssociationStore = associationStore;
        mDevicePresenceMonitor = devicePresenceMonitor;
    }

    @Override
    public int onCommand(String cmd) {
        final PrintWriter out = getOutPrintWriter();
        final int associationId;
        try {
            switch (cmd) {
                case "list": {
                    final int userId = getNextArgInt();
                    final int userId = getNextIntArgRequired();
                    final List<AssociationInfo> associationsForUser =
                            mAssociationStore.getAssociationsForUser(userId);
                    for (AssociationInfo association : associationsForUser) {
@@ -55,7 +61,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
                break;

                case "associate": {
                    int userId = getNextArgInt();
                    int userId = getNextIntArgRequired();
                    String packageName = getNextArgRequired();
                    String address = getNextArgRequired();
                    mService.legacyCreateAssociation(userId, address, packageName, null);
@@ -63,7 +69,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
                break;

                case "disassociate": {
                    final int userId = getNextArgInt();
                    final int userId = getNextIntArgRequired();
                    final String packageName = getNextArgRequired();
                    final String address = getNextArgRequired();
                    final AssociationInfo association =
@@ -80,6 +86,16 @@ class CompanionDeviceShellCommand extends ShellCommand {
                }
                break;

                case "simulate-device-appeared":
                    associationId = getNextIntArgRequired();
                    mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
                    break;

                case "simulate-device-disappeared":
                    associationId = getNextIntArgRequired();
                    mDevicePresenceMonitor.simulateDeviceDisappeared(associationId);
                    break;

                default:
                    return handleDefaultCommands(cmd);
            }
@@ -91,10 +107,6 @@ class CompanionDeviceShellCommand extends ShellCommand {
        }
    }

    private int getNextArgInt() {
        return Integer.parseInt(getNextArgRequired());
    }

    @Override
    public void onHelp() {
        PrintWriter pw = getOutPrintWriter();
@@ -108,7 +120,31 @@ class CompanionDeviceShellCommand extends ShellCommand {
        pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
        pw.println("      Remove an existing Association.");
        pw.println("  clear-association-memory-cache");
        pw.println("      Clear the in-memory association cache and reload all association "
                + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
        pw.println("      Clear the in-memory association cache and reload all association ");
        pw.println("      information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");

        pw.println("  simulate-device-appeared ASSOCIATION_ID");
        pw.println("      Make CDM act as if the given companion device has appeared.");
        pw.println("      I.e. bind the associated companion application's");
        pw.println("      CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
        pw.println("      The CDM will consider the devices as present for 60 seconds and then");
        pw.println("      will act as if device disappeared, unless 'simulate-device-disappeared'");
        pw.println("      or 'simulate-device-appeared' is called again before 60 seconds run out"
                + ".");
        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");

        pw.println("  simulate-device-disappeared ASSOCIATION_ID");
        pw.println("      Make CDM act as if the given companion device has disappeared.");
        pw.println("      I.e. unbind the associated companion application's");
        pw.println("      CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
        pw.println("      NOTE: This will only have effect if 'simulate-device-appeared' was");
        pw.println("      invoked for the same device (same ASSOCIATION_ID) no longer than");
        pw.println("      60 seconds ago.");
        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
    }

    private int getNextIntArgRequired() {
        return Integer.parseInt(getNextArgRequired());
    }
}
+87 −1
Original line number Diff line number Diff line
@@ -16,11 +16,19 @@

package com.android.server.companion.presence;

import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;

import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.bluetooth.BluetoothAdapter;
import android.companion.AssociationInfo;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.android.server.companion.AssociationStore;
@@ -72,6 +80,11 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
    private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
    private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();

    // Tracking "simulated" presence. Used for debugging and testing only.
    private final @NonNull Set<Integer> mSimulated = new HashSet<>();
    private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
            new SimulatedDevicePresenceSchedulerHelper();

    public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
            @NonNull Callback callback) {
        mAssociationStore = associationStore;
@@ -106,7 +119,8 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
    public boolean isDevicePresent(int associationId) {
        return mReportedSelfManagedDevices.contains(associationId)
                || mConnectedBtDevices.contains(associationId)
                || mNearbyBleDevices.contains(associationId);
                || mNearbyBleDevices.contains(associationId)
                || mSimulated.contains(associationId);
    }

    /**
@@ -155,6 +169,45 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
        onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
    }

    /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
    @TestApi
    public void simulateDeviceAppeared(int associationId) {
        // IMPORTANT: this API should only be invoked via the
        // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
        // make this call are SHELL and ROOT.
        // No other caller (including SYSTEM!) should be allowed.
        enforceCallerShellOrRoot();
        // Make sure the association exists.
        enforceAssociationExists(associationId);

        onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated");

        mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
    }

    /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
    @TestApi
    public void simulateDeviceDisappeared(int associationId) {
        // IMPORTANT: this API should only be invoked via the
        // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
        // make this call are SHELL and ROOT.
        // No other caller (including SYSTEM!) should be allowed.
        enforceCallerShellOrRoot();
        // Make sure the association exists.
        enforceAssociationExists(associationId);

        mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);

        onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
    }

    private void enforceAssociationExists(int associationId) {
        if (mAssociationStore.getAssociationById(associationId) == null) {
            throw new IllegalArgumentException(
                    "Association with id " + associationId + " does not exist.");
        }
    }

    private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
            int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
        if (DEBUG) {
@@ -212,6 +265,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
        mConnectedBtDevices.remove(associationId);
        mNearbyBleDevices.remove(associationId);
        mReportedSelfManagedDevices.remove(associationId);
        mSimulated.remove(associationId);
    }

    /**
@@ -232,4 +286,36 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
        // CompanionDeviceManagerService will know that the association is removed, and will do
        // what's needed.
    }

    private static void enforceCallerShellOrRoot() {
        final int callingUid = Binder.getCallingUid();
        if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;

        throw new SecurityException("Caller is neither Shell nor Root");
    }

    private class SimulatedDevicePresenceSchedulerHelper extends Handler {
        SimulatedDevicePresenceSchedulerHelper() {
            super(Looper.getMainLooper());
        }

        void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
            // First, unschedule if it was scheduled previously.
            if (hasMessages(/* what */ associationId)) {
                removeMessages(/* what */ associationId);
            }

            sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
        }

        void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
            removeMessages(/* what */ associationId);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            final int associationId = msg.what;
            onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
        }
    }
}