| /* |
| * 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; |
| } |
| } |
| } |