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

Commit b45cdc77 authored by George Lu's avatar George Lu
Browse files

Implement AIDL fanout from v2 HAL BroadcastRadioService

Following CLs will:
- Fix app-side "ProgramInfo callback is not set yet" error in TunerCallbackAdapterExt.java.
- Implement fanout for startProgramListUpdates() API.

Bug: 121305828
Test: Manually verified multiple TunerSessions created from single RadioModule
Change-Id: I9ab4d3ce4f0fc9cfe93f8b6fe076fa24fca38ddd
parent de11a5ae
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -90,7 +90,11 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
            for (ModuleWatcher watcher : mModuleWatchers) {
                combined.addAll(watcher.currentList);
            }
            TunerCallback.dispatch(() -> mListener.onListUpdated(combined));
            try {
                mListener.onListUpdated(combined);
            } catch (RemoteException ex) {
                Slog.e(TAG, "mListener.onListUpdated() failed: ", ex);
            }
        }
    }

+8 −23
Original line number Diff line number Diff line
@@ -54,13 +54,6 @@ public class BroadcastRadioService {
    @GuardedBy("mLock")
    private final Map<Integer, RadioModule> mModules = new HashMap<>();

    // Map from module ID to TunerSession created by openSession().
    //
    // Because this service currently implements a 1 AIDL to 1 HAL policy, mTunerSessions is used to
    // enforce the "aggresive open" policy mandated for IBroadcastRadio.openSession(). In the
    // future, this solution will be replaced with a multiple-AIDL to 1 HAL implementation.
    private final Map<Integer, TunerSession> mTunerSessions = new HashMap<>();

    private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
        @Override
        public void onRegistration(String fqName, String serviceName, boolean preexisting) {
@@ -81,8 +74,10 @@ public class BroadcastRadioService {
                }
                Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
                        + " (HAL 2.0)");
                closeTunerSessionLocked(moduleId);
                mModules.put(moduleId, module);
                RadioModule prevModule = mModules.put(moduleId, module);
                if (prevModule != null) {
                    prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
                }

                if (newService) {
                    mServiceNameToModuleIdMap.put(serviceName, moduleId);
@@ -105,8 +100,10 @@ public class BroadcastRadioService {
            Slog.v(TAG, "serviceDied(" + cookie + ")");
            synchronized (mLock) {
                int moduleId = (int) cookie;
                mModules.remove(moduleId);
                closeTunerSessionLocked(moduleId);
                RadioModule prevModule = mModules.remove(moduleId);
                if (prevModule != null) {
                    prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
                }

                for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
                    if (entry.getValue() == moduleId) {
@@ -166,13 +163,9 @@ public class BroadcastRadioService {
            if (module == null) {
                throw new IllegalArgumentException("Invalid module ID");
            }
            closeTunerSessionLocked(moduleId);
        }

        TunerSession tunerSession = module.openSession(callback);
        synchronized (mLock) {
            mTunerSessions.put(moduleId, tunerSession);
        }
        if (legacyConfig != null) {
            tunerSession.setConfiguration(legacyConfig);
        }
@@ -198,12 +191,4 @@ public class BroadcastRadioService {
        }
        return aggregator;
    }

    private void closeTunerSessionLocked(int moduleId) {
        TunerSession tunerSession = mTunerSessions.remove(moduleId);
        if (tunerSession != null) {
            Slog.d(TAG, "Closing previous TunerSession");
            tunerSession.close(RadioTuner.ERROR_HARDWARE_FAILURE);
        }
    }
}
+141 −12
Original line number Diff line number Diff line
@@ -26,16 +26,26 @@ import android.hardware.broadcastradio.V2_0.DabTableEntry;
import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
import android.hardware.broadcastradio.V2_0.ICloseHandle;
import android.hardware.broadcastradio.V2_0.ITunerCallback;
import android.hardware.broadcastradio.V2_0.ITunerSession;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
import android.hardware.broadcastradio.V2_0.ProgramListChunk;
import android.hardware.broadcastradio.V2_0.ProgramSelector;
import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.hardware.radio.RadioManager;
import android.os.DeadObjectException;
import android.os.RemoteException;
import android.util.MutableInt;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

class RadioModule {
@@ -44,8 +54,63 @@ class RadioModule {
    @NonNull private final IBroadcastRadio mService;
    @NonNull public final RadioManager.ModuleProperties mProperties;

    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private ITunerSession mHalTunerSession;

    // Tracks antenna state reported by HAL (if any).
    @GuardedBy("mLock")
    private Boolean mAntennaConnected = null;

    @GuardedBy("mLock")
    private RadioManager.ProgramInfo mProgramInfo = null;

    // Callback registered with the HAL to relay callbacks to AIDL clients.
    private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
        @Override
        public void onTuneFailed(int result, ProgramSelector programSelector) {
            fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal(
                    programSelector)));
        }

        @Override
        public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
            RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo);
            synchronized (mLock) {
                mProgramInfo = programInfo;
                fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo));
            }
        }

        @Override
        public void onProgramListUpdated(ProgramListChunk programListChunk) {
            // TODO: Cache per-AIDL client filters, send union of filters to HAL, use filters to fan
            // back out to clients.
            fanoutAidlCallback(cb -> cb.onProgramListUpdated(Convert.programListChunkFromHal(
                    programListChunk)));
        }

        @Override
        public void onAntennaStateChange(boolean connected) {
            synchronized (mLock) {
                mAntennaConnected = connected;
                fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
            }
        }

        @Override
        public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
            fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
        }
    };

    // Collection of active AIDL tuner sessions created through openSession().
    @GuardedBy("mLock")
    private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();

    private RadioModule(@NonNull IBroadcastRadio service,
            @NonNull RadioManager.ModuleProperties properties) {
            @NonNull RadioManager.ModuleProperties properties) throws RemoteException {
        mProperties = Objects.requireNonNull(properties);
        mService = Objects.requireNonNull(service);
    }
