MediaSession2: Fix timing issue

Session/Controller needs mProvider. However, if the createProvider()
interacts with other components, than other components may use session
/controller object before mProvider is set.

This CL prevents such issues by calling initialize() to communicate
with other components after the provider is set.

Test: Run all MediaComponents test once
Change-Id: I7f4c52136038a0522471015344881552b678a2ab
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index c7f6014..1d6d1ed 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.media;
 
+import static android.media.SessionToken2.TYPE_SESSION;
+
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -28,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
@@ -130,15 +133,9 @@
     private final List<MediaSession2Record> mSessions = new ArrayList<>();
 
     private final MediaSession2Record.SessionDestroyedListener mSessionDestroyedListener =
-            (MediaSession2Record record) -> {
+            (record) -> {
                 synchronized (mLock) {
-                    if (DEBUG) {
-                        Log.d(TAG, record.toString() + " becomes inactive");
-                    }
-                    record.onSessionDestroyed();
-                    if (!(record instanceof MediaSessionService2Record)) {
-                        mSessions.remove(record);
-                    }
+                    destroySessionLocked(record);
                 }
             };
 
@@ -446,14 +443,16 @@
         }
 
         // TODO(jaewan): Query per users.
+        // TODO(jaewan): Similar codes are also at the updatable. Can't we share codes?
+        PackageManager manager = getContext().getPackageManager();
         List<ResolveInfo> services = new ArrayList<>();
         // If multiple actions are declared for a service, browser gets higher priority.
