• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.contacts.util.concurrent;
17 
18 import android.content.BroadcastReceiver;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.Loader;
23 import android.support.v4.content.LocalBroadcastManager;
24 import android.util.Log;
25 
26 import com.google.common.util.concurrent.FutureCallback;
27 import com.google.common.util.concurrent.Futures;
28 import com.google.common.util.concurrent.ListenableFuture;
29 
30 import java.util.concurrent.CancellationException;
31 import 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  */
40 public abstract class ListenableFutureLoader<D> extends Loader<D> {
41     private static final String TAG = "FutureLoader";
42 
43     private final IntentFilter mReloadFilter;
44     private final Executor mUiExecutor;
45     private final LocalBroadcastManager mLocalBroadcastManager;
46 
47     private ListenableFuture<D> mFuture;
48     private D mLoadedData;
49 
50     private BroadcastReceiver mReceiver;
51 
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      */
ListenableFutureLoader(Context context)62     public ListenableFutureLoader(Context context) {
63         this(context, null);
64     }
65 
ListenableFutureLoader(Context context, IntentFilter reloadBroadcastFilter)66     public ListenableFutureLoader(Context context, IntentFilter reloadBroadcastFilter) {
67         super(context);
68         mUiExecutor = ContactsExecutors.newUiThreadExecutor();
69         mReloadFilter = reloadBroadcastFilter;
70         mLocalBroadcastManager = LocalBroadcastManager.getInstance(context);
71     }
72 
73     @Override
onStartLoading()74     protected void onStartLoading() {
75         if (mReloadFilter != null && mReceiver == null) {
76             mReceiver = new ForceLoadReceiver();
77             mLocalBroadcastManager.registerReceiver(mReceiver, mReloadFilter);
78         }
79 
80         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
onForceLoad()92     protected void onForceLoad() {
93         mFuture = loadData();
94         Futures.addCallback(mFuture, new FutureCallback<D>() {
95             @Override
96             public void onSuccess(D result) {
97                 if (mLoadedData == null || !isSameData(mLoadedData, result)) {
98                     deliverResult(result);
99                 }
100                 mLoadedData = result;
101                 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, "Loading failed", t);
111                 }
112             }
113         }, mUiExecutor);
114     }
115 
116     @Override
onStopLoading()117     protected void onStopLoading() {
118         if (mFuture != null) {
119             mFuture.cancel(false);
120             mFuture = null;
121         }
122     }
123 
124     @Override
onReset()125     protected void onReset() {
126         mFuture = null;
127         mLoadedData = null;
128         if (mReceiver != null) {
129             mLocalBroadcastManager.unregisterReceiver(mReceiver);
130         }
131     }
132 
loadData()133     protected abstract ListenableFuture<D> loadData();
134 
135     /**
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      */
isSameData(D previousData, D newData)142     protected boolean isSameData(D previousData, D newData) {
143         return false;
144     }
145 
getLoadedData()146     public final D getLoadedData() {
147         return mLoadedData;
148     }
149 
150     public class ForceLoadReceiver extends BroadcastReceiver {
151         @Override
onReceive(Context context, Intent intent)152         public void onReceive(Context context, Intent intent) {
153             onContentChanged();
154         }
155     }
156 }
157