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