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

Commit 5aa60825 authored by Kyunglyul Hyun's avatar Kyunglyul Hyun
Browse files

Media: Introduce MediaRouter2Service

Implementations related to MediaRouter2 and MediaRouter2Manager are
moved to MediaRouter2Service to differentiate it and the previous
implementation.

Once MediaRouter2Service is finished, either it is merged to
MediaRouterService or it will replace MediaRouterService.

Bug: 130848964
Test: manually test cast using system ui and settings

Change-Id: I1360977e2c241e21c14c7fb5f705b77a3e3a85ac
parent 50866778
Loading
Loading
Loading
Loading
+438 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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.media;

import android.app.ActivityManager;
import android.content.Context;
import android.media.IMediaRouter2ManagerClient;
import android.media.IMediaRouterClient;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;

import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * TODO: Merge this to MediaRouterService once it's finished.
 */
class MediaRouter2ServiceImpl {
    private static final String TAG = "MediaRouter2ServiceImpl";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final Context mContext;
    private final Object mLock = new Object();
    private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();

    private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
    private int mCurrentUserId = -1;

    MediaRouter2ServiceImpl(Context context) {
        mContext = context;
    }

    public void registerManagerAsUser(IMediaRouter2ManagerClient client,
            String packageName, int userId) {
        if (client == null) {
            throw new IllegalArgumentException("client must not be null");
        }
        //TODO: should check permission
        final boolean trusted = true;

        final int uid = Binder.getCallingUid();
        final int pid = Binder.getCallingPid();
        final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
                false /*allowAll*/, true /*requireFull*/, "registerManagerAsUser", packageName);
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                registerManagerLocked(client, uid, pid, packageName, resolvedUserId, trusted);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    public void unregisterManager(IMediaRouter2ManagerClient client) {
        if (client == null) {
            throw new IllegalArgumentException("client must not be null");
        }

        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                unregisterManagerLocked(client, false);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    public void setControlCategories(IMediaRouterClient client, List<String> categories) {
        if (client == null) {
            throw new IllegalArgumentException("client must not be null");
        }
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                setControlCategoriesLocked(client, categories);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    public void setRemoteRoute(IMediaRouter2ManagerClient client,
            int uid, String routeId, boolean explicit) {
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                setRemoteRouteLocked(client, uid, routeId, explicit);
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    void clientDied(ManagerRecord managerRecord) {
        synchronized (mLock) {
            unregisterManagerLocked(managerRecord.mClient, true);
        }
    }

    private void registerManagerLocked(IMediaRouter2ManagerClient client,
            int uid, int pid, String packageName, int userId, boolean trusted) {
        final IBinder binder = client.asBinder();
        ManagerRecord managerRecord = mAllManagerRecords.get(binder);
        if (managerRecord == null) {
            boolean newUser = false;
            UserRecord userRecord = mUserRecords.get(userId);
            if (userRecord == null) {
                userRecord = new UserRecord(userId);
                newUser = true;
            }
            managerRecord = new ManagerRecord(userRecord, client, uid, pid, packageName, trusted);
            try {
                binder.linkToDeath(managerRecord, 0);
            } catch (RemoteException ex) {
                throw new RuntimeException("Media router client died prematurely.", ex);
            }

            if (newUser) {
                mUserRecords.put(userId, userRecord);
                initializeUserLocked(userRecord);
            }

            userRecord.mManagerRecords.add(managerRecord);
            mAllManagerRecords.put(binder, managerRecord);

            //TODO: send client's info to manager
        }
    }

    private void unregisterManagerLocked(IMediaRouter2ManagerClient client, boolean died) {
        ManagerRecord clientRecord = mAllManagerRecords.remove(client.asBinder());
        if (clientRecord != null) {
            UserRecord userRecord = clientRecord.mUserRecord;
            userRecord.mManagerRecords.remove(clientRecord);
            clientRecord.dispose();
            disposeUserIfNeededLocked(userRecord); // since client removed from user
        }
    }

    private void setRemoteRouteLocked(IMediaRouter2ManagerClient client,
            int uid, String routeId, boolean explicit) {
        ManagerRecord managerRecord = mAllManagerRecords.get(client.asBinder());
        if (managerRecord != null) {
            if (explicit && managerRecord.mTrusted) {
                Pair<Integer, String> obj = new Pair<>(uid, routeId);
                managerRecord.mUserRecord.mHandler.obtainMessage(
                        UserHandler.MSG_SELECT_REMOTE_ROUTE, obj).sendToTarget();
            }
        }
    }

    private void setControlCategoriesLocked(IMediaRouterClient client, List<String> categories) {
        //TODO: implement this when we have client record (MediaRouter2?)
//        final IBinder binder = client.asBinder();
//        ClientRecord clientRecord = mAllClientRecords.get(binder);
//
//        if (clientRecord != null) {
//            clientRecord.mControlCategories = categories;
//            clientRecord.mUserRecord.mHandler.obtainMessage(
//                    UserHandler.MSG_UPDATE_CLIENT_USAGE, clientRecord).sendToTarget();
//        }
    }

    private void initializeUserLocked(UserRecord userRecord) {
        if (DEBUG) {
            Slog.d(TAG, userRecord + ": Initialized");
        }
        if (userRecord.mUserId == mCurrentUserId) {
            userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
        }
    }

    private void disposeUserIfNeededLocked(UserRecord userRecord) {
        // If there are no records left and the user is no longer current then go ahead
        // and purge the user record and all of its associated state.  If the user is current
        // then leave it alone since we might be connected to a route or want to query
        // the same route information again soon.
        if (userRecord.mUserId != mCurrentUserId
                && userRecord.mManagerRecords.isEmpty()) {
            if (DEBUG) {
                Slog.d(TAG, userRecord + ": Disposed");
            }
            mUserRecords.remove(userRecord.mUserId);
            // Note: User already stopped (by switchUser) so no need to send stop message here.
        }
    }

    final class UserRecord {
        public final int mUserId;
        final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();
        final UserHandler mHandler;

        UserRecord(int userId) {
            mUserId = userId;
            mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
        }
    }

    final class ManagerRecord implements IBinder.DeathRecipient {
        public final UserRecord mUserRecord;
        public final IMediaRouter2ManagerClient mClient;
        public final int mUid;
        public final int mPid;
        public final String mPackageName;
        public final boolean mTrusted;

        ManagerRecord(UserRecord userRecord, IMediaRouter2ManagerClient client,
                int uid, int pid, String packageName, boolean trusted) {
            mUserRecord = userRecord;
            mClient = client;
            mUid = uid;
            mPid = pid;
            mPackageName = packageName;
            mTrusted = trusted;
        }

        public void dispose() {
            mClient.asBinder().unlinkToDeath(this, 0);
        }

        @Override
        public void binderDied() {
            clientDied(this);
        }

        public void dump(PrintWriter pw, String prefix) {
            pw.println(prefix + this);

            final String indent = prefix + "  ";
            pw.println(indent + "mTrusted=" + mTrusted);
        }

        @Override
        public String toString() {
            return "Client " + mPackageName + " (pid " + mPid + ")";
        }
    }

    static final class UserHandler extends Handler implements
            MediaRoute2ProviderWatcher.Callback,
            MediaRoute2ProviderProxy.Callback {

        //TODO: Should be rearranged
        public static final int MSG_START = 1;
        public static final int MSG_STOP = 2;

        private static final int MSG_UPDATE_CLIENT_STATE = 8;

        private static final int MSG_SELECT_REMOTE_ROUTE = 10;
        private static final int MSG_UPDATE_CLIENT_USAGE = 11;

        private final WeakReference<MediaRouter2ServiceImpl> mServiceRef;
        private final UserRecord mUserRecord;
        private final MediaRoute2ProviderWatcher mWatcher;
        private final ArrayList<IMediaRouter2ManagerClient> mTempManagers = new ArrayList<>();

        private final ArrayList<MediaRoute2ProviderProxy> mMediaProviders =
                new ArrayList<>();

        private boolean mRunning;
        private boolean mClientStateUpdateScheduled;

        UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) {
            mServiceRef = new WeakReference<>(service);
            mUserRecord = userRecord;
            mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this,
                    this, mUserRecord.mUserId);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_START: {
                    start();
                    break;
                }
                case MSG_STOP: {
                    stop();
                    break;
                }
                case MSG_UPDATE_CLIENT_STATE: {
                    updateClientState();
                    break;
                }
                case MSG_SELECT_REMOTE_ROUTE: {
                    Pair<Integer, String> obj = (Pair<Integer, String>) msg.obj;
                    selectRemoteRoute(obj.first, obj.second);
                    break;
                }
                case MSG_UPDATE_CLIENT_USAGE: {
                    updateClientUsage();
                    break;
                }
            }
        }

        private void start() {
            if (!mRunning) {
                mRunning = true;
                mWatcher.start();
            }
        }

        private void stop() {
            if (mRunning) {
                mRunning = false;
                //TODO: may unselect routes
                mWatcher.stop(); // also stops all providers
            }
        }

        @Override
        public void addProvider(MediaRoute2ProviderProxy provider) {
            provider.setCallback(this);
            mMediaProviders.add(provider);
        }

        @Override
        public void removeProvider(MediaRoute2ProviderProxy provider) {
            mMediaProviders.remove(provider);
        }

        @Override
        public void onProviderStateChanged(MediaRoute2ProviderProxy provider) {
            updateProvider(provider);
        }

        private void updateProvider(MediaRoute2ProviderProxy provider) {
            scheduleUpdateClientState();
        }


        private void selectRemoteRoute(int uid, String routeId) {
            if (routeId != null) {
                final int providerCount = mMediaProviders.size();

                //TODO: should find proper provider (currently assumes a single provider)
                for (int i = 0; i < providerCount; ++i) {
                    mMediaProviders.get(i).setSelectedRoute(uid, routeId);
                }
            }
        }

        private void scheduleUpdateClientState() {
            if (!mClientStateUpdateScheduled) {
                mClientStateUpdateScheduled = true;
                sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
            }
        }

        private void updateClientState() {
            mClientStateUpdateScheduled = false;

            MediaRouter2ServiceImpl service = mServiceRef.get();
            if (service == null) {
                return;
            }

            //TODO: send provider info
            int selectedUid = 0;
            String selectedRouteId = null;
            final int mediaCount = mMediaProviders.size();
            for (int i = 0; i < mediaCount; i++) {
                selectedUid = mMediaProviders.get(i).mSelectedUid;
                selectedRouteId = mMediaProviders.get(i).mSelectedRouteId;
            }

            try {
                synchronized (service.mLock) {
                    final int count = mUserRecord.mManagerRecords.size();
                    for (int i = 0; i < count; i++) {
                        mTempManagers.add(mUserRecord.mManagerRecords.get(i).mClient);
                    }
                }

                //TODO: Call proper callbacks when provider descriptor is implemented.
                final int count = mTempManagers.size();
                for (int i = 0; i < count; i++) {
                    try {
                        mTempManagers.get(i).onRouteSelected(selectedUid, selectedRouteId);
                    } catch (RemoteException ex) {
                        Slog.w(TAG, "Failed to call onStateChanged. Manager probably died.", ex);
                    }
                }
            } finally {
                mTempManagers.clear();
            }
        }

        private void updateClientUsage() {
            //TODO: merge these code to updateClientState()

//            List<IMediaRouter2ManagerClient> managers = new ArrayList<>();
//            synchronized (mService.mLock) {
//                final int count = mUserRecord.mManagerRecords.size();
//                for (int i = 0; i < count; i++) {
//                    managers.add(mUserRecord.mManagerRecords.get(i).mClient);
//                }
//            }
//            final int count = managers.size();
//            for (int i = 0; i < count; i++) {
//                try {
//                    managers.get(i).onControlCategoriesChanged(clientRecord.mUid,
//                            clientRecord.mControlCategories);
//                } catch (RemoteException ex) {
//                    Slog.w(TAG, "Failed to call onControlCategoriesChanged. "
//                            + "Manager probably died.", ex);
//                }
//            }
        }
    }
}
+10 −268

File changed.

Preview size limit exceeded, changes collapsed.