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.MediaRoute2ProviderService.Reason; 25 import android.media.MediaRouter2; 26 import android.media.MediaRouter2Utils; 27 import android.media.RouteDiscoveryPreference; 28 import android.media.RoutingSessionInfo; 29 import android.os.Bundle; 30 import android.os.UserHandle; 31 32 import com.android.internal.annotations.GuardedBy; 33 34 import java.io.PrintWriter; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Objects; 38 import java.util.Set; 39 40 abstract class MediaRoute2Provider { 41 final ComponentName mComponentName; 42 final String mUniqueId; 43 final Object mLock = new Object(); 44 45 Callback mCallback; 46 public final boolean mIsSystemRouteProvider; 47 private volatile MediaRoute2ProviderInfo mProviderInfo; 48 49 @GuardedBy("mLock") 50 final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>(); 51 MediaRoute2Provider(@onNull ComponentName componentName, boolean isSystemRouteProvider)52 MediaRoute2Provider(@NonNull ComponentName componentName, boolean isSystemRouteProvider) { 53 mComponentName = Objects.requireNonNull(componentName, "Component name must not be null."); 54 mUniqueId = componentName.flattenToShortString(); 55 mIsSystemRouteProvider = isSystemRouteProvider; 56 } 57 setCallback(Callback callback)58 public void setCallback(Callback callback) { 59 mCallback = callback; 60 } 61 requestCreateSession( long requestId, String packageName, String routeOriginalId, @Nullable Bundle sessionHints, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)62 public abstract void requestCreateSession( 63 long requestId, 64 String packageName, 65 String routeOriginalId, 66 @Nullable Bundle sessionHints, 67 @RoutingSessionInfo.TransferReason int transferReason, 68 @NonNull UserHandle transferInitiatorUserHandle, 69 @NonNull String transferInitiatorPackageName); 70 releaseSession(long requestId, String sessionId)71 public abstract void releaseSession(long requestId, String sessionId); 72 updateDiscoveryPreference( Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference)73 public abstract void updateDiscoveryPreference( 74 Set<String> activelyScanningPackages, RouteDiscoveryPreference discoveryPreference); 75 selectRoute(long requestId, String sessionId, String routeId)76 public abstract void selectRoute(long requestId, String sessionId, String routeId); deselectRoute(long requestId, String sessionId, String routeId)77 public abstract void deselectRoute(long requestId, String sessionId, String routeId); 78 transferToRoute( long requestId, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName, String sessionOriginalId, String routeOriginalId, @RoutingSessionInfo.TransferReason int transferReason)79 public abstract void transferToRoute( 80 long requestId, 81 @NonNull UserHandle transferInitiatorUserHandle, 82 @NonNull String transferInitiatorPackageName, 83 String sessionOriginalId, 84 String routeOriginalId, 85 @RoutingSessionInfo.TransferReason int transferReason); 86 setRouteVolume(long requestId, String routeOriginalId, int volume)87 public abstract void setRouteVolume(long requestId, String routeOriginalId, int volume); 88 setSessionVolume(long requestId, String sessionOriginalId, int volume)89 public abstract void setSessionVolume(long requestId, String sessionOriginalId, int volume); 90 prepareReleaseSession(@onNull String sessionUniqueId)91 public abstract void prepareReleaseSession(@NonNull String sessionUniqueId); 92 93 @NonNull getUniqueId()94 public String getUniqueId() { 95 return mUniqueId; 96 } 97 98 @Nullable getProviderInfo()99 public MediaRoute2ProviderInfo getProviderInfo() { 100 return mProviderInfo; 101 } 102 103 @NonNull getSessionInfos()104 public List<RoutingSessionInfo> getSessionInfos() { 105 synchronized (mLock) { 106 return new ArrayList<>(mSessionInfos); 107 } 108 } 109 setProviderState(MediaRoute2ProviderInfo providerInfo)110 void setProviderState(MediaRoute2ProviderInfo providerInfo) { 111 if (providerInfo == null) { 112 mProviderInfo = null; 113 } else { 114 mProviderInfo = new MediaRoute2ProviderInfo.Builder(providerInfo) 115 .setUniqueId(mComponentName.getPackageName(), mUniqueId) 116 .setSystemRouteProvider(mIsSystemRouteProvider) 117 .build(); 118 } 119 } 120 notifyProviderState()121 void notifyProviderState() { 122 if (mCallback != null) { 123 mCallback.onProviderStateChanged(this); 124 } 125 } 126 127 /** Calls {@link Callback#onRequestFailed} with the given id and reason. */ notifyRequestFailed(long requestId, @Reason int reason)128 protected void notifyRequestFailed(long requestId, @Reason int reason) { 129 if (mCallback != null) { 130 mCallback.onRequestFailed(/* provider= */ this, requestId, reason); 131 } 132 } 133 setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo)134 void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) { 135 setProviderState(providerInfo); 136 notifyProviderState(); 137 } 138 hasComponentName(String packageName, String className)139 public boolean hasComponentName(String packageName, String className) { 140 return mComponentName.getPackageName().equals(packageName) 141 && mComponentName.getClassName().equals(className); 142 } 143 dump(PrintWriter pw, String prefix)144 public void dump(PrintWriter pw, String prefix) { 145 pw.println(prefix + getDebugString()); 146 prefix += " "; 147 148 if (mProviderInfo == null) { 149 pw.println(prefix + "<provider info not received, yet>"); 150 } else if (mProviderInfo.getRoutes().isEmpty()) { 151 pw.println(prefix + "<provider info has no routes>"); 152 } else { 153 for (MediaRoute2Info route : mProviderInfo.getRoutes()) { 154 pw.printf("%s%s | %s\n", prefix, route.getId(), route.getName()); 155 } 156 } 157 158 pw.println(prefix + "Active routing sessions:"); 159 synchronized (mLock) { 160 if (mSessionInfos.isEmpty()) { 161 pw.println(prefix + " <no active routing sessions>"); 162 } else { 163 for (RoutingSessionInfo routingSessionInfo : mSessionInfos) { 164 routingSessionInfo.dump(pw, prefix + " "); 165 } 166 } 167 } 168 } 169 170 @Override toString()171 public String toString() { 172 return getDebugString(); 173 } 174 175 /** Returns a human-readable string describing the instance, for debugging purposes. */ getDebugString()176 protected abstract String getDebugString(); 177 178 public interface Callback { onProviderStateChanged(@ullable MediaRoute2Provider provider)179 void onProviderStateChanged(@Nullable MediaRoute2Provider provider); onSessionCreated(@onNull MediaRoute2Provider provider, long requestId, @Nullable RoutingSessionInfo sessionInfo)180 void onSessionCreated(@NonNull MediaRoute2Provider provider, 181 long requestId, @Nullable RoutingSessionInfo sessionInfo); 182 183 /** 184 * Called when there's a session info change. 185 * 186 * <p>If the provided {@code sessionInfo} has a null {@link 187 * RoutingSessionInfo#getClientPackageName()}, that means that it's applicable to all 188 * packages. We call this type of routing session "global". This is typically used for 189 * system provided {@link RoutingSessionInfo}. However, some applications may be exempted 190 * from the global routing sessions, because their media is being routed using a session 191 * different from the global routing session. 192 * 193 * @param provider The provider that owns the session that changed. 194 * @param sessionInfo The new {@link RoutingSessionInfo}. 195 * @param packageNamesWithRoutingSessionOverrides The names of packages that are not 196 * affected by global session changes. This set may only be non-empty when the {@code 197 * sessionInfo} is for the global session, and therefore has no {@link 198 * RoutingSessionInfo#getClientPackageName()}. 199 */ onSessionUpdated( @onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo, Set<String> packageNamesWithRoutingSessionOverrides)200 void onSessionUpdated( 201 @NonNull MediaRoute2Provider provider, 202 @NonNull RoutingSessionInfo sessionInfo, 203 Set<String> packageNamesWithRoutingSessionOverrides); 204 onSessionReleased(@onNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo)205 void onSessionReleased(@NonNull MediaRoute2Provider provider, 206 @NonNull RoutingSessionInfo sessionInfo); 207 onRequestFailed( @onNull MediaRoute2Provider provider, long requestId, @Reason int reason)208 void onRequestFailed( 209 @NonNull MediaRoute2Provider provider, long requestId, @Reason int reason); 210 } 211 212 /** 213 * Holds session creation or transfer initiation information for a transfer in flight. 214 * 215 * <p>The initiator app is typically also the {@link RoutingSessionInfo#getClientPackageName() 216 * client app}, with the exception of the {@link MediaRouter2#getSystemController() system 217 * routing session} which is exceptional in that it's shared among all apps. 218 * 219 * <p>For the system routing session, the initiator app is the one that programmatically 220 * triggered the transfer (for example, via {@link MediaRouter2#transferTo}), or the target app 221 * of the proxy router that did the transfer. 222 * 223 * @see MediaRouter2.RoutingController#wasTransferInitiatedBySelf() 224 * @see RoutingSessionInfo#getTransferInitiatorPackageName() 225 * @see RoutingSessionInfo#getTransferInitiatorUserHandle() 226 */ 227 protected static class SessionCreationOrTransferRequest { 228 229 /** 230 * The id of the request, or {@link 231 * android.media.MediaRoute2ProviderService#REQUEST_ID_NONE} if unknown. 232 */ 233 public final long mRequestId; 234 235 /** The {@link MediaRoute2Info#getOriginalId()} original id} of the target route. */ 236 @NonNull public final String mTargetOriginalRouteId; 237 238 @RoutingSessionInfo.TransferReason public final int mTransferReason; 239 240 /** The {@link android.os.UserHandle} on which the initiator app is running. */ 241 @NonNull public final UserHandle mTransferInitiatorUserHandle; 242 243 @NonNull public final String mTransferInitiatorPackageName; 244 SessionCreationOrTransferRequest( long requestId, @NonNull String targetOriginalRouteId, @RoutingSessionInfo.TransferReason int transferReason, @NonNull UserHandle transferInitiatorUserHandle, @NonNull String transferInitiatorPackageName)245 SessionCreationOrTransferRequest( 246 long requestId, 247 @NonNull String targetOriginalRouteId, 248 @RoutingSessionInfo.TransferReason int transferReason, 249 @NonNull UserHandle transferInitiatorUserHandle, 250 @NonNull String transferInitiatorPackageName) { 251 mRequestId = requestId; 252 mTargetOriginalRouteId = targetOriginalRouteId; 253 mTransferReason = transferReason; 254 mTransferInitiatorUserHandle = transferInitiatorUserHandle; 255 mTransferInitiatorPackageName = transferInitiatorPackageName; 256 } 257 isTargetRoute(@ullable MediaRoute2Info route2Info)258 public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { 259 return route2Info != null && mTargetOriginalRouteId.equals(route2Info.getOriginalId()); 260 } 261 262 /** 263 * Returns whether the given list of {@link MediaRoute2Info#getOriginalId() original ids} 264 * contains the {@link #mTargetOriginalRouteId target route id}. 265 */ isTargetRouteIdInRouteOriginalIdList( @onNull List<String> originalRouteIdList)266 public boolean isTargetRouteIdInRouteOriginalIdList( 267 @NonNull List<String> originalRouteIdList) { 268 return originalRouteIdList.stream().anyMatch(mTargetOriginalRouteId::equals); 269 } 270 271 /** 272 * Returns whether the given list of {@link MediaRoute2Info#getId() unique ids} contains the 273 * {@link #mTargetOriginalRouteId target route id}. 274 */ isTargetRouteIdInRouteUniqueIdList(@onNull List<String> uniqueRouteIdList)275 public boolean isTargetRouteIdInRouteUniqueIdList(@NonNull List<String> uniqueRouteIdList) { 276 return uniqueRouteIdList.stream() 277 .map(MediaRouter2Utils::getOriginalId) 278 .anyMatch(mTargetOriginalRouteId::equals); 279 } 280 } 281 } 282