-        List<ResolveInfo> libraryServices = getContext().getPackageManager().queryIntentServices(
+        List<ResolveInfo> libraryServices = manager.queryIntentServices(
                 new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
         if (libraryServices != null) {
             services.addAll(libraryServices);
         }
-        List<ResolveInfo> sessionServices = getContext().getPackageManager().queryIntentServices(
+        List<ResolveInfo> sessionServices = manager.queryIntentServices(
                 new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
         if (sessionServices != null) {
             services.addAll(sessionServices);
@@ -468,12 +467,19 @@
                     continue;
                 }
                 ServiceInfo serviceInfo = services.get(i).serviceInfo;
+                int uid;
+                try {
+                    uid = manager.getPackageUid(serviceInfo.packageName,
+                            PackageManager.GET_META_DATA);
+                } catch (NameNotFoundException e) {
+                    continue;
+                }
                 String id = (serviceInfo.metaData != null) ? serviceInfo.metaData.getString(
                         MediaSessionService2.SERVICE_META_DATA) : null;
                 // Do basic sanity check
                 // TODO(jaewan): also santity check if it's protected with the system|privileged
                 //               permission
-                boolean conflict = (getSessionRecordLocked(serviceInfo.name, id) != null);
+                boolean conflict = (getSessionRecordLocked(uid, serviceInfo.name, id) != null);
                 if (conflict) {
                     Log.w(TAG, serviceInfo.packageName + " contains multiple"
                             + " MediaSessionService2s declared in the manifest with"
@@ -481,10 +487,12 @@
                             + serviceInfo.packageName + "/" + serviceInfo.name);
                 } else {
                     int type = (libraryServices.contains(services.get(i)))
-                            ? SessionToken2.TYPE_LIBRARY_SERVICE : SessionToken2.TYPE_SESSION_SERVICE;
-                    MediaSessionService2Record record =
-                            new MediaSessionService2Record(getContext(), mSessionDestroyedListener,
-                                    type, serviceInfo.packageName, serviceInfo.name, id);
+                            ? SessionToken2.TYPE_LIBRARY_SERVICE
+                            : SessionToken2.TYPE_SESSION_SERVICE;
+                    SessionToken2 token = new SessionToken2(getContext(), uid, type,
+                            serviceInfo.packageName, serviceInfo.name, id, null);
+                    MediaSession2Record record = new MediaSession2Record(getContext(),
+                            token, mSessionDestroyedListener);
                     mSessions.add(record);
                 }
             }
@@ -497,17 +505,27 @@
         }
     }
 
-    MediaSession2Record getSessionRecordLocked(String packageName, String id) {
+    private MediaSession2Record getSessionRecordLocked(int uid, String packageName, String id) {
         for (int i = 0; i < mSessions.size(); i++) {
-            MediaSession2Record record = mSessions.get(i);
-            if (record.getToken().getPackageName().equals(packageName)
-                    && record.getToken().getId().equals(id)) {
-                return record;
+            SessionToken2 token = mSessions.get(i).getToken();
+            if (token.getUid() == uid && token.getPackageName().equals(packageName)
+                    && token.getId().equals(id)) {
+                return mSessions.get(i);
             }
         }
         return null;
     }
 
+    private void destroySessionLocked(MediaSession2Record record) {
+        if (DEBUG) {
+            Log.d(TAG, record.toString() + " becomes inactive");
+        }
+        record.onSessionDestroyed();
+        if (record.getToken().getType() == TYPE_SESSION) {
+            mSessions.remove(record);
+        }
+    }
+
     private void enforcePackageName(String packageName, int uid) {
         if (TextUtils.isEmpty(packageName)) {
             throw new IllegalArgumentException("packageName may not be empty");
@@ -1410,29 +1428,55 @@
         }
 
         @Override
-        public Bundle createSessionToken(String sessionPackage, String id,
-                IMediaSession2 sessionBinder) throws RemoteException {
-            int uid = Binder.getCallingUid();
-            int pid = Binder.getCallingPid();
-
-            MediaSession2Record record;
-            SessionToken2 token;
-            // TODO(jaewan): Add sanity check for the token if calling package is from uid.
+        public boolean onSessionCreated(Bundle sessionToken) {
+            final int uid = Binder.getCallingUid();
+            final int pid = Binder.getCallingPid();
+            final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
+            if (token == null || token.getUid() != uid) {
+                Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
+                        + " but from uid=" + uid);
+            }
+            if (DEBUG) {
+                Log.d(TAG, "onSessionCreated " + token);
+            }
             synchronized (mLock) {
-                record = getSessionRecordLocked(sessionPackage, id);
-                if (record == null) {
-                    record = new MediaSession2Record(getContext(), mSessionDestroyedListener);
-                    mSessions.add(record);
-                }
-                token = record.createSessionToken(pid, sessionPackage, id, sessionBinder);
-                if (token == null) {
-                    Log.d(TAG, "failed to create session token for " + sessionPackage
-                            + " from pid=" + pid + ". Previously " + record);
+                MediaSession2Record record = getSessionRecordLocked(
+                        uid, token.getPackageName(), token.getId());
+                if (record != null) {
+                    return record.onSessionCreated(token);
                 } else {
-                    Log.d(TAG, "session " + token + " is created");
+                    record = new MediaSession2Record(
+                            getContext(), token, mSessionDestroyedListener);
+                    mSessions.add(record);
+                    return record.onSessionCreated(token);
                 }
             }
-            return token == null ? null : token.toBundle();
+        }
+
+        @Override
+        public void onSessionDestroyed(Bundle sessionToken) {
+            final int uid = Binder.getCallingUid();
+            final int pid = Binder.getCallingPid();
+            final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
+            if (token == null || token.getUid() != uid) {
+                Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
+                        + " but from uid=" + uid);
+            }
+            if (DEBUG) {
+                Log.d(TAG, "onSessionDestroyed " + token);
+            }
+            synchronized (mLock) {
+                MediaSession2Record record = getSessionRecordLocked(
+                        uid, token.getPackageName(), token.getId());
+                if (record != null) {
+                    record.onSessionDestroyed();
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG, "Cannot find a session record to destroy. uid=" + uid
+                                + ", pkg=" + token.getPackageName() + ", id=" + token.getId());
+                    }
+                }
+            }
         }
 
         // TODO(jaewan): Protect this API with permission
@@ -1444,8 +1488,8 @@
             synchronized (mLock) {
                 for (int i = 0; i < mSessions.size(); i++) {
                     MediaSession2Record record = mSessions.get(i);
-                    boolean isSessionService = (record instanceof MediaSessionService2Record);
-                    boolean isActive = record.getSessionPid() != 0;
+                    boolean isSessionService = (record.getToken().getType() != TYPE_SESSION);
+                    boolean isActive = record.getController() != null;
                     if ((!activeSessionOnly && isSessionService)
                             || (!sessionServiceOnly && isActive)) {
                         SessionToken2 token = record.getToken();