blob: 441ca68adab765ca0f3e9651628c53754f8e1935 [file] [log] [blame]
Marcus Hagerott04be88c2016-12-07 09:55:03 -08001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.contacts.util.concurrent;
17
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080018import android.content.BroadcastReceiver;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080019import android.content.Context;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080020import android.content.Intent;
Marcus Hagerott75895e72016-12-12 17:21:57 -080021import android.content.IntentFilter;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080022import android.content.Loader;
Marcus Hagerott75895e72016-12-12 17:21:57 -080023import android.support.v4.content.LocalBroadcastManager;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080024import android.util.Log;
25
26import com.google.common.util.concurrent.FutureCallback;
27import com.google.common.util.concurrent.Futures;
28import com.google.common.util.concurrent.ListenableFuture;
29
30import java.util.concurrent.CancellationException;
31import java.util.concurrent.Executor;
32
33/**
34 * Wraps a ListenableFuture for integration with {@link android.app.LoaderManager}
35 *
36 * <p>Using a loader ensures that the result is delivered while the receiving component (activity
37 * or fragment) is resumed and also prevents leaking references these components
38 * </p>
39 */
40public abstract class ListenableFutureLoader<D> extends Loader<D> {
41 private static final String TAG = "FutureLoader";
42
Marcus Hagerott75895e72016-12-12 17:21:57 -080043 private final IntentFilter mReloadFilter;
44 private final Executor mUiExecutor;
45 private final LocalBroadcastManager mLocalBroadcastManager;
46
Marcus Hagerott04be88c2016-12-07 09:55:03 -080047 private ListenableFuture<D> mFuture;
48 private D mLoadedData;
Marcus Hagerott75895e72016-12-12 17:21:57 -080049
50 private BroadcastReceiver mReceiver;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080051
52 /**
53 * Stores away the application context associated with context.
54 * Since Loaders can be used across multiple activities it's dangerous to
55 * store the context directly; always use {@link #getContext()} to retrieve
56 * the Loader's Context, don't use the constructor argument directly.
57 * The Context returned by {@link #getContext} is safe to use across
58 * Activity instances.
59 *
60 * @param context used to retrieve the application context.
61 */
62 public ListenableFutureLoader(Context context) {
Marcus Hagerott75895e72016-12-12 17:21:57 -080063 this(context, null);
64 }
65
66 public ListenableFutureLoader(Context context, IntentFilter reloadBroadcastFilter) {
Marcus Hagerott04be88c2016-12-07 09:55:03 -080067 super(context);
68 mUiExecutor = ContactsExecutors.newUiThreadExecutor();
Marcus Hagerott75895e72016-12-12 17:21:57 -080069 mReloadFilter = reloadBroadcastFilter;
70 mLocalBroadcastManager = LocalBroadcastManager.getInstance(context);
Marcus Hagerott04be88c2016-12-07 09:55:03 -080071 }
72
73 @Override
74 protected void onStartLoading() {
Marcus Hagerott75895e72016-12-12 17:21:57 -080075 if (mReloadFilter != null && mReceiver == null) {
76 mReceiver = new ForceLoadReceiver();
77 mLocalBroadcastManager.registerReceiver(mReceiver, mReloadFilter);
78 }
79
Marcus Hagerott04be88c2016-12-07 09:55:03 -080080 if (mLoadedData != null) {
81 deliverResult(mLoadedData);
82 }
83 if (mFuture == null) {
84 takeContentChanged();
85 forceLoad();
86 } else if (takeContentChanged()) {
87 forceLoad();
88 }
89 }
90
91 @Override
92 protected void onForceLoad() {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080093 mFuture = loadData();
Marcus Hagerott04be88c2016-12-07 09:55:03 -080094 Futures.addCallback(mFuture, new FutureCallback<D>() {
95 @Override
96 public void onSuccess(D result) {
Marcus Hagerott9a679e72016-12-15 09:30:17 -080097 if (mLoadedData == null || !isSameData(mLoadedData, result)) {
98 deliverResult(result);
99 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800100 mLoadedData = result;
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800101 commitContentChanged();
102 }
103
104 @Override
105 public void onFailure(Throwable t) {
106 if (t instanceof CancellationException) {
107 Log.i(TAG, "Loading cancelled", t);
108 rollbackContentChanged();
109 } else {
110 Log.e(TAG, "Failed to load accounts", t);
111 }
112 }
113 }, mUiExecutor);
114 }
115
116 @Override
117 protected void onStopLoading() {
118 if (mFuture != null) {
119 mFuture.cancel(false);
120 mFuture = null;
121 }
122 }
123
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800124 @Override
125 protected void onReset() {
126 mFuture = null;
127 mLoadedData = null;
Marcus Hagerott75895e72016-12-12 17:21:57 -0800128 if (mReceiver != null) {
129 mLocalBroadcastManager.unregisterReceiver(mReceiver);
130 }
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800131 }
132
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800133 protected abstract ListenableFuture<D> loadData();
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800134
Marcus Hagerott9a679e72016-12-15 09:30:17 -0800135 /**
136 * Returns whether the newly loaded data is the same as the cached value
137 *
138 * <p>This allows subclasses to suppress delivering results when the data hasn't
139 * actually changed. By default it will always return false.
140 * </p>
141 */
142 protected boolean isSameData(D previousData, D newData) {
143 return false;
144 }
145
146 public final D getLoadedData() {
147 return mLoadedData;
148 }
149
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800150 public class ForceLoadReceiver extends BroadcastReceiver {
151 @Override
152 public void onReceive(Context context, Intent intent) {
153 onContentChanged();
154 }
155 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800156}