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