1 /* 2 * Copyright 2018 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.media; 18 19 import static android.media.SessionToken2.TYPE_SESSION; 20 import static android.media.SessionToken2.TYPE_SESSION_SERVICE; 21 import static android.media.SessionToken2.TYPE_LIBRARY_SERVICE; 22 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.content.pm.ResolveInfo; 28 import android.media.MediaLibraryService2; 29 import android.media.MediaSessionService2; 30 import android.media.SessionToken2; 31 import android.media.SessionToken2.TokenType; 32 import android.media.update.SessionToken2Provider; 33 import android.os.Bundle; 34 import android.os.IBinder; 35 import android.text.TextUtils; 36 37 import java.util.List; 38 39 public class SessionToken2Impl implements SessionToken2Provider { 40 private static final String KEY_UID = "android.media.token.uid"; 41 private static final String KEY_TYPE = "android.media.token.type"; 42 private static final String KEY_PACKAGE_NAME = "android.media.token.package_name"; 43 private static final String KEY_SERVICE_NAME = "android.media.token.service_name"; 44 private static final String KEY_ID = "android.media.token.id"; 45 private static final String KEY_SESSION_BINDER = "android.media.token.session_binder"; 46 47 private final SessionToken2 mInstance; 48 private final int mUid; 49 private final @TokenType int mType; 50 private final String mPackageName; 51 private final String mServiceName; 52 private final String mId; 53 private final IMediaSession2 mSessionBinder; 54 55 /** 56 * Public constructor for the legacy support (i.e. browser can try connecting to any browser 57 * service if it knows the service name) 58 */ SessionToken2Impl(Context context, SessionToken2 instance, String packageName, String serviceName, int uid)59 public SessionToken2Impl(Context context, SessionToken2 instance, 60 String packageName, String serviceName, int uid) { 61 if (TextUtils.isEmpty(packageName)) { 62 throw new IllegalArgumentException("packageName shouldn't be empty"); 63 } 64 if (TextUtils.isEmpty(serviceName)) { 65 throw new IllegalArgumentException("serviceName shouldn't be empty"); 66 } 67 mInstance = instance; 68 // Calculate uid if it's not specified. 69 final PackageManager manager = context.getPackageManager(); 70 if (uid < 0) { 71 try { 72 uid = manager.getPackageUid(packageName, 0); 73 } catch (NameNotFoundException e) { 74 throw new IllegalArgumentException("Cannot find package " + packageName); 75 } 76 } 77 mUid = uid; 78 79 // Infer id and type from package name and service name 80 // TODO(jaewan): Handle multi-user. 81 String id = getSessionIdFromService(manager, MediaLibraryService2.SERVICE_INTERFACE, 82 packageName, serviceName); 83 if (id != null) { 84 mId = id; 85 mType = TYPE_LIBRARY_SERVICE; 86 } else { 87 // retry with session service 88 mId = getSessionIdFromService(manager, MediaSessionService2.SERVICE_INTERFACE, 89 packageName, serviceName); 90 mType = TYPE_SESSION_SERVICE; 91 } 92 if (mId == null) { 93 throw new IllegalArgumentException("service " + serviceName + " doesn't implement" 94 + " session service nor library service. Use service's full name."); 95 } 96 mPackageName = packageName; 97 mServiceName = serviceName; 98 mSessionBinder = null; 99 } 100 SessionToken2Impl(int uid, int type, String packageName, String serviceName, String id, IMediaSession2 sessionBinder)101 SessionToken2Impl(int uid, int type, String packageName, String serviceName, String id, 102 IMediaSession2 sessionBinder) { 103 // TODO(jaewan): Add sanity check (b/73863865) 104 mUid = uid; 105 mType = type; 106 mPackageName = packageName; 107 mServiceName = serviceName; 108 mId = id; 109 mSessionBinder = sessionBinder; 110 mInstance = new SessionToken2(this); 111 } 112 getSessionIdFromService(PackageManager manager, String serviceInterface, String packageName, String serviceName)113 private static String getSessionIdFromService(PackageManager manager, String serviceInterface, 114 String packageName, String serviceName) { 115 Intent serviceIntent = new Intent(serviceInterface); 116 serviceIntent.setPackage(packageName); 117 // Use queryIntentServices to find services with MediaLibraryService2.SERVICE_INTERFACE. 118 // We cannot use resolveService with intent specified class name, because resolveService 119 // ignores actions if Intent.setClassName() is specified. 120 List<ResolveInfo> list = manager.queryIntentServices( 121 serviceIntent, PackageManager.GET_META_DATA); 122 if (list != null) { 123 for (int i = 0; i < list.size(); i++) { 124 ResolveInfo resolveInfo = list.get(i); 125 if (resolveInfo == null || resolveInfo.serviceInfo == null) { 126 continue; 127 } 128 if (TextUtils.equals(resolveInfo.serviceInfo.name, serviceName)) { 129 return getSessionId(resolveInfo); 130 } 131 } 132 } 133 return null; 134 } 135 getSessionId(ResolveInfo resolveInfo)136 public static String getSessionId(ResolveInfo resolveInfo) { 137 if (resolveInfo == null || resolveInfo.serviceInfo == null) { 138 return null; 139 } else if (resolveInfo.serviceInfo.metaData == null) { 140 return ""; 141 } else { 142 return resolveInfo.serviceInfo.metaData.getString( 143 MediaSessionService2.SERVICE_META_DATA, ""); 144 } 145 } 146 getInstance()147 public SessionToken2 getInstance() { 148 return mInstance; 149 } 150 151 @Override getPackageName_impl()152 public String getPackageName_impl() { 153 return mPackageName; 154 } 155 156 @Override getUid_impl()157 public int getUid_impl() { 158 return mUid; 159 } 160 161 @Override getId_imp()162 public String getId_imp() { 163 return mId; 164 } 165 166 @Override getType_impl()167 public int getType_impl() { 168 return mType; 169 } 170 getServiceName()171 String getServiceName() { 172 return mServiceName; 173 } 174 getSessionBinder()175 IMediaSession2 getSessionBinder() { 176 return mSessionBinder; 177 } 178 fromBundle_impl(Bundle bundle)179 public static SessionToken2 fromBundle_impl(Bundle bundle) { 180 if (bundle == null) { 181 return null; 182 } 183 final int uid = bundle.getInt(KEY_UID); 184 final @TokenType int type = bundle.getInt(KEY_TYPE, -1); 185 final String packageName = bundle.getString(KEY_PACKAGE_NAME); 186 final String serviceName = bundle.getString(KEY_SERVICE_NAME); 187 final String id = bundle.getString(KEY_ID); 188 final IBinder sessionBinder = bundle.getBinder(KEY_SESSION_BINDER); 189 190 // Sanity check. 191 switch (type) { 192 case TYPE_SESSION: 193 if (sessionBinder == null) { 194 throw new IllegalArgumentException("Unexpected sessionBinder for session," 195 + " binder=" + sessionBinder); 196 } 197 break; 198 case TYPE_SESSION_SERVICE: 199 case TYPE_LIBRARY_SERVICE: 200 if (TextUtils.isEmpty(serviceName)) { 201 throw new IllegalArgumentException("Session service needs service name"); 202 } 203 break; 204 default: 205 throw new IllegalArgumentException("Invalid type"); 206 } 207 if (TextUtils.isEmpty(packageName) || id == null) { 208 throw new IllegalArgumentException("Package name nor ID cannot be null."); 209 } 210 return new SessionToken2Impl(uid, type, packageName, serviceName, id, 211 sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null) 212 .getInstance(); 213 } 214 215 @Override toBundle_impl()216 public Bundle toBundle_impl() { 217 Bundle bundle = new Bundle(); 218 bundle.putInt(KEY_UID, mUid); 219 bundle.putString(KEY_PACKAGE_NAME, mPackageName); 220 bundle.putString(KEY_SERVICE_NAME, mServiceName); 221 bundle.putString(KEY_ID, mId); 222 bundle.putInt(KEY_TYPE, mType); 223 bundle.putBinder(KEY_SESSION_BINDER, 224 mSessionBinder != null ? mSessionBinder.asBinder() : null); 225 return bundle; 226 } 227 228 @Override hashCode_impl()229 public int hashCode_impl() { 230 final int prime = 31; 231 return mType 232 + prime * (mUid 233 + prime * (mPackageName.hashCode() 234 + prime * (mId.hashCode() 235 + prime * (mServiceName != null ? mServiceName.hashCode() : 0)))); 236 } 237 238 @Override equals_impl(Object obj)239 public boolean equals_impl(Object obj) { 240 if (!(obj instanceof SessionToken2)) { 241 return false; 242 } 243 SessionToken2Impl other = from((SessionToken2) obj); 244 return mUid == other.mUid 245 && TextUtils.equals(mPackageName, other.mPackageName) 246 && TextUtils.equals(mServiceName, other.mServiceName) 247 && TextUtils.equals(mId, other.mId) 248 && mType == other.mType; 249 } 250 251 @Override toString_impl()252 public String toString_impl() { 253 return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType 254 + " service=" + mServiceName + " binder=" + mSessionBinder + "}"; 255 } 256 from(SessionToken2 token)257 static SessionToken2Impl from(SessionToken2 token) { 258 return ((SessionToken2Impl) token.getProvider()); 259 } 260 } 261