blob: 5c0a13d99d1a53505149cefe35e353a5d931af35 [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.browse;
import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.media.MediaBrowserCompat;
import androidx.lifecycle.LiveData;
import com.android.car.media.common.MediaItemMetadata;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* A LiveData that provides access the a MediaBrowser's children
*/
class BrowsedMediaItems extends LiveData<List<MediaItemMetadata>> {
/**
* Number of times we will retry obtaining the list of children of a certain node
*/
private static final int CHILDREN_SUBSCRIPTION_RETRIES = 1;
/**
* Time between retries while trying to obtain the list of children of a certain node
*/
private static final int CHILDREN_SUBSCRIPTION_RETRY_TIME_MS = 5000;
/** Whether to send an error after the last timeout. The subscription stays active regardless.*/
private static final boolean LAST_RETRY_TIMEOUT_SENDS_ERROR = false;
private final MediaBrowserCompat mBrowser;
private final String mParentId;
private final Handler mHandler = new Handler();
private ChildrenSubscription mSubscription;
BrowsedMediaItems(@NonNull MediaBrowserCompat mediaBrowser, @NonNull String parentId) {
mBrowser = mediaBrowser;
mParentId = parentId;
}
@Override
protected void onActive() {
super.onActive();
mSubscription = new ChildrenSubscription(mParentId);
mSubscription.start(CHILDREN_SUBSCRIPTION_RETRIES, CHILDREN_SUBSCRIPTION_RETRY_TIME_MS);
}
@Override
protected void onInactive() {
super.onInactive();
mSubscription.stop();
mSubscription = null;
mHandler.removeCallbacksAndMessages(null);
}
/**
* {@link MediaBrowserCompat.SubscriptionCallback} wrapper used to overcome the lack of a
* reliable method to obtain the initial list of children of a given node.
* <p>
* When some 3rd party apps go through configuration changes (i.e., in the case of user-switch),
* they leave subscriptions in an intermediate state where neither {@link
* MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded(String, List)} nor {@link
* MediaBrowserCompat.SubscriptionCallback#onError(String)} are invoked.
* <p>
* This wrapper works around this problem by retrying the subscription a given number of times
* if no data is received after a certain amount of time. This process is started by calling
* {@link #start(int, int)}, passing the number of retries and delay between them as
* parameters.
* TODO: remove all this code if it's indeed not needed anymore (using retry=1 to be sure).
*/
private class ChildrenSubscription extends MediaBrowserCompat.SubscriptionCallback {
private final String mItemId;
private boolean mIsDataLoaded;
private int mRetries;
private int mRetryDelay;
ChildrenSubscription(String itemId) {
mItemId = itemId;
}
private Runnable mRetryRunnable = new Runnable() {
@Override
public void run() {
if (!mIsDataLoaded) {
if (mRetries > 0) {
mRetries--;
mBrowser.unsubscribe(mItemId);
mBrowser.subscribe(mItemId, ChildrenSubscription.this);
mHandler.postDelayed(this, mRetryDelay);
} else if (LAST_RETRY_TIMEOUT_SENDS_ERROR) {
mIsDataLoaded = true;
setValue(null);
}
}
}
};
/**
* Starts trying to obtain the list of children
*
* @param retries number of times to retry. If children are not obtained in this time
* then the LiveData's value will be set to {@code null}
* @param retryDelay time between retries in milliseconds
*/
void start(int retries, int retryDelay) {
if (mIsDataLoaded) {
mBrowser.subscribe(mItemId, this);
} else {
mRetries = retries;
mRetryDelay = retryDelay;
mHandler.post(mRetryRunnable);
}
}
/**
* Stops retrying
*/
void stop() {
mHandler.removeCallbacks(mRetryRunnable);
mBrowser.unsubscribe(mItemId);
}
@Override
public void onChildrenLoaded(@NonNull String parentId,
@NonNull List<MediaBrowserCompat.MediaItem> children) {
mHandler.removeCallbacks(mRetryRunnable);
mIsDataLoaded = true;
setValue(children.stream()
.filter(Objects::nonNull)
.map(MediaItemMetadata::new)
.collect(Collectors.toList()));
}
@Override
public void onChildrenLoaded(@NonNull String parentId,
@NonNull List<MediaBrowserCompat.MediaItem> children,
@NonNull Bundle options) {
onChildrenLoaded(parentId, children);
}
@Override
public void onError(@NonNull String parentId) {
mHandler.removeCallbacks(mRetryRunnable);
mIsDataLoaded = true;
setValue(null);
}
@Override
public void onError(@NonNull String parentId, @NonNull Bundle options) {
onError(parentId);
}
}
}