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

Commit 1d9cf534 authored by Oleg Nitz's avatar Oleg Nitz
Browse files

Implement client side framework for the new Serial API.

Design Doc: go/webserial-android

Bug: 369155426
Flag: android.hardware.serial.flags.enable_serial_api
Test: manual
API-Coverage-Bug: 412907404
Change-Id: I772196a693e54d0cf23768c9145bec63b4935d47
parent 7442cc9d
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -11370,6 +11370,7 @@ package android.content {
    field public static final String SEARCH_SERVICE = "search";
    field @FlaggedApi("android.os.security_state_service") public static final String SECURITY_STATE_SERVICE = "security_state";
    field public static final String SENSOR_SERVICE = "sensor";
    field @FlaggedApi("android.hardware.serial.flags.enable_serial_api") public static final String SERIAL_SERVICE = "serial";
    field public static final String SHORTCUT_SERVICE = "shortcut";
    field public static final String STATUS_BAR_SERVICE = "statusbar";
    field public static final String STORAGE_SERVICE = "storage";
@@ -21316,6 +21317,34 @@ package android.hardware.lights {
}
package android.hardware.serial {
  @FlaggedApi("android.hardware.serial.flags.enable_serial_api") public final class SerialManager {
    method @NonNull public java.util.List<android.hardware.serial.SerialPort> getSerialPorts();
    method public void registerSerialPortListener(@NonNull android.hardware.serial.SerialPortListener, @NonNull java.util.concurrent.Executor);
    method public void unregisterSerialPortListener(@NonNull android.hardware.serial.SerialPortListener);
  }
  @FlaggedApi("android.hardware.serial.flags.enable_serial_api") public final class SerialPort {
    method @NonNull public String getName();
    method public int getProductId();
    method public int getVendorId();
    method public void requestOpen(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.hardware.serial.SerialPortResponse,java.lang.Exception>);
    field public static final int INVALID_ID = -1; // 0xffffffff
  }
  @FlaggedApi("android.hardware.serial.flags.enable_serial_api") public interface SerialPortListener {
    method public void onSerialPortConnected(@NonNull android.hardware.serial.SerialPort);
    method public void onSerialPortDisconnected(@NonNull android.hardware.serial.SerialPort);
  }
  @FlaggedApi("android.hardware.serial.flags.enable_serial_api") public final class SerialPortResponse {
    method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
    method @NonNull public android.hardware.serial.SerialPort getPort();
  }
}
package android.hardware.usb {
  public class UsbAccessory implements android.os.Parcelable {
+24 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.app;

import static android.app.appfunctions.flags.Flags.enableAppFunctionManager;
import static android.hardware.serial.flags.Flags.enableSerialApi;
import static android.provider.flags.Flags.newStoragePublicApi;
import static android.server.Flags.removeGameManagerServiceFromWear;
import static android.service.chooser.Flags.interactiveChooser;
@@ -107,10 +108,8 @@ import android.devicelock.DeviceLockFrameworkInitializer;
import android.graphics.fonts.FontManager;
import android.hardware.ConsumerIrManager;
import android.hardware.ISensorPrivacyManager;
import android.hardware.ISerialManager;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IAuthService;
@@ -805,13 +804,29 @@ public final class SystemServiceRegistry {
                        return new AdbManager(ctx, IAdbManager.Stub.asInterface(b));
                    }});

        registerService(Context.SERIAL_SERVICE, SerialManager.class,
                new CachedServiceFetcher<SerialManager>() {
        if (enableSerialApi()) {
            registerService(Context.SERIAL_SERVICE, android.hardware.serial.SerialManager.class,
                    new CachedServiceFetcher<android.hardware.serial.SerialManager>() {
                        @Override
            public SerialManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                        public android.hardware.serial.SerialManager createService(ContextImpl ctx)
                                throws ServiceNotFoundException {
                            IBinder b = ServiceManager.getServiceOrThrow(Context.SERIAL_SERVICE);
                return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
            }});
                            return new android.hardware.serial.SerialManager(ctx,
                                    android.hardware.serial.ISerialManager.Stub.asInterface(b));
                        }
                    });
        } else {
            registerService(Context.SERIAL_SERVICE, android.hardware.SerialManager.class,
                    new CachedServiceFetcher<android.hardware.SerialManager>() {
                        @Override
                        public android.hardware.SerialManager createService(ContextImpl ctx)
                                throws ServiceNotFoundException {
                            IBinder b = ServiceManager.getServiceOrThrow(Context.SERIAL_SERVICE);
                            return new android.hardware.SerialManager(ctx,
                                    android.hardware.ISerialManager.Stub.asInterface(b));
                        }
                    });
        }

        registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class,
                new CachedServiceFetcher<VibratorManager>() {
+4 −5
Original line number Diff line number Diff line
@@ -4427,7 +4427,7 @@ public abstract class Context {
                // @hide: SIP_SERVICE,
                USB_SERVICE,
                LAUNCHER_APPS_SERVICE,
                // @hide: SERIAL_SERVICE,
                SERIAL_SERVICE,
                // @hide: HDMI_CONTROL_SERVICE,
                INPUT_SERVICE,
                DISPLAY_SERVICE,
@@ -5998,13 +5998,12 @@ public abstract class Context {

    /**
     * Use with {@link #getSystemService(String)} to retrieve a {@link
     * android.hardware.SerialManager} for access to serial ports.
     * android.hardware.serial.SerialManager} for access to serial ports.
     *
     * @see #getSystemService(String)
     * @see android.hardware.SerialManager
     *
     * @hide
     * @see android.hardware.serial.SerialManager
     */
    @FlaggedApi(android.hardware.serial.flags.Flags.FLAG_ENABLE_SERIAL_API)
    public static final String SERIAL_SERVICE = "serial";

    /**
+4 −1
Original line number Diff line number Diff line
@@ -22,3 +22,6 @@ per-file OverlayProperties* = file:/graphics/java/android/graphics/OWNERS

# Lut related files
per-file *Lut* = file:/graphics/java/android/graphics/OWNERS

# Serial
per-file *Serial* = file:/core/java/android/hardware/serial/OWNERS
+161 −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.hardware.serial;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

/**
 * This class allows you to communicate with Serial ports.
 */
@SystemService(Context.SERIAL_SERVICE)
@FlaggedApi(android.hardware.serial.flags.Flags.FLAG_ENABLE_SERIAL_API)
public final class SerialManager {
    private static final String TAG = "SerialManager";

    @SuppressWarnings("unused")
    private final @NonNull Context mContext;
    private final @NonNull ISerialManager mService;

    @GuardedBy("mLock")
    private SerialPortServiceListener mServiceListener;

    @GuardedBy("mLock")
    private ArrayMap<SerialPortListener, Executor> mListeners;

    private final Object mLock = new Object();

    /** @hide */
    public SerialManager(@NonNull Context context, @NonNull ISerialManager service) {
        mContext = context;
        mService = service;
    }

    /**
     * Enumerates serial ports.
     */
    @NonNull
    public List<SerialPort> getSerialPorts() {
        try {
            List<SerialPortInfo> infos = mService.getSerialPorts();
            List<SerialPort> ports = new ArrayList<>(infos.size());
            for (int i = 0; i < infos.size(); i++) {
                ports.add(new SerialPort(infos.get(i), mService));
            }
            return Collections.unmodifiableList(ports);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Register a listener to monitor serial port connections and disconnections.
     */
    public void registerSerialPortListener(@NonNull SerialPortListener listener,
            @NonNull Executor executor) {
        synchronized (mLock) {
            if (mServiceListener == null) {
                mServiceListener = new SerialPortServiceListener();
                try {
                    mService.registerSerialPortListener(mServiceListener);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            if (mListeners == null) {
                mListeners = new ArrayMap<>();
            }
            if (mListeners.containsKey(listener)) {
                throw new IllegalStateException("Listener has already been registered.");
            }
            mListeners.put(listener, executor);
        }
    }

    /**
     * Unregister a listener that monitored serial port connections and disconnections.
     */
    public void unregisterSerialPortListener(@NonNull SerialPortListener listener) {
        synchronized (mLock) {
            if (mListeners == null) {
                return;
            }
            mListeners.remove(listener);
            if (mListeners.isEmpty()) {
                if (mServiceListener != null) {
                    try {
                        mService.unregisterSerialPortListener(mServiceListener);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    } finally {
                        // If there was a RemoteException, the system server may have died,
                        // and this listener probably became unregistered, so clear it for
                        // re-registration.
                        mServiceListener = null;
                    }
                }
            }
        }
    }

    private class SerialPortServiceListener extends ISerialPortListener.Stub {
        @Override
        public void onSerialPortConnected(SerialPortInfo info) {
            SerialPort port = new SerialPort(info, mService);
            synchronized (mLock) {
                for (Map.Entry<SerialPortListener, Executor> e : mListeners.entrySet()) {
                    Executor executor = e.getValue();
                    SerialPortListener listener = e.getKey();
                    try {
                        executor.execute(() -> listener.onSerialPortConnected(port));
                    } catch (RuntimeException e2) {
                        Slog.w(TAG, "Exception in listener", e2);
                    }
                }
            }
        }

        @Override
        public void onSerialPortDisconnected(SerialPortInfo info) {
            SerialPort port = new SerialPort(info, mService);
            synchronized (mLock) {
                for (Map.Entry<SerialPortListener, Executor> e : mListeners.entrySet()) {
                    Executor executor = e.getValue();
                    SerialPortListener listener = e.getKey();
                    try {
                        executor.execute(() -> listener.onSerialPortDisconnected(port));
                    } catch (RuntimeException e2) {
                        Slog.w(TAG, "Exception in listener", e2);
                    }
                }
            }
        }
    }
}
Loading