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