1/*
2 * Copyright (C) 2015 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.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentSender;
24import android.os.Bundle;
25import android.os.Handler;
26import android.util.ArrayMap;
27import android.view.LayoutInflater;
28import android.view.View;
29
30import java.io.FileDescriptor;
31import java.io.PrintWriter;
32
33/**
34 * Integration points with the Fragment host.
35 * <p>
36 * Fragments may be hosted by any object; such as an {@link Activity}. In order to
37 * host fragments, implement {@link FragmentHostCallback}, overriding the methods
38 * applicable to the host.
39 */
40public abstract class FragmentHostCallback<E> extends FragmentContainer {
41    private final Activity mActivity;
42    final Context mContext;
43    private final Handler mHandler;
44    final int mWindowAnimations;
45    final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
46    /** The loader managers for individual fragments [i.e. Fragment#getLoaderManager()] */
47    private ArrayMap<String, LoaderManager> mAllLoaderManagers;
48    /** Whether or not fragment loaders should retain their state */
49    private boolean mRetainLoaders;
50    /** The loader manger for the fragment host [i.e. Activity#getLoaderManager()] */
51    private LoaderManagerImpl mLoaderManager;
52    private boolean mCheckedForLoaderManager;
53    /** Whether or not the fragment host loader manager was started */
54    private boolean mLoadersStarted;
55
56    public FragmentHostCallback(Context context, Handler handler, int windowAnimations) {
57        this(null /*activity*/, context, handler, windowAnimations);
58    }
59
60    FragmentHostCallback(Activity activity) {
61        this(activity, activity /*context*/, activity.mHandler, 0 /*windowAnimations*/);
62    }
63
64    FragmentHostCallback(Activity activity, Context context, Handler handler,
65            int windowAnimations) {
66        mActivity = activity;
67        mContext = context;
68        mHandler = handler;
69        mWindowAnimations = windowAnimations;
70    }
71
72    /**
73     * Print internal state into the given stream.
74     *
75     * @param prefix Desired prefix to prepend at each line of output.
76     * @param fd The raw file descriptor that the dump is being sent to.
77     * @param writer The PrintWriter to which you should dump your state. This will be closed
78     *                  for you after you return.
79     * @param args additional arguments to the dump request.
80     */
81    public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
82    }
83
84    /**
85     * Return {@code true} if the fragment's state needs to be saved.
86     */
87    public boolean onShouldSaveFragmentState(Fragment fragment) {
88        return true;
89    }
90
91    /**
92     * Return a {@link LayoutInflater}.
93     * See {@link Activity#getLayoutInflater()}.
94     */
95    public LayoutInflater onGetLayoutInflater() {
96        return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
97    }
98
99    /**
100     * Return {@code true} if the FragmentManager's LayoutInflaterFactory should be used.
101     */
102    public boolean onUseFragmentManagerInflaterFactory() {
103        return false;
104    }
105
106    /**
107     * Return the object that's currently hosting the fragment. If a {@link Fragment}
108     * is hosted by a {@link Activity}, the object returned here should be the same
109     * object returned from {@link Fragment#getActivity()}.
110     */
111    @Nullable
112    public abstract E onGetHost();
113
114    /**
115     * Invalidates the activity's options menu.
116     * See {@link Activity#invalidateOptionsMenu()}
117     */
118    public void onInvalidateOptionsMenu() {
119    }
120
121    /**
122     * Starts a new {@link Activity} from the given fragment.
123     * See {@link Activity#startActivityForResult(Intent, int)}.
124     */
125    public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode,
126            Bundle options) {
127        if (requestCode != -1) {
128            throw new IllegalStateException(
129                    "Starting activity with a requestCode requires a FragmentActivity host");
130        }
131        mContext.startActivity(intent);
132    }
133
134    /**
135     * Starts a new {@link IntentSender} from the given fragment.
136     * See {@link Activity#startIntentSender(IntentSender, Intent, int, int, int, Bundle)}.
137     */
138    public void onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent,
139            int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
140            int extraFlags, Bundle options) throws IntentSender.SendIntentException {
141        if (requestCode != -1) {
142            throw new IllegalStateException(
143                    "Starting intent sender with a requestCode requires a FragmentActivity host");
144        }
145        mContext.startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags,
146                options);
147    }
148
149    /**
150     * Requests permissions from the given fragment.
151     * See {@link Activity#requestPermissions(String[], int)}
152     */
153    public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
154            @NonNull String[] permissions, int requestCode) {
155    }
156
157    /**
158     * Return {@code true} if there are window animations.
159     */
160    public boolean onHasWindowAnimations() {
161        return true;
162    }
163
164    /**
165     * Return the window animations.
166     */
167    public int onGetWindowAnimations() {
168        return mWindowAnimations;
169    }
170
171    /**
172     * Called when a {@link Fragment} is being attached to this host, immediately
173     * after the call to its {@link Fragment#onAttach(Context)} method and before
174     * {@link Fragment#onCreate(Bundle)}.
175     */
176    public void onAttachFragment(Fragment fragment) {
177    }
178
179    @Nullable
180    @Override
181    public View onFindViewById(int id) {
182        return null;
183    }
184
185    @Override
186    public boolean onHasView() {
187        return true;
188    }
189
190    boolean getRetainLoaders() {
191        return mRetainLoaders;
192    }
193
194    Activity getActivity() {
195        return mActivity;
196    }
197
198    Context getContext() {
199        return mContext;
200    }
201
202    Handler getHandler() {
203        return mHandler;
204    }
205
206    FragmentManagerImpl getFragmentManagerImpl() {
207        return mFragmentManager;
208    }
209
210    LoaderManagerImpl getLoaderManagerImpl() {
211        if (mLoaderManager != null) {
212            return mLoaderManager;
213        }
214        mCheckedForLoaderManager = true;
215        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/);
216        return mLoaderManager;
217    }
218
219    void inactivateFragment(String who) {
220        //Log.v(TAG, "invalidateSupportFragment: who=" + who);
221        if (mAllLoaderManagers != null) {
222            LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who);
223            if (lm != null && !lm.mRetaining) {
224                lm.doDestroy();
225                mAllLoaderManagers.remove(who);
226            }
227        }
228    }
229
230    void doLoaderStart() {
231        if (mLoadersStarted) {
232            return;
233        }
234        mLoadersStarted = true;
235
236        if (mLoaderManager != null) {
237            mLoaderManager.doStart();
238        } else if (!mCheckedForLoaderManager) {
239            mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
240        }
241        mCheckedForLoaderManager = true;
242    }
243
244    void doLoaderStop(boolean retain) {
245        mRetainLoaders = retain;
246
247        if (mLoaderManager == null) {
248            return;
249        }
250
251        if (!mLoadersStarted) {
252            return;
253        }
254        mLoadersStarted = false;
255
256        if (retain) {
257            mLoaderManager.doRetain();
258        } else {
259            mLoaderManager.doStop();
260        }
261    }
262
263    void doLoaderRetain() {
264        if (mLoaderManager == null) {
265            return;
266        }
267        mLoaderManager.doRetain();
268    }
269
270    void doLoaderDestroy() {
271        if (mLoaderManager == null) {
272            return;
273        }
274        mLoaderManager.doDestroy();
275    }
276
277    void reportLoaderStart() {
278        if (mAllLoaderManagers != null) {
279            final int N = mAllLoaderManagers.size();
280            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
281            for (int i=N-1; i>=0; i--) {
282                loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i);
283            }
284            for (int i=0; i<N; i++) {
285                LoaderManagerImpl lm = loaders[i];
286                lm.finishRetain();
287                lm.doReportStart();
288            }
289        }
290    }
291
292    LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
293        if (mAllLoaderManagers == null) {
294            mAllLoaderManagers = new ArrayMap<String, LoaderManager>();
295        }
296        LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who);
297        if (lm == null) {
298            if (create) {
299                lm = new LoaderManagerImpl(who, this, started);
300                mAllLoaderManagers.put(who, lm);
301            }
302        } else {
303            lm.updateHostController(this);
304        }
305        return lm;
306    }
307
308    ArrayMap<String, LoaderManager> retainLoaderNonConfig() {
309        boolean retainLoaders = false;
310        if (mAllLoaderManagers != null) {
311            // Restart any loader managers that were already stopped so that they
312            // will be ready to retain
313            final int N = mAllLoaderManagers.size();
314            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
315            for (int i=N-1; i>=0; i--) {
316                loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i);
317            }
318            final boolean doRetainLoaders = getRetainLoaders();
319            for (int i=0; i<N; i++) {
320                LoaderManagerImpl lm = loaders[i];
321                if (!lm.mRetaining && doRetainLoaders) {
322                    if (!lm.mStarted) {
323                        lm.doStart();
324                    }
325                    lm.doRetain();
326                }
327                if (lm.mRetaining) {
328                    retainLoaders = true;
329                } else {
330                    lm.doDestroy();
331                    mAllLoaderManagers.remove(lm.mWho);
332                }
333            }
334        }
335
336        if (retainLoaders) {
337            return mAllLoaderManagers;
338        }
339        return null;
340    }
341
342    void restoreLoaderNonConfig(ArrayMap<String, LoaderManager> loaderManagers) {
343        mAllLoaderManagers = loaderManagers;
344    }
345
346    void dumpLoaders(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
347        writer.print(prefix); writer.print("mLoadersStarted=");
348        writer.println(mLoadersStarted);
349        if (mLoaderManager != null) {
350            writer.print(prefix); writer.print("Loader Manager ");
351            writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager)));
352            writer.println(":");
353            mLoaderManager.dump(prefix + "  ", fd, writer, args);
354        }
355    }
356}
357