blob: 8c9342622db7bb8a18b9c46e45bc5144b969861b [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.car.media.common.source;
import static com.android.car.arch.common.LiveDataFunctions.dataOf;
import android.app.Application;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.media.CarMediaManager;
import android.content.ComponentName;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.car.media.common.source.MediaBrowserConnector.BrowsingState;
import java.util.Objects;
/**
* Contains observable data needed for displaying playback and browse UI.
* MediaSourceViewModel is a singleton tied to the application to provide a single source of truth.
*/
public class MediaSourceViewModel extends AndroidViewModel {
private static final String TAG = "MediaSourceViewModel";
private static MediaSourceViewModel[] sInstances = new MediaSourceViewModel[2];
private final Car mCar;
private CarMediaManager mCarMediaManager;
// Primary media source.
private final MutableLiveData<MediaSource> mPrimaryMediaSource = dataOf(null);
// Browser for the primary media source and its connection state.
private final MutableLiveData<BrowsingState> mBrowsingState = dataOf(null);
private final Handler mHandler;
private final CarMediaManager.MediaSourceChangedListener mMediaSourceListener;
/**
* Factory for creating dependencies. Can be swapped out for testing.
*/
@VisibleForTesting
interface InputFactory {
MediaBrowserConnector createMediaBrowserConnector(@NonNull Application application,
@NonNull MediaBrowserConnector.Callback connectedBrowserCallback);
Car getCarApi();
CarMediaManager getCarMediaManager(Car carApi) throws CarNotConnectedException;
MediaSource getMediaSource(ComponentName componentName);
}
/** Returns the MediaSourceViewModel singleton tied to the application. */
public static MediaSourceViewModel get(@NonNull Application application, int mode) {
if (sInstances[mode] == null) {
sInstances[mode] = new MediaSourceViewModel(application, mode);
}
return sInstances[mode];
}
/**
* Create a new instance of MediaSourceViewModel
*
* @see AndroidViewModel
*/
private MediaSourceViewModel(@NonNull Application application, int mode) {
this(application, mode, new InputFactory() {
@Override
public MediaBrowserConnector createMediaBrowserConnector(
@NonNull Application application,
@NonNull MediaBrowserConnector.Callback connectedBrowserCallback) {
return new MediaBrowserConnector(application, connectedBrowserCallback);
}
@Override
public Car getCarApi() {
return Car.createCar(application);
}
@Override
public CarMediaManager getCarMediaManager(Car carApi) throws CarNotConnectedException {
return (CarMediaManager) carApi.getCarManager(Car.CAR_MEDIA_SERVICE);
}
@Override
public MediaSource getMediaSource(ComponentName componentName) {
return componentName == null ? null : MediaSource.create(application,
componentName);
}
});
}
private final InputFactory mInputFactory;
private final MediaBrowserConnector mBrowserConnector;
private final MediaBrowserConnector.Callback mBrowserCallback = mBrowsingState::setValue;
@VisibleForTesting
MediaSourceViewModel(@NonNull Application application, int mode,
@NonNull InputFactory inputFactory) {
super(application);
mInputFactory = inputFactory;
mCar = inputFactory.getCarApi();
mBrowserConnector = inputFactory.createMediaBrowserConnector(application, mBrowserCallback);
mHandler = new Handler(application.getMainLooper());
mMediaSourceListener = componentName -> mHandler.post(
() -> updateModelState(mInputFactory.getMediaSource(componentName)));
try {
mCarMediaManager = mInputFactory.getCarMediaManager(mCar);
mCarMediaManager.addMediaSourceListener(mMediaSourceListener, mode);
updateModelState(mInputFactory.getMediaSource(mCarMediaManager.getMediaSource(mode)));
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car not connected", e);
}
}
@Override
protected void onCleared() {
super.onCleared();
mCar.disconnect();
}
@VisibleForTesting
MediaBrowserConnector.Callback getBrowserCallback() {
return mBrowserCallback;
}
/**
* Returns a LiveData that emits the MediaSource that is to be browsed or displayed.
*/
public LiveData<MediaSource> getPrimaryMediaSource() {
return mPrimaryMediaSource;
}
/**
* Updates the primary media source.
*/
public void setPrimaryMediaSource(@NonNull MediaSource mediaSource, int mode) {
mCarMediaManager.setMediaSource(mediaSource.getBrowseServiceComponentName(), mode);
}
/**
* Returns a LiveData that emits a {@link BrowsingState}, or {@code null} if there is no media
* source.
*/
public LiveData<BrowsingState> getBrowsingState() {
return mBrowsingState;
}
private void updateModelState(MediaSource newMediaSource) {
MediaSource oldMediaSource = mPrimaryMediaSource.getValue();
if (Objects.equals(oldMediaSource, newMediaSource)) {
return;
}
// Broadcast the new source
mPrimaryMediaSource.setValue(newMediaSource);
// Recompute dependent values
if (newMediaSource != null) {
mBrowserConnector.connectTo(newMediaSource);
}
}
}