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