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

Commit 870ca5ea authored by Iván Budnik's avatar Iván Budnik Committed by Android (Google) Code Review
Browse files

Merge "Implement MediaRouter2-based InfoMediaManager" into main

parents a4e21751 5bb1e5e8
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line Diff line number Diff line
@@ -82,6 +82,13 @@ public abstract class InfoMediaManager extends MediaManager {
    private static final String TAG = "InfoMediaManager";
    private static final String TAG = "InfoMediaManager";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);


    /** Checked exception that signals the specified package is not present in the system. */
    public static class PackageNotAvailableException extends Exception {
        public PackageNotAvailableException(String message) {
            super(message);
        }
    }

    protected String mPackageName;
    protected String mPackageName;
    private MediaDevice mCurrentConnectedDevice;
    private MediaDevice mCurrentConnectedDevice;
    private final LocalBluetoothManager mBluetoothManager;
    private final LocalBluetoothManager mBluetoothManager;
+320 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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.settingslib.media;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.settingslib.bluetooth.LocalBluetoothManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
@SuppressLint("MissingPermission")
public final class RouterInfoMediaManager extends InfoMediaManager {

    private static final String TAG = "RouterInfoMediaManager";

    private final MediaRouter2 mRouter;
    private final MediaRouter2Manager mRouterManager;

    private final Executor mExecutor = Executors.newSingleThreadExecutor();

    private final RouteCallback mRouteCallback = new RouteCallback();
    private final TransferCallback mTransferCallback = new TransferCallback();
    private final ControllerCallback mControllerCallback = new ControllerCallback();
    private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
            new RouteListingPreferenceCallback();

    // TODO: b/192657812 - Create factory method in InfoMediaManager to return
    //      RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
    public RouterInfoMediaManager(
            Context context,
            String packageName,
            Notification notification,
            LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
        super(context, packageName, notification, localBluetoothManager);

        // TODO: b/291277292 - Change optional package name for a mandatory uid.
        if (packageName == null) {
            packageName = context.getPackageName();
        }

        mRouter = MediaRouter2.getInstance(context, packageName);

        if (mRouter == null) {
            throw new PackageNotAvailableException(
                    "Package name " + packageName + " does not exist.");
        }
        mRouterManager = MediaRouter2Manager.getInstance(context);
    }

    @Override
    protected void startScanOnRouter() {
        mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
        mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
        mRouter.registerTransferCallback(mExecutor, mTransferCallback);
        mRouter.registerControllerCallback(mExecutor, mControllerCallback);
        mRouter.startScan();
    }

    @Override
    public void stopScan() {
        mRouter.stopScan();
        mRouter.unregisterControllerCallback(mControllerCallback);
        mRouter.unregisterTransferCallback(mTransferCallback);
        mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
        mRouter.unregisterRouteCallback(mRouteCallback);
    }

    @Override
    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
        if (device.mRouteInfo == null) {
            return false;
        }

