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

Commit 919d5199 authored by Oleg Nitz's avatar Oleg Nitz Committed by Android (Google) Code Review
Browse files

Merge "Use Native Serial Service for file operations." into main

parents e0b58633 ab98f839
Loading
Loading
Loading
Loading
+5 −8
Original line number Diff line number Diff line
@@ -28,14 +28,12 @@ oneway interface ISerialPortResponseCallback {
    /** Error codes for {@link #onError}. */
    @Backing(type="int")
    enum ErrorCode {
        // IOException while reading the list of derial drivers
        ERROR_READING_DRIVERS = 0,
        // Serial port with the given name does not exist.
        ERROR_PORT_NOT_FOUND = 1,
        ERROR_PORT_NOT_FOUND = 0,
        // SecurityException due to access denied.
        ERROR_ACCESS_DENIED = 2,
        // ErrnoException while opening the serial port.
        ERROR_OPENING_PORT = 3,
        ERROR_ACCESS_DENIED = 1,
        // Error while opening the serial port.
        ERROR_OPENING_PORT = 2,
    }

    /**
@@ -50,8 +48,7 @@ oneway interface ISerialPortResponseCallback {
     * Called when the serial port opening failed.
     *
     * @param errorCode The error code indicating the type of error that occurred.
     * @param errno The errno from ErrnoException in case of ERROR_OPENING_PORT.
     * @param message Additional text information about the error.
     */
    void onError(in ErrorCode errorCode, in int errno, in String message);
    void onError(in ErrorCode errorCode, in String message);
}
+12 −15
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package android.hardware.serial;

import static android.system.OsConstants.ENOENT;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.FlaggedApi;
@@ -27,7 +25,6 @@ import android.content.Context;
import android.os.OutcomeReceiver;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.system.ErrnoException;

