blob: 97c7bf6ced50acf4d34ef664ef6981919ac60b73 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.media;
import android.annotation.NonNull;
import android.content.Context;
import android.media.MediaController2;
import android.media.MediaSession2;
import android.media.SessionToken2;
import android.util.Log;
import java.util.concurrent.Executor;
/**
* Records a {@link MediaSession2} and holds {@link MediaController2}.
* <p>
* Owner of this object should handle synchronization.
*/
class MediaSession2Record {
interface SessionDestroyedListener {
void onSessionDestroyed(MediaSession2Record record);
}
private static final String TAG = "Session2Record";
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final Context mContext;
private final SessionToken2 mSessionToken;
private final SessionDestroyedListener mSessionDestroyedListener;
// TODO(jaewan): Replace these with the mContext.getMainExecutor()
private final Executor mMainExecutor;
private MediaController2 mController;
/**
* Constructor
*/
public MediaSession2Record(@NonNull Context context, @NonNull SessionToken2 token,
@NonNull SessionDestroyedListener listener) {
mContext = context;
mSessionToken = token;
mSessionDestroyedListener = listener;
mMainExecutor = (runnable) -> runnable.run();
}
public Context getContext() {
return mContext;
}
public void onSessionDestroyed() {
if (mController != null) {
mController.close();
// close() triggers ControllerCallback.onDisconnected() here already.
mController = null;
}
}
public boolean onSessionCreated(SessionToken2 token) {
if (mController != null) {
// Disclaimer: This may fail if following happens for an app.
// Step 1) Create a session in the process #1
// Step 2) Process #1 is killed
// Step 3) Before the death of process #1 is delivered,
// (i.e. ControllerCallback#onDisconnected is called),
// new process is started and create another session with the same
// id in the new process.
// Step 4) fail!!! But this is tricky case that wouldn't happen in normal.
Log.w(TAG, "Cannot create a new session with the id=" + token.getId() + " in the"
+ " pkg=" + token.getPackageName() + ". ID should be unique in a package");
return false;
}
mController = new MediaController2(mContext, token, mMainExecutor,
new ControllerCallback());
return true;
}
/**
* @return token
*/
public SessionToken2 getToken() {
return mSessionToken;
}
/**
* @return controller
*/
public MediaController2 getController() {
return mController;
}
@Override
public String toString() {
return getToken() == null
? "Token {null}" : "SessionRecord {" + getToken().toString() + "}";
}
private class ControllerCallback extends MediaController2.ControllerCallback {
// This is called on the random thread with no lock. So place ensure followings.
// 1. Don't touch anything in the parent class that needs synchronization.
// All other APIs in the MediaSession2Record assumes that server would use them with
// the lock hold.
// 2. This can be called after the controller registered is closed.
@Override
public void onDisconnected() {
if (DEBUG) {
Log.d(TAG, "onDisconnected, token=" + getToken());
}
mSessionDestroyedListener.onSessionDestroyed(MediaSession2Record.this);
}
};
}