1package com.bumptech.glide.manager;
2
3import android.annotation.TargetApi;
4import android.app.Activity;
5import android.app.Application;
6import android.content.Context;
7import android.content.ContextWrapper;
8import android.os.Build;
9import android.os.Handler;
10import android.os.Looper;
11import android.os.Message;
12import android.support.v4.app.Fragment;
13import android.support.v4.app.FragmentActivity;
14import android.support.v4.app.FragmentManager;
15import android.util.Log;
16
17import com.bumptech.glide.RequestManager;
18import com.bumptech.glide.util.Util;
19
20import java.util.HashMap;
21import java.util.Map;
22
23/**
24 * A collection of static methods for creating new {@link com.bumptech.glide.RequestManager}s or retrieving existing
25 * ones from activities and fragment.
26 */
27public class RequestManagerRetriever implements Handler.Callback {
28    static final String TAG = "com.bumptech.glide.manager";
29
30    /** The singleton instance of RequestManagerRetriever. */
31    private static final RequestManagerRetriever INSTANCE = new RequestManagerRetriever();
32
33    private static final int ID_REMOVE_FRAGMENT_MANAGER = 1;
34    private static final int ID_REMOVE_SUPPORT_FRAGMENT_MANAGER = 2;
35
36    /** The top application level RequestManager. */
37    private volatile RequestManager applicationManager;
38
39    // Visible for testing.
40    /** Pending adds for RequestManagerFragments. */
41    final Map<android.app.FragmentManager, RequestManagerFragment> pendingRequestManagerFragments =
42            new HashMap<android.app.FragmentManager, RequestManagerFragment>();
43
44    // Visible for testing.
45    /** Pending adds for SupportRequestManagerFragments. */
46    final Map<FragmentManager, SupportRequestManagerFragment> pendingSupportRequestManagerFragments =
47            new HashMap<FragmentManager, SupportRequestManagerFragment>();
48
49    /** Main thread handler to handle cleaning up pending fragment maps. */
50    private final Handler handler;
51
52    /**
53     * Retrieves and returns the RequestManagerRetriever singleton.
54     */
55    public static RequestManagerRetriever get() {
56        return INSTANCE;
57    }
58
59    // Visible for testing.
60    RequestManagerRetriever() {
61        handler = new Handler(Looper.getMainLooper(), this /* Callback */);
62    }
63
64    private RequestManager getApplicationManager(Context context) {
65        // Either an application context or we're on a background thread.
66        if (applicationManager == null) {
67            synchronized (this) {
68                if (applicationManager == null) {
69                    // Normally pause/resume is taken care of by the fragment we add to the fragment or activity.
70                    // However, in this case since the manager attached to the application will not receive lifecycle
71                    // events, we must force the manager to start resumed using ApplicationLifecycle.
72                    applicationManager = new RequestManager(context.getApplicationContext(),
73                            new ApplicationLifecycle());
74                }
75            }
76        }
77
78        return applicationManager;
79    }
80
81    public RequestManager get(Context context) {
82        if (context == null) {
83            throw new IllegalArgumentException("You cannot start a load on a null Context");
84        } else if (Util.isOnMainThread() && !(context instanceof Application)) {
85            if (context instanceof FragmentActivity) {
86                return get((FragmentActivity) context);
87            } else if (context instanceof Activity) {
88                return get((Activity) context);
89            } else if (context instanceof ContextWrapper) {
90                return get(((ContextWrapper) context).getBaseContext());
91            }
92        }
93
94        return getApplicationManager(context);
95    }
96
97    public RequestManager get(FragmentActivity activity) {
98        if (Util.isOnBackgroundThread()) {
99            return get(activity.getApplicationContext());
100        } else {
101            assertNotDestroyed(activity);
102            FragmentManager fm = activity.getSupportFragmentManager();
103            return supportFragmentGet(activity, fm);
104        }
105    }
106
107    public RequestManager get(Fragment fragment) {
108        if (fragment.getActivity() == null) {
109            throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
110        }
111        if (Util.isOnBackgroundThread()) {
112            return get(fragment.getActivity().getApplicationContext());
113        } else {
114            if (fragment.isDetached()) {
115                throw new IllegalArgumentException("You cannot start a load on a detached fragment");
116            }
117            FragmentManager fm = fragment.getChildFragmentManager();
118            return supportFragmentGet(fragment.getActivity(), fm);
119        }
120    }
121
122    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
123    public RequestManager get(Activity activity) {
124        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
125            return get(activity.getApplicationContext());
126        } else {
127            assertNotDestroyed(activity);
128            android.app.FragmentManager fm = activity.getFragmentManager();
129            return fragmentGet(activity, fm);
130        }
131    }
132
133    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
134    private static void assertNotDestroyed(Activity activity) {
135        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
136            throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
137        }
138    }
139
140    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
141    public RequestManager get(android.app.Fragment fragment) {
142        if (fragment.getActivity() == null) {
143            throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached");
144        }
145        if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
146            return get(fragment.getActivity().getApplicationContext());
147        } else {
148            assertNotDetached(fragment);
149            android.app.FragmentManager fm = fragment.getChildFragmentManager();
150            return fragmentGet(fragment.getActivity(), fm);
151        }
152    }
153
154    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
155    private static void assertNotDetached(android.app.Fragment fragment) {
156        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2 && fragment.isDetached()) {
157            throw new IllegalArgumentException("You cannot start a load on a detached fragment");
158        }
159    }
160
161    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
162    RequestManager fragmentGet(Context context, final android.app.FragmentManager fm) {
163        RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(TAG);
164        if (current == null) {
165            current = pendingRequestManagerFragments.get(fm);
166            if (current == null) {
167                current = new RequestManagerFragment();
168                pendingRequestManagerFragments.put(fm, current);
169                fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
170                handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
171            }
172        }
173        RequestManager requestManager = current.getRequestManager();
174        if (requestManager == null) {
175            requestManager = new RequestManager(context, current.getLifecycle());
176            current.setRequestManager(requestManager);
177        }
178        return requestManager;
179
180    }
181
182    RequestManager supportFragmentGet(Context context, final FragmentManager fm) {
183        SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(TAG);
184        if (current == null) {
185            current = pendingSupportRequestManagerFragments.get(fm);
186            if (current == null) {
187                current = new SupportRequestManagerFragment();
188                pendingSupportRequestManagerFragments.put(fm, current);
189                fm.beginTransaction().add(current, TAG).commitAllowingStateLoss();
190                handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
191            }
192        }
193        RequestManager requestManager = current.getRequestManager();
194        if (requestManager == null) {
195            requestManager = new RequestManager(context, current.getLifecycle());
196            current.setRequestManager(requestManager);
197        }
198        return requestManager;
199    }
200
201    @Override
202    public boolean handleMessage(Message message) {
203        boolean handled = true;
204        Object removed = null;
205        Object key = null;
206        switch (message.what) {
207            case ID_REMOVE_FRAGMENT_MANAGER:
208                android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
209                key = fm;
210                removed = pendingRequestManagerFragments.remove(fm);
211                break;
212            case ID_REMOVE_SUPPORT_FRAGMENT_MANAGER:
213                FragmentManager supportFm = (FragmentManager) message.obj;
214                key = supportFm;
215                removed = pendingSupportRequestManagerFragments.remove(supportFm);
216                break;
217            default:
218                handled = false;
219        }
220        if (handled && removed == null && Log.isLoggable(TAG, Log.WARN)) {
221            Log.w(TAG, "Failed to remove expected request manager fragment, manager: " + key);
222        }
223        return handled;
224    }
225}
226