• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.media;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.os.Bundle;
27 import android.support.v4.media.session.MediaSessionCompat;
28 import android.text.TextUtils;
29 
30 import androidx.annotation.IntDef;
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.RestrictTo;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.List;
38 
39 /**
40  * Represents an ongoing {@link MediaSession2} or a {@link MediaSessionService2}.
41  * If it's representing a session service, it may not be ongoing.
42  * <p>
43  * This may be passed to apps by the session owner to allow them to create a
44  * {@link MediaController2} to communicate with the session.
45  * <p>
46  * It can be also obtained by {@link MediaSessionManager}.
47  */
48 // New version of MediaSession.Token for following reasons
49 //   - Stop implementing Parcelable for updatable support
50 //   - Represent session and library service (formerly browser service) in one class.
51 //     Previously MediaSession.Token was for session and ComponentName was for service.
52 public final class SessionToken2 {
53     /**
54      * @hide
55      */
56     @RestrictTo(LIBRARY_GROUP)
57     @Retention(RetentionPolicy.SOURCE)
58     @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
59     public @interface TokenType {
60     }
61 
62     /**
63      * Type for {@link MediaSession2}.
64      */
65     public static final int TYPE_SESSION = 0;
66 
67     /**
68      * Type for {@link MediaSessionService2}.
69      */
70     public static final int TYPE_SESSION_SERVICE = 1;
71 
72     /**
73      * Type for {@link MediaLibraryService2}.
74      */
75     public static final int TYPE_LIBRARY_SERVICE = 2;
76 
77     //private final SessionToken2Provider mProvider;
78 
79     // From the return value of android.os.Process.getUidForName(String) when error
80     private static final int UID_UNKNOWN = -1;
81 
82     private static final String KEY_UID = "android.media.token.uid";
83     private static final String KEY_TYPE = "android.media.token.type";
84     private static final String KEY_PACKAGE_NAME = "android.media.token.package_name";
85     private static final String KEY_SERVICE_NAME = "android.media.token.service_name";
86     private static final String KEY_ID = "android.media.token.id";
87     private static final String KEY_SESSION_TOKEN = "android.media.token.session_token";
88 
89     private final int mUid;
90     private final @TokenType int mType;
91     private final String mPackageName;
92     private final String mServiceName;
93     private final String mId;
94     private final MediaSessionCompat.Token mSessionCompatToken;
95     private final ComponentName mComponentName;
96 
97     /**
98      * Constructor for the token. You can only create token for session service or library service
99      * to use by {@link MediaController2} or {@link MediaBrowser2}.
100      *
101      * @param context The context.
102      * @param serviceComponent The component name of the media browser service.
103      */
SessionToken2(@onNull Context context, @NonNull ComponentName serviceComponent)104     public SessionToken2(@NonNull Context context, @NonNull ComponentName serviceComponent) {
105         this(context, serviceComponent, UID_UNKNOWN);
106     }
107 
108     /**
109      * Constructor for the token. You can only create token for session service or library service
110      * to use by {@link MediaController2} or {@link MediaBrowser2}.
111      *
112      * @param context The context.
113      * @param serviceComponent The component name of the media browser service.
114      * @param uid uid of the app.
115      * @hide
116      */
117     @RestrictTo(LIBRARY_GROUP)
SessionToken2(@onNull Context context, @NonNull ComponentName serviceComponent, int uid)118     public SessionToken2(@NonNull Context context, @NonNull ComponentName serviceComponent,
119             int uid) {
120         if (serviceComponent == null) {
121             throw new IllegalArgumentException("serviceComponent shouldn't be null");
122         }
123         mComponentName = serviceComponent;
124         mPackageName = serviceComponent.getPackageName();
125         mServiceName = serviceComponent.getClassName();
126         // Calculate uid if it's not specified.
127         final PackageManager manager = context.getPackageManager();
128         if (uid < 0) {
129             try {
130                 uid = manager.getApplicationInfo(mPackageName, 0).uid;
131             } catch (PackageManager.NameNotFoundException e) {
132                 throw new IllegalArgumentException("Cannot find package " + mPackageName);
133             }
134         }
135         mUid = uid;
136 
137         // Infer id and type from package name and service name
138         String id = getSessionIdFromService(manager, MediaLibraryService2.SERVICE_INTERFACE,
139                 serviceComponent);
140         if (id != null) {
141             mId = id;
142             mType = TYPE_LIBRARY_SERVICE;
143         } else {
144             // retry with session service
145             mId = getSessionIdFromService(manager, MediaSessionService2.SERVICE_INTERFACE,
146                     serviceComponent);
147             mType = TYPE_SESSION_SERVICE;
148         }
149         if (mId == null) {
150             throw new IllegalArgumentException("service " + mServiceName + " doesn't implement"
151                     + " session service nor library service. Use service's full name.");
152         }
153         mSessionCompatToken = null;
154     }
155 
156     /**
157      * @hide
158      */
159     @RestrictTo(LIBRARY_GROUP)
SessionToken2(int uid, int type, String packageName, String serviceName, String id, MediaSessionCompat.Token sessionCompatToken)160     SessionToken2(int uid, int type, String packageName, String serviceName,
161             String id, MediaSessionCompat.Token sessionCompatToken) {
162         mUid = uid;
163         mType = type;
164         mPackageName = packageName;
165         mServiceName = serviceName;
166         mComponentName = (mType == TYPE_SESSION) ? null
167                 : new ComponentName(packageName, serviceName);
168         mId = id;
169         mSessionCompatToken = sessionCompatToken;
170     }
171 
172     @Override
hashCode()173     public int hashCode() {
174         final int prime = 31;
175         return mType
176                 + prime * (mUid
177                 + prime * (mPackageName.hashCode()
178                 + prime * (mId.hashCode()
179                 + prime * (mServiceName != null ? mServiceName.hashCode() : 0))));
180     }
181 
182     @Override
equals(Object obj)183     public boolean equals(Object obj) {
184         if (!(obj instanceof SessionToken2)) {
185             return false;
186         }
187         SessionToken2 other = (SessionToken2) obj;
188         return mUid == other.mUid
189                 && TextUtils.equals(mPackageName, other.mPackageName)
190                 && TextUtils.equals(mServiceName, other.mServiceName)
191                 && TextUtils.equals(mId, other.mId)
192                 && mType == other.mType;
193     }
194 
195     @Override
toString()196     public String toString() {
197         return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType
198                 + " service=" + mServiceName + " sessionCompatToken=" + mSessionCompatToken + "}";
199     }
200 
201     /**
202      * @return uid of the session
203      */
getUid()204     public int getUid() {
205         return mUid;
206     }
207 
208     /**
209      * @return package name
210      */
getPackageName()211     public @NonNull String getPackageName() {
212         return mPackageName;
213     }
214 
215     /**
216      * @return service name. Can be {@code null} for TYPE_SESSION.
217      */
getServiceName()218     public @Nullable String getServiceName() {
219         return mServiceName;
220     }
221 
222     /**
223      * @hide
224      * @return component name of this session token. Can be null for TYPE_SESSION.
225      */
226     @RestrictTo(LIBRARY_GROUP)
getComponentName()227     public ComponentName getComponentName() {
228         return mComponentName;
229     }
230 
231     /**
232      * @return id
233      */
getId()234     public String getId() {
235         return mId;
236     }
237 
238     /**
239      * @return type of the token
240      * @see #TYPE_SESSION
241      * @see #TYPE_SESSION_SERVICE
242      * @see #TYPE_LIBRARY_SERVICE
243      */
getType()244     public @TokenType int getType() {
245         return mType;
246     }
247 
248     /**
249      * Create a token from the bundle, exported by {@link #toBundle()}.
250      *
251      * @param bundle
252      * @return
253      */
fromBundle(@onNull Bundle bundle)254     public static SessionToken2 fromBundle(@NonNull Bundle bundle) {
255         if (bundle == null) {
256             return null;
257         }
258         final int uid = bundle.getInt(KEY_UID);
259         final @TokenType int type = bundle.getInt(KEY_TYPE, -1);
260         final String packageName = bundle.getString(KEY_PACKAGE_NAME);
261         final String serviceName = bundle.getString(KEY_SERVICE_NAME);
262         final String id = bundle.getString(KEY_ID);
263         final MediaSessionCompat.Token token = bundle.getParcelable(KEY_SESSION_TOKEN);
264 
265         // Sanity check.
266         switch (type) {
267             case TYPE_SESSION:
268                 if (token == null) {
269                     throw new IllegalArgumentException("Unexpected token for session,"
270                             + " SessionCompat.Token=" + token);
271                 }
272                 break;
273             case TYPE_SESSION_SERVICE:
274             case TYPE_LIBRARY_SERVICE:
275                 if (TextUtils.isEmpty(serviceName)) {
276                     throw new IllegalArgumentException("Session service needs service name");
277                 }
278                 break;
279             default:
280                 throw new IllegalArgumentException("Invalid type");
281         }
282         if (TextUtils.isEmpty(packageName) || id == null) {
283             throw new IllegalArgumentException("Package name nor ID cannot be null.");
284         }
285         return new SessionToken2(uid, type, packageName, serviceName, id, token);
286     }
287 
288     /**
289      * Create a {@link Bundle} from this token to share it across processes.
290      * @return Bundle
291      */
toBundle()292     public Bundle toBundle() {
293         Bundle bundle = new Bundle();
294         bundle.putInt(KEY_UID, mUid);
295         bundle.putString(KEY_PACKAGE_NAME, mPackageName);
296         bundle.putString(KEY_SERVICE_NAME, mServiceName);
297         bundle.putString(KEY_ID, mId);
298         bundle.putInt(KEY_TYPE, mType);
299         bundle.putParcelable(KEY_SESSION_TOKEN, mSessionCompatToken);
300         return bundle;
301     }
302 
303     /**
304      * @hide
305      */
306     @RestrictTo(LIBRARY_GROUP)
getSessionId(ResolveInfo resolveInfo)307     public static String getSessionId(ResolveInfo resolveInfo) {
308         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
309             return null;
310         } else if (resolveInfo.serviceInfo.metaData == null) {
311             return "";
312         } else {
313             return resolveInfo.serviceInfo.metaData.getString(
314                     MediaSessionService2.SERVICE_META_DATA, "");
315         }
316     }
317 
getSessionCompatToken()318     MediaSessionCompat.Token getSessionCompatToken() {
319         return mSessionCompatToken;
320     }
321 
getSessionIdFromService(PackageManager manager, String serviceInterface, ComponentName serviceComponent)322     private static String getSessionIdFromService(PackageManager manager, String serviceInterface,
323             ComponentName serviceComponent) {
324         Intent serviceIntent = new Intent(serviceInterface);
325         // Use queryIntentServices to find services with MediaLibraryService2.SERVICE_INTERFACE.
326         // We cannot use resolveService with intent specified class name, because resolveService
327         // ignores actions if Intent.setClassName() is specified.
328         serviceIntent.setPackage(serviceComponent.getPackageName());
329 
330         List<ResolveInfo> list = manager.queryIntentServices(
331                 serviceIntent, PackageManager.GET_META_DATA);
332         if (list != null) {
333             for (int i = 0; i < list.size(); i++) {
334                 ResolveInfo resolveInfo = list.get(i);
335                 if (resolveInfo == null || resolveInfo.serviceInfo == null) {
336                     continue;
337                 }
338                 if (TextUtils.equals(
339                         resolveInfo.serviceInfo.name, serviceComponent.getClassName())) {
340                     return getSessionId(resolveInfo);
341                 }
342             }
343         }
344         return null;
345     }
346 }
347