import java.io.IOException;
import java.lang.annotation.Retention;
@@ -131,14 +128,14 @@ public final class SerialPort {
     *
     * <p>Exceptions passed to {@code receiver} may be
     * <ul>
     * <li> {@link ErrnoException} with ENOENT if the port is detached or any syscall to open the
     * port fails that come with an errno</li>
     * <li> {@link IOException} if other required operations fail that don't come with errno</li>
     * <li> {@link SecurityException} if the user rejects the open request</li>
     * <li> {@link IllegalStateException} if the port is not found.</li>
     * <li> {@link IOException} if the port cannot be opened.</li>
     * <li> {@link SecurityException} if the user rejects the open request.</li>
     * </ul>
     *
     * @param flags     open flags that define read/write mode and other options.
     * @param exclusive whether the app needs exclusive access with TIOCEXCL(2const)
     * @param exclusive whether to request exclusive access to the port, preventing other processes
     *                  from opening it.
     * @param executor  the executor used to run receiver
     * @param receiver  the outcome receiver
     * @throws IllegalArgumentException if the set of flags is not correct.
@@ -174,18 +171,18 @@ public final class SerialPort {
        }

        @Override
        public void onError(@ErrorCode int errorCode, int errno, String message) {
            mExecutor.execute(() -> mReceiver.onError(getException(errorCode, errno, message)));
        public void onError(@ErrorCode int errorCode, String message) {
            mExecutor.execute(() -> mReceiver.onError(getException(errorCode, message)));
        }

        @NonNull
        private static Exception getException(int errorCode, int errno, String message) {
        private static Exception getException(int errorCode, String message) {
            return switch (errorCode) {
                case ErrorCode.ERROR_READING_DRIVERS -> new IOException(message);
                case ErrorCode.ERROR_PORT_NOT_FOUND -> new ErrnoException(message, ENOENT);
                case ErrorCode.ERROR_PORT_NOT_FOUND -> new IllegalStateException(message);
                case ErrorCode.ERROR_ACCESS_DENIED -> new SecurityException(message);
                case ErrorCode.ERROR_OPENING_PORT -> new ErrnoException(message, errno);
                default -> new IllegalStateException("Unexpected errorCode " + errorCode);
                case ErrorCode.ERROR_OPENING_PORT -> new IOException(message);
                default -> new IllegalStateException(
                        "errorCode=" + errorCode + ", message=" + message);
            };
        }
    }
+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@ java_library_static {
    name: "services.serial",
    defaults: ["platform_service_defaults"],
    srcs: [":services.serial-sources"],
    static_libs: [
        "android.hardware.serialservice-java",
    ],
    libs: [
        "services.core",
    ],
+0 −129
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.server.serial;

import static com.android.server.serial.SerialConstants.DEV_DIR;
import static com.android.server.serial.SerialConstants.SYSFS_DIR;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;

import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/** Represents device related files and links under /dev and /sys. */
class DeviceFileInfo {

    // Device name
    final String mName;

    // Device Number (`st_rdev` field of `struct stat`)
    final long mNumber;

    // Device info link under /sys
    private Path mSysfsLink;

    // Device subsystem
    private String mSubsystem;

    @VisibleForTesting
    DeviceFileInfo(String name, long number) {
        mName = name;
        mNumber = number;
        mSysfsLink = Paths.get(SYSFS_DIR).resolve(name);
    }

    /** Create from /dev/name (check if /sys/class/tty/name exists). */
    @Nullable
    static DeviceFileInfo fromNameInDev(String name) {
        if (!new File(SYSFS_DIR, name).exists()) {
            return null;
        }
        return fromName(name);
    }

    /** Create from /sys/class/tty/name (check if /dev/name exists). */
    @Nullable
    static DeviceFileInfo fromNameInSysfs(String name) {
        if (!new File(DEV_DIR, name).exists()) {
            return null;
        }
        return fromName(name);
    }

    @Nullable
    private static DeviceFileInfo fromName(String name) {
        try {
            StructStat stat = Os.stat(DEV_DIR + "/" + name);
            DeviceFileInfo device = new DeviceFileInfo(name, stat.st_rdev);
            if (device.getSubsystem().equals("platform")) {
                // Keep built-in UARTs hidden (for security reasons).
                return null;
            }
            return device;
        } catch (ErrnoException | IOException e) {
            return null;
        }
    }

    boolean isUsb() {
        try {
            return switch (getSubsystem()) {
                case "usb", "usb-serial" -> true;
                default -> false;
            };
        } catch (IOException e) {
            return false;
        }
    }

    @NonNull
    private String getSubsystem() throws IOException {
        if (mSubsystem == null) {
            // E.g. /sys/class/tty/ttyACM0/device/subsystem -> .../bus/usb
            Path subsystemLink = getSysfsLink().resolve("device/subsystem");
            if (!Files.exists(subsystemLink)) {
                mSubsystem = "virtual";
            }
            mSubsystem = subsystemLink.toRealPath().getFileName().toString();
        }
        return mSubsystem;
    }

    /** Device info link under /sys. */
    Path getSysfsLink() {
        return mSysfsLink;
    }

    @VisibleForTesting
    void setSysfsLink(Path sysfsLink) {
        mSysfsLink = sysfsLink;
    }

    @VisibleForTesting
    void setSubsystem(String subsystem) {
        mSubsystem = subsystem;
    }
}
+13 −6
Original line number Diff line number Diff line
@@ -16,14 +16,21 @@

package com.android.server.serial;

class SerialConstants {
import android.hardware.serialservice.SerialPortInfo;

    /** Directory with device pseudo-files, including serial devices. */
    static final String DEV_DIR = "/dev";
import java.util.function.Predicate;

    /** Sysfs directory containing information about serial devices. */
    static final String SYSFS_DIR = "/sys/class/tty";
/** Filters serial devices that are exposed to the user. */
class SerialDeviceFilter implements Predicate<SerialPortInfo> {

    private SerialConstants() {
    private static final String SERIAL_DRIVER_TYPE = "serial";

    private static final String BUILT_IN_SERIAL_SUBSYSTEM = "serial-base";

    public boolean test(SerialPortInfo info) {
        // Expose only devices having "serial" driver type.
        return info.driverType.equals(SERIAL_DRIVER_TYPE)
                // Keep built-in UARTs hidden (for security reasons).
                && !info.subsystem.equals(BUILT_IN_SERIAL_SUBSYSTEM);
    }
}
Loading