1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.media; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.media.MediaRoute2Info; 23 import android.media.MediaRoute2ProviderInfo; 24 import android.media.MediaRouter2; 25 import android.media.MediaRouter2Utils; 26 import android.media.RouteDiscoveryPreference; 27 import android.media.RoutingSessionInfo; 28 import android.os.Bundle; 29 import android.os.UserHandle; 30 31 import com.android.internal.annotations.GuardedBy; 32 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.Objects; 37 import java.util.Set; 38 39 abstract class MediaRoute2Provider { 40 final ComponentName mComponentName; 41 final String mUniqueId; 42 final Object mLock = new Object(); 43 44 Callback mCallback; 45 boolean mIsSystemRouteProvider; 46 private volatile MediaRoute2ProviderInfo mProviderInfo; 47 48 @GuardedBy("mLock") 49 final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>(); 50 MediaRoute2Provider(@onNull ComponentName componentName)51 MediaRoute2Provider(@NonNull ComponentName componentName) { 52 mComponentName = Objects.requireNonNull(componentName, "Component name must not be null."); 53 mUniqueId = componentName.flattenToShortString(); 54 } 55 setCallback(Callback callback)56 public void setCallback(Callback callback) { 57 mCallback = callback; 58 } 59 requestCreateSession( long requestId, String packageName, String routeOriginalId, @Nullable Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)60 public abstract void requestCreateSession( 61 long requestId, 62 String packageName, 63 String routeOriginalId, 64 @Nullable Bundle sessionHints, 65 @RoutingSessionInfo.TransferReason int transferReason, 66 @NonNull UserHandle transferInitiatorUserHandle, 67 @NonNull String transferInitiatorPackageName); 68 releaseSession(long requestId, String sessionId)69 public abstract void releaseSession(long requestId, String sessionId); 70 updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)71 public abstract void updateDiscoveryPreference( 72 Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference); 73 selectRoute(long requestId, String sessionId, String routeId)74 public abstract void selectRoute(long requestId, String sessionId, String routeId); deselectRoute(long requestId, String sessionId, String routeId)75 public abstract void deselectRoute(long requestId, String sessionId, String routeId); 76 transferToRoute( long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, String sessionOriginalId, String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason)77 public abstract void transferToRoute( 78 long requestId, 79 @NonNull UserHandle transferInitiatorUserHandle, 80 @NonNull String transferInitiatorPackageName, 81 String sessionOriginalId, 82 String routeOriginalId, 83 @RoutingSessionInfo.TransferReason int transferReason); 84 setRouteVolume(long requestId, String routeOriginalId, int volume)85 public abstract void setRouteVolume(long requestId, String routeOriginalId, int volume); 86 setSessionVolume(long requestId, String sessionOriginalId, int volume)87 public abstract void setSessionVolume(long requestId, String sessionOriginalId, int volume); 88 prepareReleaseSession(@onNull String sessionUniqueId)89 public abstract void prepareReleaseSession(@NonNull String sessionUniqueId); 90 91 @NonNull getUniqueId()92 public String getUniqueId() { 93 return mUniqueId; 94 } 95 96 @Nullable getProviderInfo()97 public MediaRoute2ProviderInfo getProviderInfo() { 98 return mProviderInfo; 99 } 100 101 @NonNull getSessionInfos()102 public List<RoutingSessionInfo> getSessionInfos() { 103 synchronized (mLock) { 104 return new ArrayList<>(mSessionInfos); 105 } 106 } 107 setProviderState(MediaRoute2ProviderInfo providerInfo)108 void setProviderState(MediaRoute2ProviderInfo providerInfo) { 109 if (providerInfo == null) { 110 mProviderInfo = null; 111 } else { 112 mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo) 113 .setUniqueId(mComponentName.getPackageName(), mUniqueId) 114 .setSystemRouteProvider(mIsSystemRouteProvider) 115 .build(); 116 } 117 } 118 notifyProviderState()119 void notifyProviderState() { 120 if (mCallback != null) { 121 mCallback.onProviderStateChanged(this); 122 } 123 } 124 setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo)125 void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) { 126 setProviderState(providerInfo); 127 notifyProviderState(); 128 } 129 hasComponentName(String packageName, String className)130 public boolean hasComponentName(String packageName, String className) { 131 return mComponentName.getPackageName().equals(packageName) 132 && mComponentName.getClassName().equals(className); 133 } 134 dump(PrintWriter pw, String prefix)135 public void dump(PrintWriter pw, String prefix) { 136 pw.println(prefix + getDebugString()); 137 prefix += " "; 138 139 if (mProviderInfo == null) { 140 pw.println(prefix + "<provider info not received, yet>"); 141 } else if (mProviderInfo.getRoutes().isEmpty()) { 142 pw.println(prefix + "<provider info has no routes>"); 143 } else { 144 for (MediaRoute2Info route : mProviderInfo.getRoutes()) { 145 pw.printf("%s%s | %s\n", prefix, route.getId(), route.getName()); 146 } 147 } 148 149 pw.println(prefix + "Active routing sessions:"); 150 synchronized (mLock) { 151 if (mSessionInfos.isEmpty()) { 152 pw.println(prefix + " <no active routing sessions>"); 153 } else { 154 for (RoutingSessionInfo routingSessionInfo : mSessionInfos) { 155 routingSessionInfo.dump(pw, prefix + " "); 156 } 157 } 158 } 159 } 160 161 @Override toString()162 public String toString() { 163 return getDebugString(); 164 } 165 166 /** Returns a human-readable string describing the instance, for debugging purposes. */ getDebugString()167 protected abstract String getDebugString(); 168 169 public interface Callback { onProviderStateChanged(@ullable MediaRoute2Provider provider)170 void onProviderStateChanged(@Nullable MediaRoute2Provider provider); onSessionCreated(@onNull MediaRoute2Provider provider, long requestId, @Nullable RoutingSessionInfo sessionInfo)171 void onSessionCreated(@NonNull MediaRoute2Provider provider, 172 long requestId, @Nullable RoutingSessionInfo sessionInfo); onSessionUpdated(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)173 void onSessionUpdated(@NonNull MediaRoute2Provider provider, 174 @NonNull RoutingSessionInfo sessionInfo); onSessionReleased(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)175 void onSessionReleased(@NonNull MediaRoute2Provider provider, 176 @NonNull RoutingSessionInfo sessionInfo); onRequestFailed(@onNull MediaRoute2Provider provider, long requestId, int reason)177 void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason); 178 } 179 180 /** 181 * Holds session creation or transfer initiation information for a transfer in flight. 182 * 183 * <p>The initiator app is typically also the {@link RoutingSessionInfo#getClientPackageName() 184 * client app}, with the exception of the {@link MediaRouter2#getSystemController() system 185 * routing session} which is exceptional in that it's shared among all apps. 186 * 187 * <p>For the system routing session, the initiator app is the one that programmatically 188 * triggered the transfer (for example, via {@link MediaRouter2#transferTo}), or the target app 189 * of the proxy router that did the transfer. 190 * 191 * @see MediaRouter2.RoutingController#wasTransferInitiatedBySelf() 192 * @see RoutingSessionInfo#getTransferInitiatorPackageName() 193 * @see RoutingSessionInfo#getTransferInitiatorUserHandle() 194 */ 195 protected static class SessionCreationOrTransferRequest { 196 197 /** 198 * The id of the request, or {@link 199 * android.media.MediaRoute2ProviderService#REQUEST_ID_NONE} if unknown. 200 */ 201 public final long mRequestId; 202 203 /** The {@link MediaRoute2Info#getOriginalId()} original id} of the target route. */ 204 @NonNull public final String mTargetOriginalRouteId; 205 206 @RoutingSessionInfo.TransferReason public final int mTransferReason; 207 208 /** The {@link android.os.UserHandle} on which the initiator app is running. */ 209 @NonNull public final UserHandle mTransferInitiatorUserHandle; 210 211 @NonNull public final String mTransferInitiatorPackageName; 212 SessionCreationOrTransferRequest( long requestId, @NonNull String targetOriginalRouteId, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)213 SessionCreationOrTransferRequest( 214 long requestId, 215 @NonNull String targetOriginalRouteId, 216 @RoutingSessionInfo.TransferReason int transferReason, 217 @NonNull UserHandle transferInitiatorUserHandle, 218 @NonNull String transferInitiatorPackageName) { 219 mRequestId = requestId; 220 mTargetOriginalRouteId = targetOriginalRouteId; 221 mTransferReason = transferReason; 222 mTransferInitiatorUserHandle = transferInitiatorUserHandle; 223 mTransferInitiatorPackageName = transferInitiatorPackageName; 224 } 225 isTargetRoute(@ullable MediaRoute2Info route2Info)226 public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { 227 return route2Info != null && mTargetOriginalRouteId.equals(route2Info.getOriginalId()); 228 } 229 230 /** 231 * Returns whether the given list of {@link MediaRoute2Info#getOriginalId() original ids} 232 * contains the {@link #mTargetOriginalRouteId target route id}. 233 */ isTargetRouteIdInRouteOriginalIdList( @onNull List<String> originalRouteIdList)234 public boolean isTargetRouteIdInRouteOriginalIdList( 235 @NonNull List<String> originalRouteIdList) { 236 return originalRouteIdList.stream().anyMatch(mTargetOriginalRouteId::equals); 237 } 238 239 /** 240 * Returns whether the given list of {@link MediaRoute2Info#getId() unique ids} contains the 241 * {@link #mTargetOriginalRouteId target route id}. 242 */ isTargetRouteIdInRouteUniqueIdList(@onNull List<String> uniqueRouteIdList)243 public boolean isTargetRouteIdInRouteUniqueIdList(@NonNull List<String> uniqueRouteIdList) { 244 return uniqueRouteIdList.stream() 245 .map(MediaRouter2Utils::getOriginalId) 246 .anyMatch(mTargetOriginalRouteId::equals); 247 } 248 } 249 } 250