        RoutingController controller = mRouter.getSystemController();
        mRouter.transfer(controller, device.mRouteInfo);
        return true;
    }

    @Override
    protected void transferToRoute(@NonNull MediaRoute2Info route) {
        mRouter.transferTo(route);
    }

    @Override
    protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
        RoutingController controller = getControllerForSession(info);
        if (controller != null) {
            controller.selectRoute(route);
        }
    }

    @Override
    protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
        RoutingController controller = getControllerForSession(info);
        if (controller != null) {
            controller.deselectRoute(route);
        }
    }

    @Override
    protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
        RoutingController controller = getControllerForSession(sessionInfo);
        if (controller != null) {
            controller.release();
        }
    }

    @NonNull
    @Override
    protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
        RoutingController controller = getControllerForSession(info);
        if (controller == null) {
            return Collections.emptyList();
        }

        // Filter out selected routes.
        List<String> selectedRouteIds = controller.getRoutingSessionInfo().getSelectedRoutes();
        return controller.getSelectableRoutes().stream()
                .filter(route -> !selectedRouteIds.contains(route.getId()))
                .collect(Collectors.toList());
    }

    @NonNull
    @Override
    protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
        RoutingController controller = getControllerForSession(info);
        if (controller == null) {
            return Collections.emptyList();
        }

        return controller.getDeselectableRoutes();
    }

    @NonNull
    @Override
    protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
        RoutingController controller = getControllerForSession(info);
        if (controller == null) {
            return Collections.emptyList();
        }
        return controller.getSelectedRoutes();
    }

    @Override
    protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
        // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
        //      MR2 filters information by package name.
        mRouterManager.setSessionVolume(info, volume);
    }

    @Override
    protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
        mRouter.setRouteVolume(route, volume);
    }

    @Nullable
    @Override
    protected RouteListingPreference getRouteListingPreference() {
        return mRouter.getRouteListingPreference();
    }

    @NonNull
    @Override
    protected List<RoutingSessionInfo> getRemoteSessions() {
        // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
        //      MR2 filters information by package name.
        return mRouterManager.getRemoteSessions();
    }

    @NonNull
    @Override
    protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
        return mRouter.getControllers().stream()
                .map(RoutingController::getRoutingSessionInfo)
                .collect(Collectors.toList());
    }

    @Nullable
    @Override
    protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
        // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager calls as
        //      MR2 filters information by package name.

        for (RoutingSessionInfo sessionInfo : getRemoteSessions()) {
            if (TextUtils.equals(sessionInfo.getId(), sessionId)) {
                return sessionInfo;
            }
        }

        RoutingSessionInfo systemSession = mRouterManager.getSystemRoutingSession(null);
        return TextUtils.equals(systemSession.getId(), sessionId) ? systemSession : null;
    }

    @NonNull
    @Override
    protected List<MediaRoute2Info> getAllRoutes() {
        return mRouter.getAllRoutes();
    }

    @NonNull
    @Override
    protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
        return mRouter.getRoutes();
    }

    @NonNull
    @Override
    protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
        List<MediaRoute2Info> transferableRoutes = new ArrayList<>();

        List<RoutingController> controllers = mRouter.getControllers();
        RoutingController activeController = controllers.get(controllers.size() - 1);
        RoutingSessionInfo sessionInfo = activeController.getRoutingSessionInfo();
        List<MediaRoute2Info> routes = mRouter.getRoutes();

        for (MediaRoute2Info route : routes) {
            boolean isCrossDeviceTransfer = sessionInfo.isSystemSession() ^ route.isSystemRoute();

            // Always show remote routes if transfer is local -> remote or viceversa regardless of
            // whether route is in transferable routes list.
            if (sessionInfo.getTransferableRoutes().contains(route.getId())
                    || isCrossDeviceTransfer) {
                transferableRoutes.add(route);
            }
        }

        return transferableRoutes;
    }

    @Nullable
    private RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) {
        return mRouter.getController(sessionInfo.getId());
    }

    private final class RouteCallback extends MediaRouter2.RouteCallback {
        @Override
        public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {
            refreshDevices();
        }

        @Override
        public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {
            refreshDevices();
        }
    }

    private final class TransferCallback extends MediaRouter2.TransferCallback {
        @Override
        public void onTransfer(
                @NonNull RoutingController oldController,
                @NonNull RoutingController newController) {
            rebuildDeviceList();
            notifyCurrentConnectedDeviceChanged();
        }

        @Override
        public void onTransferFailure(@NonNull MediaRoute2Info requestedRoute) {
            // Do nothing.
        }

        @Override
        public void onStop(@NonNull RoutingController controller) {
            refreshDevices();
        }

        @Override
        public void onRequestFailed(int reason) {
            dispatchOnRequestFailed(reason);
        }
    }

    private final class ControllerCallback extends MediaRouter2.ControllerCallback {
        @Override
        public void onControllerUpdated(@NonNull RoutingController controller) {
            refreshDevices();
        }
    }

    private final class RouteListingPreferenceCallback
            extends MediaRouter2.RouteListingPreferenceCallback {
        @Override
        public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
            notifyRouteListingPreferenceUpdated(preference);
            refreshDevices();
        }
    }
}