blob: 28abcaa7735f14242c1fde6afd06e1057c7be21a [file] [log] [blame]
Dianne Hackbornc8017682010-07-06 13:34:38 -07001/*
2 * Copyright (C) 2010 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
17package android.app;
18
19import android.content.Loader;
Dianne Hackbornc8017682010-07-06 13:34:38 -070020import android.os.Bundle;
Dianne Hackborn5e0d5952010-08-05 13:45:35 -070021import android.util.Log;
Dianne Hackbornc8017682010-07-06 13:34:38 -070022import android.util.SparseArray;
23
24/**
Dianne Hackborn4911b782010-07-15 12:54:39 -070025 * Interface associated with an {@link Activity} or {@link Fragment} for managing
Dianne Hackbornc8017682010-07-06 13:34:38 -070026 * one or more {@link android.content.Loader} instances associated with it.
27 */
Dianne Hackborn4911b782010-07-15 12:54:39 -070028public interface LoaderManager {
29 /**
30 * Callback interface for a client to interact with the manager.
31 */
32 public interface LoaderCallbacks<D> {
33 /**
34 * Instantiate and return a new Loader for the given ID.
35 *
36 * @param id The ID whose loader is to be created.
37 * @param args Any arguments supplied by the caller.
38 * @return Return a new Loader instance that is ready to start loading.
39 */
40 public Loader<D> onCreateLoader(int id, Bundle args);
41
42 /**
43 * Called when a previously created loader has finished its load.
44 * @param loader The Loader that has finished.
45 * @param data The data generated by the Loader.
46 */
47 public void onLoadFinished(Loader<D> loader, D data);
48 }
49
50 /**
51 * Ensures a loader is initialized and active. If the loader doesn't
52 * already exist, one is created and (if the activity/fragment is currently
53 * started) starts the loader. Otherwise the last created
54 * loader is re-used.
55 *
56 * <p>In either case, the given callback is associated with the loader, and
57 * will be called as the loader state changes. If at the point of call
58 * the caller is in its started state, and the requested loader
59 * already exists and has generated its data, then
60 * callback. {@link LoaderCallbacks#onLoadFinished} will
61 * be called immediately (inside of this function), so you must be prepared
62 * for this to happen.
63 */
64 public <D> Loader<D> initLoader(int id, Bundle args,
65 LoaderManager.LoaderCallbacks<D> callback);
66
67 /**
68 * Creates a new loader in this manager, registers the callbacks to it,
69 * and (if the activity/fragment is currently started) starts loading it.
70 * If a loader with the same id has previously been
71 * started it will automatically be destroyed when the new loader completes
72 * its work. The callback will be delivered before the old loader
73 * is destroyed.
74 */
75 public <D> Loader<D> restartLoader(int id, Bundle args,
76 LoaderManager.LoaderCallbacks<D> callback);
77
78 /**
79 * Stops and removes the loader with the given ID.
80 */
81 public void stopLoader(int id);
82
83 /**
84 * Return the Loader with the given id or null if no matching Loader
85 * is found.
86 */
87 public <D> Loader<D> getLoader(int id);
88}
89
90class LoaderManagerImpl implements LoaderManager {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -070091 static final String TAG = "LoaderManagerImpl";
92 static final boolean DEBUG = true;
93
94 // These are the currently active loaders. A loader is here
95 // from the time its load is started until it has been explicitly
96 // stopped or restarted by the application.
Dianne Hackborn2707d602010-07-09 18:01:20 -070097 final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
Dianne Hackborn5e0d5952010-08-05 13:45:35 -070098
99 // These are previously run loaders. This list is maintained internally
100 // to avoid destroying a loader while an application is still using it.
101 // It allows an application to restart a loader, but continue using its
102 // previously run loader until the new loader's data is available.
Dianne Hackborn2707d602010-07-09 18:01:20 -0700103 final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700104
Dianne Hackborn2707d602010-07-09 18:01:20 -0700105 boolean mStarted;
106 boolean mRetaining;
107 boolean mRetainingStarted;
Dianne Hackborn4911b782010-07-15 12:54:39 -0700108
Dianne Hackbornc8017682010-07-06 13:34:38 -0700109 final class LoaderInfo implements Loader.OnLoadCompleteListener<Object> {
Dianne Hackborn2707d602010-07-09 18:01:20 -0700110 final int mId;
111 final Bundle mArgs;
112 LoaderManager.LoaderCallbacks<Object> mCallbacks;
113 Loader<Object> mLoader;
114 Object mData;
115 boolean mStarted;
116 boolean mRetaining;
117 boolean mRetainingStarted;
118 boolean mDestroyed;
119 boolean mListenerRegistered;
120
121 public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
122 mId = id;
123 mArgs = args;
124 mCallbacks = callbacks;
125 }
126
127 void start() {
128 if (mRetaining && mRetainingStarted) {
129 // Our owner is started, but we were being retained from a
130 // previous instance in the started state... so there is really
131 // nothing to do here, since the loaders are still started.
132 mStarted = true;
133 return;
134 }
135
Dianne Hackborn4911b782010-07-15 12:54:39 -0700136 if (mStarted) {
137 // If loader already started, don't restart.
138 return;
139 }
140
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700141 if (DEBUG) Log.v(TAG, " Starting: " + this);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700142 if (mLoader == null && mCallbacks != null) {
143 mLoader = mCallbacks.onCreateLoader(mId, mArgs);
144 }
145 if (mLoader != null) {
Dianne Hackborn4911b782010-07-15 12:54:39 -0700146 if (!mListenerRegistered) {
147 mLoader.registerListener(mId, this);
148 mListenerRegistered = true;
149 }
Dianne Hackborn2707d602010-07-09 18:01:20 -0700150 mLoader.startLoading();
151 mStarted = true;
152 }
153 }
154
155 void retain() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700156 if (DEBUG) Log.v(TAG, " Retaining: " + this);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700157 mRetaining = true;
158 mRetainingStarted = mStarted;
159 mStarted = false;
160 mCallbacks = null;
161 }
162
163 void finishRetain() {
164 if (mRetaining) {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700165 if (DEBUG) Log.v(TAG, " Finished Retaining: " + this);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700166 mRetaining = false;
167 if (mStarted != mRetainingStarted) {
168 if (!mStarted) {
169 // This loader was retained in a started state, but
170 // at the end of retaining everything our owner is
171 // no longer started... so make it stop.
172 stop();
173 }
174 }
175 if (mStarted && mData != null && mCallbacks != null) {
176 // This loader was retained, and now at the point of
177 // finishing the retain we find we remain started, have
178 // our data, and the owner has a new callback... so
179 // let's deliver the data now.
180 mCallbacks.onLoadFinished(mLoader, mData);
181 }
182 }
183 }
184
185 void stop() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700186 if (DEBUG) Log.v(TAG, " Stopping: " + this);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700187 mStarted = false;
Dianne Hackborndebb2e22010-08-09 16:32:52 -0700188 if (!mRetaining) {
189 if (mLoader != null && mListenerRegistered) {
190 // Let the loader know we're done with it
191 mListenerRegistered = false;
192 mLoader.unregisterListener(this);
193 mLoader.stopLoading();
194 }
195 mData = null;
Dianne Hackborn2707d602010-07-09 18:01:20 -0700196 }
197 }
198
199 void destroy() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700200 if (DEBUG) Log.v(TAG, " Destroying: " + this);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700201 mDestroyed = true;
202 mCallbacks = null;
203 if (mLoader != null) {
204 if (mListenerRegistered) {
205 mListenerRegistered = false;
206 mLoader.unregisterListener(this);
207 }
208 mLoader.destroy();
209 }
210 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700211
212 @Override public void onLoadComplete(Loader<Object> loader, Object data) {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700213 if (DEBUG) Log.v(TAG, "onLoadComplete: " + this + " mDestroyed=" + mDestroyed);
214
Dianne Hackborn2707d602010-07-09 18:01:20 -0700215 if (mDestroyed) {
216 return;
217 }
218
Dianne Hackbornc8017682010-07-06 13:34:38 -0700219 // Notify of the new data so the app can switch out the old data before
220 // we try to destroy it.
Dianne Hackborn2707d602010-07-09 18:01:20 -0700221 mData = data;
222 if (mCallbacks != null) {
223 mCallbacks.onLoadFinished(loader, data);
224 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700225
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700226 if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this);
227
228 // We have now given the application the new loader with its
229 // loaded data, so it should have stopped using the previous
230 // loader. If there is a previous loader on the inactive list,
231 // clean it up.
Dianne Hackborn2707d602010-07-09 18:01:20 -0700232 LoaderInfo info = mInactiveLoaders.get(mId);
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700233 if (info != null && info != this) {
234 info.destroy();
Dianne Hackborn2707d602010-07-09 18:01:20 -0700235 mInactiveLoaders.remove(mId);
Dianne Hackbornc8017682010-07-06 13:34:38 -0700236 }
237 }
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700238
239 @Override
240 public String toString() {
241 StringBuilder sb = new StringBuilder(64);
242 sb.append("LoaderInfo{");
243 sb.append(Integer.toHexString(System.identityHashCode(this)));
244 sb.append(" #");
245 sb.append(mId);
246 if (mArgs != null) {
247 sb.append(" ");
248 sb.append(mArgs.toString());
249 }
250 sb.append("}");
251 return sb.toString();
252 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700253 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700254
Dianne Hackborn4911b782010-07-15 12:54:39 -0700255 LoaderManagerImpl(boolean started) {
Dianne Hackbornc8017682010-07-06 13:34:38 -0700256 mStarted = started;
257 }
258
Dianne Hackborn2707d602010-07-09 18:01:20 -0700259 private LoaderInfo createLoader(int id, Bundle args,
260 LoaderManager.LoaderCallbacks<Object> callback) {
261 LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
262 mLoaders.put(id, info);
263 Loader<Object> loader = callback.onCreateLoader(id, args);
264 info.mLoader = (Loader<Object>)loader;
265 if (mStarted) {
Dianne Hackborn4911b782010-07-15 12:54:39 -0700266 // The activity will start all existing loaders in it's onStart(),
267 // so only start them here if we're past that point of the activitiy's
268 // life cycle
269 info.start();
Dianne Hackborn2707d602010-07-09 18:01:20 -0700270 }
271 return info;
272 }
273
Dianne Hackborn2707d602010-07-09 18:01:20 -0700274 @SuppressWarnings("unchecked")
275 public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
276 LoaderInfo info = mLoaders.get(id);
277
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700278 if (DEBUG) Log.v(TAG, "initLoader in " + this + ": cur=" + info);
279
Dianne Hackborn2707d602010-07-09 18:01:20 -0700280 if (info == null) {
281 // Loader doesn't already exist; create.
282 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
283 } else {
284 info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
285 }
286
287 if (info.mData != null && mStarted) {
288 // If the loader has already generated its data, report it now.
289 info.mCallbacks.onLoadFinished(info.mLoader, info.mData);
290 }
291
292 return (Loader<D>)info.mLoader;
293 }
294
Dianne Hackbornc8017682010-07-06 13:34:38 -0700295 @SuppressWarnings("unchecked")
Dianne Hackborn2707d602010-07-09 18:01:20 -0700296 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
Dianne Hackbornc8017682010-07-06 13:34:38 -0700297 LoaderInfo info = mLoaders.get(id);
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700298 if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": cur=" + info);
Dianne Hackbornc8017682010-07-06 13:34:38 -0700299 if (info != null) {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700300 LoaderInfo inactive = mInactiveLoaders.get(id);
301 if (inactive != null) {
302 if (info.mData != null) {
303 // This loader now has data... we are probably being
304 // called from within onLoadComplete, where we haven't
305 // yet destroyed the last inactive loader. So just do
306 // that now.
307 if (DEBUG) Log.v(TAG, " Removing last inactive loader in " + this);
308 inactive.destroy();
309 mInactiveLoaders.put(id, info);
310 } else {
311 // We already have an inactive loader for this ID that we are
312 // waiting for! Now we have three active loaders... let's just
313 // drop the one in the middle, since we are still waiting for
314 // its result but that result is already out of date.
315 if (DEBUG) Log.v(TAG, " Removing intermediate loader in " + this);
316 info.destroy();
317 }
Dianne Hackborn2707d602010-07-09 18:01:20 -0700318 } else {
319 // Keep track of the previous instance of this loader so we can destroy
320 // it when the new one completes.
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700321 if (DEBUG) Log.v(TAG, " Making inactive: " + info);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700322 mInactiveLoaders.put(id, info);
323 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700324 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700325
Dianne Hackborn2707d602010-07-09 18:01:20 -0700326 info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
327 return (Loader<D>)info.mLoader;
Dianne Hackbornc8017682010-07-06 13:34:38 -0700328 }
329
Dianne Hackborn2707d602010-07-09 18:01:20 -0700330 public void stopLoader(int id) {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700331 if (DEBUG) Log.v(TAG, "stopLoader in " + this + " of " + id);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700332 int idx = mLoaders.indexOfKey(id);
333 if (idx >= 0) {
334 LoaderInfo info = mLoaders.valueAt(idx);
335 mLoaders.removeAt(idx);
Dianne Hackborn4911b782010-07-15 12:54:39 -0700336 info.destroy();
Dianne Hackbornc8017682010-07-06 13:34:38 -0700337 }
338 }
339
Dianne Hackbornc8017682010-07-06 13:34:38 -0700340 @SuppressWarnings("unchecked")
341 public <D> Loader<D> getLoader(int id) {
342 LoaderInfo loaderInfo = mLoaders.get(id);
343 if (loaderInfo != null) {
Dianne Hackborn2707d602010-07-09 18:01:20 -0700344 return (Loader<D>)mLoaders.get(id).mLoader;
Dianne Hackbornc8017682010-07-06 13:34:38 -0700345 }
346 return null;
347 }
348
349 void doStart() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700350 if (DEBUG) Log.v(TAG, "Starting: " + this);
351
Dianne Hackbornc8017682010-07-06 13:34:38 -0700352 // Call out to sub classes so they can start their loaders
353 // Let the existing loaders know that we want to be notified when a load is complete
354 for (int i = mLoaders.size()-1; i >= 0; i--) {
Dianne Hackborn2707d602010-07-09 18:01:20 -0700355 mLoaders.valueAt(i).start();
Dianne Hackbornc8017682010-07-06 13:34:38 -0700356 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700357 mStarted = true;
358 }
359
360 void doStop() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700361 if (DEBUG) Log.v(TAG, "Stopping: " + this);
362
Dianne Hackbornc8017682010-07-06 13:34:38 -0700363 for (int i = mLoaders.size()-1; i >= 0; i--) {
Dianne Hackborn2707d602010-07-09 18:01:20 -0700364 mLoaders.valueAt(i).stop();
Dianne Hackbornc8017682010-07-06 13:34:38 -0700365 }
Dianne Hackbornc8017682010-07-06 13:34:38 -0700366 mStarted = false;
367 }
368
Dianne Hackborn2707d602010-07-09 18:01:20 -0700369 void doRetain() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700370 if (DEBUG) Log.v(TAG, "Retaining: " + this);
371
Dianne Hackborn2707d602010-07-09 18:01:20 -0700372 mRetaining = true;
373 mStarted = false;
374 for (int i = mLoaders.size()-1; i >= 0; i--) {
375 mLoaders.valueAt(i).retain();
376 }
377 }
378
379 void finishRetain() {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700380 if (DEBUG) Log.v(TAG, "Finished Retaining: " + this);
381
Dianne Hackborn2707d602010-07-09 18:01:20 -0700382 mRetaining = false;
383 for (int i = mLoaders.size()-1; i >= 0; i--) {
384 mLoaders.valueAt(i).finishRetain();
385 }
386 }
387
Dianne Hackbornc8017682010-07-06 13:34:38 -0700388 void doDestroy() {
Dianne Hackborn2707d602010-07-09 18:01:20 -0700389 if (!mRetaining) {
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700390 if (DEBUG) Log.v(TAG, "Destroying Active: " + this);
Dianne Hackbornc8017682010-07-06 13:34:38 -0700391 for (int i = mLoaders.size()-1; i >= 0; i--) {
Dianne Hackborn2707d602010-07-09 18:01:20 -0700392 mLoaders.valueAt(i).destroy();
Dianne Hackbornc8017682010-07-06 13:34:38 -0700393 }
394 }
Dianne Hackborn2707d602010-07-09 18:01:20 -0700395
Dianne Hackborn5e0d5952010-08-05 13:45:35 -0700396 if (DEBUG) Log.v(TAG, "Destroying Inactive: " + this);
Dianne Hackborn2707d602010-07-09 18:01:20 -0700397 for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
398 mInactiveLoaders.valueAt(i).destroy();
399 }
400 mInactiveLoaders.clear();
Dianne Hackbornc8017682010-07-06 13:34:38 -0700401 }
402}