@@ -81,21 +146,85 @@ class RadioModule {

    public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
            throws RemoteException {
        TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
        synchronized (mLock) {
            if (mHalTunerSession == null) {
                Mutable<ITunerSession> hwSession = new Mutable<>();
        MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);

        synchronized (mService) {
            mService.openSession(cb, (result, session) -> {
                mService.openSession(mHalTunerCallback, (result, session) -> {
                    Convert.throwOnError("openSession", result);
                    hwSession.value = session;
                halResult.value = result;
                });
                mHalTunerSession = Objects.requireNonNull(hwSession.value);
            }
            TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
            mAidlTunerSessions.add(tunerSession);

            // Propagate state to new client. Note: These callbacks are invoked while holding mLock
            // to prevent race conditions with new callbacks from the HAL.
            if (mAntennaConnected != null) {
                userCb.onAntennaState(mAntennaConnected);
            }
            if (mProgramInfo != null) {
                userCb.onCurrentProgramInfoChanged(mProgramInfo);
            }

            return tunerSession;
        }
    }

    public void closeSessions(Integer error) {
        // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
        // must be called without mAidlTunerSessions locked because it can call
        // onTunerSessionClosed().
        TunerSession[] tunerSessions;
        synchronized (mLock) {
            tunerSessions = new TunerSession[mAidlTunerSessions.size()];
            mAidlTunerSessions.toArray(tunerSessions);
            mAidlTunerSessions.clear();
        }
        for (TunerSession tunerSession : tunerSessions) {
            tunerSession.close(error);
        }
    }

    void onTunerSessionClosed(TunerSession tunerSession) {
        synchronized (mLock) {
            mAidlTunerSessions.remove(tunerSession);
            if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
                Slog.v(TAG, "closing HAL tuner session");
                try {
                    mHalTunerSession.close();
                } catch (RemoteException ex) {
                    Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
                }
                mHalTunerSession = null;
            }
        }
    }

    interface AidlCallbackRunnable {
        void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
    }

        Convert.throwOnError("openSession", halResult.value);
        Objects.requireNonNull(hwSession.value);
    // Invokes runnable with each TunerSession currently open.
    void fanoutAidlCallback(AidlCallbackRunnable runnable) {
        synchronized (mLock) {
            fanoutAidlCallbackLocked(runnable);
        }
    }

        return new TunerSession(this, hwSession.value, cb);
    private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
        for (TunerSession tunerSession : mAidlTunerSessions) {
            try {
                runnable.run(tunerSession.mCallback);
            } catch (DeadObjectException ex) {
                // The other side died without calling close(), so just purge it from our
                // records.
                Slog.e(TAG, "Removing dead TunerSession");
                mAidlTunerSessions.remove(tunerSession);
            } catch (RemoteException ex) {
                Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
            }
        }
    }

    public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
+0 −76
Original line number Diff line number Diff line
/**
 * Copyright (C) 2017 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.broadcastradio.hal2;

import android.annotation.NonNull;
import android.hardware.broadcastradio.V2_0.ITunerCallback;
import android.hardware.broadcastradio.V2_0.ProgramInfo;
import android.hardware.broadcastradio.V2_0.ProgramListChunk;
import android.hardware.broadcastradio.V2_0.ProgramSelector;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
import android.os.RemoteException;
import android.util.Slog;

import java.util.ArrayList;
import java.util.Objects;

class TunerCallback extends ITunerCallback.Stub {
    private static final String TAG = "BcRadio2Srv.cb";

    final android.hardware.radio.ITunerCallback mClientCb;

    interface RunnableThrowingRemoteException {
        void run() throws RemoteException;
    }

    TunerCallback(@NonNull android.hardware.radio.ITunerCallback clientCallback) {
        mClientCb = Objects.requireNonNull(clientCallback);
    }

    static void dispatch(RunnableThrowingRemoteException func) {
        try {
            func.run();
        } catch (RemoteException ex) {
            Slog.e(TAG, "callback call failed", ex);
        }
    }

    @Override
    public void onTuneFailed(int result, ProgramSelector selector) {
        dispatch(() -> mClientCb.onTuneFailed(result, Convert.programSelectorFromHal(selector)));
    }

    @Override
    public void onCurrentProgramInfoChanged(ProgramInfo info) {
        dispatch(() -> mClientCb.onCurrentProgramInfoChanged(Convert.programInfoFromHal(info)));
    }

    @Override
    public void onProgramListUpdated(ProgramListChunk chunk) {
        dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk)));
    }

    @Override
    public void onAntennaStateChange(boolean connected) {
        dispatch(() -> mClientCb.onAntennaState(connected));
    }

    @Override
    public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
        dispatch(() -> mClientCb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
    }
}
+10 −5
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ class TunerSession extends ITuner.Stub {

    private final RadioModule mModule;
    private final ITunerSession mHwSession;
    private final TunerCallback mCallback;
    final android.hardware.radio.ITunerCallback mCallback;
    private boolean mIsClosed = false;
    private boolean mIsMuted = false;

@@ -51,7 +51,7 @@ class TunerSession extends ITuner.Stub {
    private RadioManager.BandConfig mDummyConfig = null;

    TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
            @NonNull TunerCallback callback) {
            @NonNull android.hardware.radio.ITunerCallback callback) {
        mModule = Objects.requireNonNull(module);
        mHwSession = Objects.requireNonNull(hwSession);
        mCallback = Objects.requireNonNull(callback);
@@ -73,9 +73,14 @@ class TunerSession extends ITuner.Stub {
        synchronized (mLock) {
            if (mIsClosed) return;
            if (error != null) {
                TunerCallback.dispatch(() -> mCallback.mClientCb.onError(error));
                try {
                    mCallback.onError(error);
                } catch (RemoteException ex) {
                    Slog.w(TAG, "mCallback.onError() failed: ", ex);
                }
            }
            mIsClosed = true;
            mModule.onTunerSessionClosed(this);
        }
    }

@@ -96,7 +101,7 @@ class TunerSession extends ITuner.Stub {
            checkNotClosedLocked();
            mDummyConfig = Objects.requireNonNull(config);
            Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x");
            TunerCallback.dispatch(() -> mCallback.mClientCb.onConfigurationChanged(config));
            mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
        }
    }

@@ -174,7 +179,7 @@ class TunerSession extends ITuner.Stub {
    @Override
    public boolean startBackgroundScan() {
        Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
        TunerCallback.dispatch(() -> mCallback.mClientCb.onBackgroundScanComplete());
        mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete());
        return true;
    }