blob: ebfd08c26567ea76d512ff2bd2ac3a42245e6b8f [file] [log] [blame]
/*
* Copyright 2021 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.content.pm.PackageManager;
import android.database.Cursor;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.content.ContentResolverCompat;
import androidx.core.content.ContextCompat;
import androidx.core.os.CancellationSignal;
import androidx.core.os.OperationCanceledException;
import androidx.loader.content.AsyncTaskLoader;
import androidx.loader.content.Loader;
/**
* An {@link AsyncTaskLoader} that queries database and convert {@link Cursor} to data entities.
*
* @param <T> type of data the cursor will be converted to.
*/
public class AsyncEntityLoader<T> extends AsyncTaskLoader<T> {
/**
* Caller of the {@link AsyncTaskLoader} will need to provide how to convert the loaded cursor
* to the data it consumes.
*
* @param <T> The type of data the cursor will be converted to.
*/
public interface EntityConverter<T> {
/** Convert cursor to the desired type of data. This function is called on non-UI thread. */
@WorkerThread
T convertToEntity(Cursor cursor);
}
private static final String TAG = "CD.AsyncEntityLoader";
private final Context mContext;
private final ContentResolver mContentResolver;
private final ForceLoadContentObserver mObserver;
private final QueryParam.Provider mQueryParamProvider;
private final EntityConverter<T> mEntityConverter;
private final Loader.OnLoadCompleteListener<T> mOnLoadCompleteListener;
private T mEntity;
private CancellationSignal mCancellationSignal;
public AsyncEntityLoader(
@NonNull Context context,
@NonNull QueryParam.Provider queryParamProvider,
@NonNull EntityConverter<T> entityConverter,
@NonNull Loader.OnLoadCompleteListener<T> onLoadCompleteListener) {
super(context);
mContext = context;
mContentResolver = context.getContentResolver();
mObserver = new ForceLoadContentObserver();
mQueryParamProvider = queryParamProvider;
mEntityConverter = entityConverter;
mOnLoadCompleteListener = onLoadCompleteListener;
}
@Nullable
@WorkerThread
@Override
public T loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
Cursor cursor = null;
T entity = null;
try {
QueryParam queryParam = mQueryParamProvider.getQueryParam();
if (queryParam == null || ContextCompat.checkSelfPermission(mContext,
queryParam.mPermission) != PackageManager.PERMISSION_GRANTED) {
return null;
}
mContentResolver.registerContentObserver(queryParam.mUri, false, mObserver);
cursor = ContentResolverCompat.query(mContentResolver,
queryParam.mUri, queryParam.mProjection, queryParam.mSelection,
queryParam.mSelectionArgs, queryParam.mOrderBy,
mCancellationSignal);
if (cursor != null) {
// Ensure the cursor window is filled.
cursor.getCount();
entity = mEntityConverter.convertToEntity(cursor);
cursor.close();
}
} finally {
if (cursor != null) {
cursor.close();
}
synchronized (this) {
mCancellationSignal = null;
}
}
return entity;
}
@Override
public void cancelLoadInBackground() {
Log.d(TAG, "cancelLoadInBackground");
mContentResolver.unregisterContentObserver(mObserver);
synchronized (this) {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
}
}
}
@MainThread
@Override
public void deliverResult(@Nullable T entity) {
if (isReset()) {
return;
}
mEntity = entity;
if (isStarted()) {
Log.d(TAG, "deliverResult");
super.deliverResult(entity);
}
}
/**
* Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
*/
@MainThread
@Override
protected void onStartLoading() {
Log.d(TAG, "onStartLoading");
registerListener(0, mOnLoadCompleteListener);
if (mEntity != null) {
deliverResult(mEntity);
}
if (takeContentChanged() || mEntity == null) {
forceLoad();
}
}
@MainThread
@Override
protected void onStopLoading() {
Log.d(TAG, "onStopLoading");
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@MainThread
@Override
protected void onReset() {
Log.d(TAG, "onReset");
// Ensure the loader is stopped.
onStopLoading();
mEntity = null;
unregisterListener(mOnLoadCompleteListener);
}
}