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

Commit ff783371 authored by Kyunglyul Hyun's avatar Kyunglyul Hyun Committed by Android (Google) Code Review
Browse files

Merge "Media: add onRoute*** callback"

parents 3eb0321d 6c5d7dcb
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -24,5 +24,5 @@ import android.media.MediaRoute2ProviderInfo;
oneway interface IMediaRouter2Manager {
    void notifyRouteSelected(int uid, String routeId);
    void notifyControlCategoriesChanged(int uid, in List<String> categories);
    void notifyProviderInfoUpdated(in MediaRoute2ProviderInfo info);
    void notifyProviderInfosUpdated(in List<MediaRoute2ProviderInfo> providers);
}
+13 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.text.TextUtils;
 * @hide
 */
public final class MediaRoute2Info implements Parcelable {
    @NonNull
    public static final Creator<MediaRoute2Info> CREATOR = new Creator<MediaRoute2Info>() {
        @Override
        public MediaRoute2Info createFromParcel(Parcel in) {
@@ -63,6 +64,18 @@ public final class MediaRoute2Info implements Parcelable {
        mExtras = in.readBundle();
    }

    /**
     * Returns true if the route info has all of the required field
     * @hide
     */
    //TODO: Reconsider the validity of a route info when fields are added.
    public boolean isValid() {
        if (TextUtils.isEmpty(getId()) || TextUtils.isEmpty(getName())) {
            return false;
        }
        return true;
    }

    @NonNull
    public String getId() {
        return mId;
+77 −25
Original line number Diff line number Diff line
@@ -20,20 +20,20 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Describes the state of a media router provider and the routes that it publishes.
 * @hide
 */
public final class MediaRoute2ProviderInfo implements Parcelable {
    public static final Parcelable.Creator<MediaRoute2ProviderInfo> CREATOR =
            new Parcelable.Creator<MediaRoute2ProviderInfo>() {
    @NonNull
    public static final Creator<MediaRoute2ProviderInfo> CREATOR =
            new Creator<MediaRoute2ProviderInfo>() {
        @Override
        public MediaRoute2ProviderInfo createFromParcel(Parcel in) {
            return new MediaRoute2ProviderInfo(in);
@@ -44,23 +44,63 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
        }
    };

    @Nullable
    private final String mUniqueId;
    @NonNull
    private final List<MediaRoute2Info> mRoutes;
    private final ArrayMap<String, MediaRoute2Info> mRoutes;

    MediaRoute2ProviderInfo(@Nullable List<MediaRoute2Info> routes) {
        mRoutes = (routes == null) ? Collections.emptyList() : routes;
    MediaRoute2ProviderInfo(@NonNull Builder builder) {
        if (builder == null) {
            throw new NullPointerException("Builder must not be null.");
        }
        mUniqueId = builder.mUniqueId;
        mRoutes = builder.mRoutes;
    }

    MediaRoute2ProviderInfo(@NonNull Parcel src) {
        mRoutes = src.createTypedArrayList(MediaRoute2Info.CREATOR);
        mUniqueId = src.readString();
        ArrayMap<String, MediaRoute2Info> routes = src.createTypedArrayMap(MediaRoute2Info.CREATOR);
        mRoutes = (routes == null) ? ArrayMap.EMPTY : routes;
    }

    /**
     * Returns true if the information of the provider and all of it's routes have all
     * of the required fields.
     * @hide
     */
    public boolean isValid() {
        if (mUniqueId == null) {
            return false;
        }
        final int count = mRoutes.size();
        for (int i = 0; i < count; i++) {
            MediaRoute2Info route = mRoutes.valueAt(i);
            if (route == null || !route.isValid()) {
                return false;
            }
        }
        return true;
    }

    @Nullable
    String getUniqueId() {
        return mUniqueId;
    }

    /**
     * Gets the route for the given route id or null if no matching route exists.
     */
    @Nullable
    public MediaRoute2Info getRoute(String routeId) {
        return mRoutes.get(routeId);
    }

    /**
     * Gets the unmodifiable list of all routes that this provider has published.
     */
    @NonNull
    public List<MediaRoute2Info> getRoutes() {
        return Collections.unmodifiableList(mRoutes);
    public Collection<MediaRoute2Info> getRoutes() {
        return mRoutes.values();
    }

    @Override
@@ -70,14 +110,16 @@ public final class MediaRoute2ProviderInfo implements Parcelable {

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeTypedList(mRoutes);
        dest.writeString(mUniqueId);
        dest.writeTypedArrayMap(mRoutes, flags);
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder()
                .append("MediaRouteProviderInfo { ")
                .append("routes=").append(Arrays.toString(getRoutes().toArray()))
                .append("uniqueId=").append(mUniqueId)
                .append(", routes=").append(Arrays.toString(getRoutes().toArray()))
                .append(" }");
        return result.toString();
    }
@@ -87,31 +129,43 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
     */
    public static final class Builder {
        @NonNull
        private final List<MediaRoute2Info> mRoutes;
        final ArrayMap<String, MediaRoute2Info> mRoutes;
        String mUniqueId;

        public Builder() {
            mRoutes = new ArrayList<>();
            mRoutes = new ArrayMap<>();
        }

        public Builder(@NonNull MediaRoute2ProviderInfo descriptor) {
            if (descriptor == null) {
                throw new IllegalArgumentException("descriptor must not be null");
            }
            mRoutes = new ArrayList<>(descriptor.mRoutes);
            mRoutes = new ArrayMap<>(descriptor.mRoutes);
        }

        /**
         * Sets the unique id of the provider info.
         * <p>
         * The unique id is automatically set by
         * {@link com.android.server.media.MediaRouterService} and used to identify providers.
         * The id set by {@link MediaRoute2ProviderService} will be ignored.
         * </p>
         */
        public Builder setUniqueId(@Nullable String uniqueId) {
            mUniqueId = uniqueId;
            return this;
        }

        /**
         * Adds a route to the provider
         */
        public Builder addRoute(@NonNull MediaRoute2Info route) {
            if (route == null) {
                throw new IllegalArgumentException("route must not be null");
            }
            Objects.requireNonNull(route, "route must not be null");

            if (mRoutes.contains(route)) {
            if (mRoutes.containsValue(route)) {
                throw new IllegalArgumentException("route descriptor already added");
            }
            mRoutes.add(route);
            mRoutes.put(route.getId(), route);
            return this;
        }

@@ -119,9 +173,7 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
         * Adds a list of routes to the provider
         */
        public Builder addRoutes(@NonNull Collection<MediaRoute2Info> routes) {
            if (routes == null) {
                throw new IllegalArgumentException("routes must not be null");
            }
            Objects.requireNonNull(routes, "routes must not be null");

            if (!routes.isEmpty()) {
                for (MediaRoute2Info route : routes) {
@@ -136,7 +188,7 @@ public final class MediaRoute2ProviderInfo implements Parcelable {
         */
        @NonNull
        public MediaRoute2ProviderInfo build() {
            return new MediaRoute2ProviderInfo(mRoutes);
            return new MediaRoute2ProviderInfo(this);
        }
    }
}
+105 −5
Original line number Diff line number Diff line
@@ -25,12 +25,17 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

/**
@@ -53,6 +58,9 @@ public class MediaRouter2Manager {
    @GuardedBy("sLock")
    final ArrayList<CallbackRecord> mCallbacks = new ArrayList<>();

    @NonNull
    private List<MediaRoute2ProviderInfo> mProviders = Collections.emptyList();

    /**
     * Gets an instance of media router manager that controls media route of other apps.
     * @param context
@@ -183,11 +191,88 @@ public class MediaRouter2Manager {
        }
    }

    void notifyProviderUpdated(MediaRoute2ProviderInfo info) {
        //TODO: should call back properly
    int findProviderIndex(MediaRoute2ProviderInfo provider) {
        final int count = mProviders.size();
        for (int i = 0; i < count; i++) {
            if (TextUtils.equals(mProviders.get(i).getUniqueId(), provider.getUniqueId())) {
                return i;
            }
        }
        return -1;
    }

    MediaRoute2ProviderInfo getProvider(int index) {
        return mProviders.get(index);
    }

    void updateProvider(@NonNull MediaRoute2ProviderInfo provider) {
        if (provider == null || !provider.isValid()) {
            Log.w(TAG, "Ignoring invalid provider : " + provider);
            return;
        }

        final Collection<MediaRoute2Info> routes = provider.getRoutes();

        final int index = findProviderIndex(provider);
        if (index >= 0) {
            final MediaRoute2ProviderInfo prevProvider = getProvider(index);
            final Set<String> updatedRouteIds = new HashSet<>();
            for (MediaRoute2Info routeInfo : routes) {
                final MediaRoute2Info prevRoute = prevProvider.getRoute(routeInfo.getId());
                if (prevRoute == null) {
                    notifyRouteAdded(routeInfo);
                } else {
                    //TODO: Notify only it's really changed.
                    notifyRouteChanged(routeInfo);
                    updatedRouteIds.add(routeInfo.getId());
                }
            }
            final Collection<MediaRoute2Info> prevRoutes = prevProvider.getRoutes();

            for (MediaRoute2Info prevRoute : prevRoutes) {
                notifyRouteRemoved(prevRoute);
            }
        } else {
            for (MediaRoute2Info routeInfo: routes) {
                notifyRouteAdded(routeInfo);
            }
        }
    }

    void notifyRouteAdded(MediaRoute2Info routeInfo) {
        for (CallbackRecord record : mCallbacks) {
            record.mExecutor.execute(
                    () -> record.mCallback.onRouteAdded(routeInfo));
        }
    }

    void notifyRouteChanged(MediaRoute2Info routeInfo) {
        for (CallbackRecord record : mCallbacks) {
            record.mExecutor.execute(
                    () -> record.mCallback.onRouteChanged(routeInfo));
        }
    }

    void notifyRouteRemoved(MediaRoute2Info routeInfo) {
        for (CallbackRecord record : mCallbacks) {
            record.mExecutor.execute(() -> record.mCallback.onProviderInfoUpdated(info));
            record.mExecutor.execute(
                    () -> record.mCallback.onRouteRemoved(routeInfo));
        }
    }

    void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> providers) {
        if (providers == null) {
            Log.w(TAG, "Providers info is null.");
            return;
        }

        for (MediaRoute2ProviderInfo provider : providers) {
            updateProvider(provider);
        }
        //TODO: Call notifyProviderRemoved for removed providers.

        //TODO: Filter invalid providers.
        mProviders = providers;
    }

    void notifyRouteSelected(int uid, String routeId) {
@@ -207,6 +292,21 @@ public class MediaRouter2Manager {
     * Interface for receiving events about media routing changes.
     */
    public abstract static class Callback {
        /**
         * Called when a route is added.
         */
        public void onRouteAdded(MediaRoute2Info routeInfo) {}

        /**
         * Called when a route is changed.
         */
        public void onRouteChanged(MediaRoute2Info routeInfo) {}

        /**
         * Called when a route is removed.
         */
        public void onRouteRemoved(MediaRoute2Info routeInfo) {}

        /**
         * Called when a route is selected for some application uid.
         * @param uid
@@ -252,8 +352,8 @@ public class MediaRouter2Manager {
        }

        @Override
        public void notifyProviderInfoUpdated(MediaRoute2ProviderInfo info) {
            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyProviderUpdated,
        public void notifyProviderInfosUpdated(List<MediaRoute2ProviderInfo> info) {
            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyProviderInfosUpdated,
                    MediaRouter2Manager.this, info));
        }
    }
+12 −9
Original line number Diff line number Diff line
@@ -16,16 +16,16 @@

package com.android.mediaroutertest;

import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
import android.media.MediaRouter;
import android.media.MediaRouter2Manager;
import android.support.test.InstrumentationRegistry;
@@ -49,10 +49,13 @@ public class MediaRouterManagerTest {
    private static final String TAG = "MediaRouterManagerTest";

    private static final int TARGET_UID = 109992;
    private static final String ROUTE_1 = "MediaRoute1";

    // Must be the same as SampleMediaRoute2ProviderService
    public static final String ROUTE_ID1 = "route_id";
    public static final String ROUTE_NAME1 = "route_name";

    private static final int AWAIT_MS = 1000;
    private static final int TIMEOUT_MS = 1000;
    private static final int TIMEOUT_MS = 5000;

    private Context mContext;
    private MediaRouter2Manager mManager;
@@ -77,16 +80,16 @@ public class MediaRouterManagerTest {
            new SynchronousQueue<Runnable>());
    }

    //TODO: onRouteChanged, onRouteRemoved must be tested
    @Test
    public void providerTest() {
    public void testRouteAddedOnce() {
        MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);

        mManager.addCallback(mExecutor, mockCallback);

        //TODO: should be changed to onRouteAdded
        verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
                .onProviderInfoUpdated(argThat(
                        (MediaRoute2ProviderInfo info) -> info.getRoutes().size() == 1));
        verify(mockCallback, timeout(TIMEOUT_MS)).onRouteAdded(argThat(
                (MediaRoute2Info info) ->
                        info.getId().equals(ROUTE_ID1) && info.getName().equals(ROUTE_NAME1)));
        mManager.removeCallback(mockCallback);
    }

Loading