blob: 69f00d5621224adce120755ba06aba2e091a2eb9 [file] [log] [blame]
/*
* Copyright (C) 2019 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.telephony.common;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* Asynchronously queries a {@link ContentResolver} for a given query and observes the loaded data
* for changes, reloading if necessary.
*
* @param <T> The type of data held by this instance
*/
public abstract class AsyncQueryLiveData<T> extends LiveData<T> {
private static final String TAG = "CD.AsyncQueryLiveData";
private final ExecutorService mExecutorService;
private final ObservableAsyncQuery mObservableAsyncQuery;
private CursorRunnable mCurrentCursorRunnable;
private Future<?> mCurrentRunnableFuture;
public AsyncQueryLiveData(Context context, QueryParam.Provider provider) {
this(context, provider, WorkerExecutor.getInstance().getSingleThreadExecutor());
}
public AsyncQueryLiveData(Context context, QueryParam.Provider provider,
ExecutorService executorService) {
mObservableAsyncQuery = new ObservableAsyncQuery(provider, context.getContentResolver(),
this::onCursorLoaded);
mExecutorService = executorService;
}
@Override
protected void onActive() {
super.onActive();
mObservableAsyncQuery.startQuery();
}
@Override
protected void onInactive() {
super.onInactive();
if (mCurrentCursorRunnable != null) {
mCurrentCursorRunnable.closeCursorIfNecessary();
mCurrentRunnableFuture.cancel(false);
}
mObservableAsyncQuery.stopQuery();
}
/**
* Override this function to convert the loaded data. This function is called on non-UI thread.
*/
@WorkerThread
protected abstract T convertToEntity(@NonNull Cursor cursor);
private void onCursorLoaded(Cursor cursor) {
Log.d(TAG, "onCursorLoaded: " + this);
if (mCurrentCursorRunnable != null) {
mCurrentCursorRunnable.closeCursorIfNecessary();
mCurrentRunnableFuture.cancel(false);
}
mCurrentCursorRunnable = new CursorRunnable(cursor);
mCurrentRunnableFuture = mExecutorService.submit(mCurrentCursorRunnable);
}
private class CursorRunnable implements Runnable {
private final Cursor mCursor;
private boolean mIsActive;
private CursorRunnable(@Nullable Cursor cursor) {
mCursor = cursor;
mIsActive = true;
}
@Override
public void run() {
// Bypass the workload to convert to entity and UI change triggered by post value if
// cursor is not current.
if (mIsActive) {
T entity = mCursor == null ? null : convertToEntity(mCursor);
if (mIsActive) {
postValue(entity);
}
}
closeCursorIfNecessary();
}
public synchronized void closeCursorIfNecessary() {
if (!mIsActive && mCursor != null) {
mCursor.close();
}
mIsActive = false;
}
}
}