1/*
2 * Copyright (C) 2016 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 */
16package android.support.car.app;
17
18import android.content.Context;
19import android.content.Intent;
20import android.content.res.Configuration;
21import android.content.res.Resources;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Message;
25import android.os.Parcelable;
26import android.support.annotation.NonNull;
27import android.support.annotation.Nullable;
28import android.support.car.Car;
29import android.support.v4.app.Fragment;
30import android.support.v4.app.FragmentController;
31import android.support.v4.app.FragmentHostCallback;
32import android.support.v4.app.FragmentManager;
33import android.support.v4.app.LoaderManager;
34import android.support.v4.util.SimpleArrayMap;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.view.LayoutInflater;
38import android.view.Menu;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42
43import java.io.FileDescriptor;
44import java.io.PrintWriter;
45import java.util.ArrayList;
46import java.util.List;
47
48/**
49 * This is mostly a copy of {@link android.support.v4.app.FragmentActivity}, so that fragments
50 * are hosted in a {@link android.support.car.app.CarActivity}.
51 *
52 * <p>Very often, we need to access the car activity inside a fragment, by calling
53 * (CarActivity) fragment.getHost(), so we cannot directly use fragments inside
54 * {@link android.support.v4.app.FragmentActivity} or any other proxy activity that backs
55 * a car activity </p>
56 */
57public class CarFragmentActivity extends CarActivity implements
58        CarActivity.RequestPermissionsRequestCodeValidator {
59
60    public CarFragmentActivity(Proxy proxy, Context context, Car car) {
61        super(proxy, context, car);
62    }
63
64    private static final String TAG = "CarFragmentActivity";
65
66    static final String FRAGMENTS_TAG = "android:support:car:fragments";
67
68    // This is the SDK API version of Honeycomb (3.0).
69    private static final int HONEYCOMB = 11;
70
71    static final int MSG_REALLY_STOPPED = 1;
72    static final int MSG_RESUME_PENDING = 2;
73
74    final Handler mHandler = new Handler() {
75        @Override
76        public void handleMessage(Message msg) {
77            switch (msg.what) {
78                case MSG_REALLY_STOPPED:
79                    if (mStopped) {
80                        doReallyStop(false);
81                    }
82                    break;
83                case MSG_RESUME_PENDING:
84                    onResumeFragments();
85                    mFragments.execPendingActions();
86                    break;
87                default:
88                    super.handleMessage(msg);
89            }
90        }
91
92    };
93    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
94
95    boolean mCreated;
96    boolean mResumed;
97    boolean mStopped;
98    boolean mReallyStopped;
99    boolean mRetaining;
100
101    boolean mRequestedPermissionsFromFragment;
102
103    static final class NonConfigurationInstances {
104        Object custom;
105        List<Fragment> fragments;
106        SimpleArrayMap<String, LoaderManager> loaders;
107    }
108
109    public void setContentFragment(Fragment fragment, int containerId) {
110        getSupportFragmentManager().beginTransaction()
111                .replace(containerId, fragment)
112                .commit();
113    }
114
115    // ------------------------------------------------------------------------
116    // HOOKS INTO ACTIVITY
117    // ------------------------------------------------------------------------
118
119    /**
120     * Dispatch incoming result to the correct fragment.
121     */
122    @Override
123    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
124        mFragments.noteStateNotSaved();
125        int index = requestCode>>16;
126        if (index > 0) {
127            index--;
128            final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
129            if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
130                Log.w(TAG, "Activity result fragment index out of range: 0x"
131                        + Integer.toHexString(requestCode));
132                return;
133            }
134            final List<Fragment> activeFragments =
135                    mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
136            Fragment frag = activeFragments.get(index);
137            if (frag == null) {
138                Log.w(TAG, "Activity result no fragment exists for index: 0x"
139                        + Integer.toHexString(requestCode));
140            } else {
141                frag.onActivityResult(requestCode&0xffff, resultCode, data);
142            }
143            return;
144        }
145
146        super.onActivityResult(requestCode, resultCode, data);
147    }
148
149    /**
150     * Called by Fragment.startActivityForResult() to implement its behavior.
151     */
152    public void startActivityFromFragment(Fragment fragment, Intent intent,
153                                          int requestCode) {
154        if (requestCode == -1) {
155            startActivityForResult(intent, -1);
156            return;
157        }
158        if ((requestCode&0xffff0000) != 0) {
159            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
160        }
161        super.startActivityForResult(intent,
162                ((getFragmentIndex(fragment)+1)<<16) + (requestCode&0xffff));
163    }
164
165    /**
166     * Modifies the standard behavior to allow results to be delivered to fragments.
167     * This imposes a restriction that requestCode be <= 0xffff.
168     */
169    @Override
170    public void startActivityForResult(Intent intent, int requestCode) {
171        if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
172            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
173        }
174        super.startActivityForResult(intent, requestCode);
175    }
176
177    /**
178     * Take care of popping the fragment back stack or finishing the activity
179     * as appropriate.
180     */
181    public void onBackPressed() {
182        if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
183            supportFinishAfterTransition();
184        }
185    }
186
187    /**
188     * Reverses the Activity Scene entry Transition and triggers the calling Activity
189     * to reverse its exit Transition. When the exit Transition completes,
190     * {@link #finish()} is called. If no entry Transition was used, finish() is called
191     * immediately and the Activity exit Transition is run.
192     *
193     * <p>On Android 4.4 or lower, this method only finishes the Activity with no
194     * special exit transition.</p>
195     */
196    public void supportFinishAfterTransition() {
197        super.finishAfterTransition();
198    }
199
200    /**
201     * Dispatch configuration change to all fragments.
202     */
203    @Override
204    public void onConfigurationChanged(Configuration newConfig) {
205        super.onConfigurationChanged(newConfig);
206        mFragments.dispatchConfigurationChanged(newConfig);
207    }
208
209    /**
210     * Perform initialization of all fragments and loaders.
211     */
212    @SuppressWarnings("deprecation")
213    @Override
214    protected void onCreate(@Nullable Bundle savedInstanceState) {
215        mFragments.attachHost(null /*parent*/);
216
217        super.onCreate(savedInstanceState);
218
219        NonConfigurationInstances nc =
220                (NonConfigurationInstances) getLastNonConfigurationInstance();
221        if (nc != null) {
222            mFragments.restoreLoaderNonConfig(nc.loaders);
223        }
224        if (savedInstanceState != null) {
225            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
226            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
227        }
228        mFragments.dispatchCreate();
229    }
230
231    /**
232     * Dispatch to Fragment.onCreateOptionsMenu().
233     */
234    @Override
235    public boolean onCreatePanelMenu(int featureId, Menu menu) {
236        if (featureId == Window.FEATURE_OPTIONS_PANEL && getMenuInflater() != null) {
237            boolean show = super.onCreatePanelMenu(featureId, menu);
238            show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
239            if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
240                return show;
241            }
242            // Prior to Honeycomb, the framework can't invalidate the options
243            // menu, so we must always say we have one in case the app later
244            // invalidates it and needs to have it shown.
245            return true;
246        }
247        return super.onCreatePanelMenu(featureId, menu);
248    }
249
250    final View dispatchFragmentsOnCreateView(View parent, String name, Context context,
251                                             AttributeSet attrs) {
252        return mFragments.onCreateView(parent, name, context, attrs);
253    }
254
255    @Override
256    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
257        if (!"fragment".equals(name)) {
258            return super.onCreateView(parent, name, context, attrs);
259        }
260
261        final View v = dispatchFragmentsOnCreateView(parent, name, context, attrs);
262        if (v == null) {
263            return super.onCreateView(parent, name, context, attrs);
264        }
265        return v;
266    }
267
268    /**
269     * Destroy all fragments and loaders.
270     */
271    @Override
272    protected void onDestroy() {
273        super.onDestroy();
274
275        doReallyStop(false);
276
277        mFragments.dispatchDestroy();
278        mFragments.doLoaderDestroy();
279    }
280
281    /**
282     * Dispatch onLowMemory() to all fragments.
283     */
284    @Override
285    public void onLowMemory() {
286        mFragments.dispatchLowMemory();
287    }
288
289    /**
290     * Dispatch onPause() to fragments.
291     */
292    @Override
293    protected void onPause() {
294        super.onPause();
295        mResumed = false;
296        if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
297            mHandler.removeMessages(MSG_RESUME_PENDING);
298            onResumeFragments();
299        }
300        mFragments.dispatchPause();
301    }
302
303    /**
304     * Handle onNewIntent() to inform the fragment manager that the
305     * state is not saved.  If you are handling new intents and may be
306     * making changes to the fragment state, you want to be sure to call
307     * through to the super-class here first.  Otherwise, if your state
308     * is saved but the activity is not stopped, you could get an
309     * onNewIntent() call which happens before onResume() and trying to
310     * perform fragment operations at that point will throw IllegalStateException
311     * because the fragment manager thinks the state is still saved.
312     */
313    @Override
314    protected void onNewIntent(Intent intent) {
315        super.onNewIntent(intent);
316        mFragments.noteStateNotSaved();
317    }
318
319    /**
320     * Hook in to note that fragment state is no longer saved.
321     */
322    public void onStateNotSaved() {
323        mFragments.noteStateNotSaved();
324    }
325
326    /**
327     * Dispatch onResume() to fragments.  Note that for better inter-operation
328     * with older versions of the platform, at the point of this call the
329     * fragments attached to the activity are <em>not</em> resumed.  This means
330     * that in some cases the previous state may still be saved, not allowing
331     * fragment transactions that modify the state.  To correctly interact
332     * with fragments in their proper state, you should instead override
333     * {@link #onResumeFragments()}.
334     */
335    @Override
336    protected void onResume() {
337        super.onResume();
338        mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
339        mResumed = true;
340        mFragments.execPendingActions();
341    }
342
343    /**
344     * Dispatch onResume() to fragments.
345     */
346    @Override
347    protected void onPostResume() {
348        super.onPostResume();
349        mHandler.removeMessages(MSG_RESUME_PENDING);
350        onResumeFragments();
351        mFragments.execPendingActions();
352    }
353
354    /**
355     * This is the fragment-orientated version of {@link #onResume()} that you
356     * can override to perform operations in the Activity at the same point
357     * where its fragments are resumed.  Be sure to always call through to
358     * the super-class.
359     */
360    protected void onResumeFragments() {
361        mFragments.dispatchResume();
362    }
363
364    /**
365     * Retain all appropriate fragment and loader state.  You can NOT
366     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
367     * if you want to retain your own state.
368     */
369    @Override
370    public final Object onRetainNonConfigurationInstance() {
371        if (mStopped) {
372            doReallyStop(true);
373        }
374
375        Object custom = onRetainCustomNonConfigurationInstance();
376
377        List<Fragment> fragments = mFragments.retainNonConfig();
378        SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
379
380        if (fragments == null && loaders == null && custom == null) {
381            return null;
382        }
383
384        NonConfigurationInstances nci = new NonConfigurationInstances();
385        nci.custom = custom;
386        nci.fragments = fragments;
387        nci.loaders = loaders;
388        return nci;
389    }
390
391    /**
392     * Save all appropriate fragment state.
393     */
394    @Override
395    protected void onSaveInstanceState(Bundle outState) {
396        super.onSaveInstanceState(outState);
397        Parcelable p = mFragments.saveAllState();
398        if (p != null) {
399            outState.putParcelable(FRAGMENTS_TAG, p);
400        }
401    }
402
403    /**
404     * Dispatch onStart() to all fragments.  Ensure any created loaders are
405     * now started.
406     */
407    @Override
408    protected void onStart() {
409        super.onStart();
410
411        mStopped = false;
412        mReallyStopped = false;
413        mHandler.removeMessages(MSG_REALLY_STOPPED);
414
415        if (!mCreated) {
416            mCreated = true;
417            mFragments.dispatchActivityCreated();
418        }
419
420        mFragments.noteStateNotSaved();
421        mFragments.execPendingActions();
422
423        mFragments.doLoaderStart();
424
425        // NOTE: HC onStart goes here.
426
427        mFragments.dispatchStart();
428        mFragments.reportLoaderStart();
429    }
430
431    /**
432     * Dispatch onStop() to all fragments.  Ensure all loaders are stopped.
433     */
434    @Override
435    protected void onStop() {
436        super.onStop();
437
438        mStopped = true;
439        mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
440
441        mFragments.dispatchStop();
442    }
443
444    // ------------------------------------------------------------------------
445    // NEW METHODS
446    // ------------------------------------------------------------------------
447
448    /**
449     * Use this instead of {@link #onRetainNonConfigurationInstance()}.
450     * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}.
451     */
452    public Object onRetainCustomNonConfigurationInstance() {
453        return null;
454    }
455
456    /**
457     * Return the value previously returned from
458     * {@link #onRetainCustomNonConfigurationInstance()}.
459     */
460    @SuppressWarnings("deprecation")
461    public Object getLastCustomNonConfigurationInstance() {
462        NonConfigurationInstances nc = (NonConfigurationInstances)
463                getLastNonConfigurationInstance();
464        return nc != null ? nc.custom : null;
465    }
466
467    /**
468     * Print the Activity's state into the given stream.  This gets invoked if
469     * you run "adb shell dumpsys activity <activity_component_name>".
470     *
471     * @param prefix Desired prefix to prepend at each line of output.
472     * @param fd The raw file descriptor that the dump is being sent to.
473     * @param writer The PrintWriter to which you should dump your state.  This will be
474     * closed for you after you return.
475     * @param args additional arguments to the dump request.
476     */
477    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
478        if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
479            // XXX This can only work if we can call the super-class impl. :/
480            //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args);
481        }
482        writer.print(prefix); writer.print("Local FragmentActivity ");
483        writer.print(Integer.toHexString(System.identityHashCode(this)));
484        writer.println(" State:");
485        String innerPrefix = prefix + "  ";
486        writer.print(innerPrefix); writer.print("mCreated=");
487        writer.print(mCreated); writer.print("mResumed=");
488        writer.print(mResumed); writer.print(" mStopped=");
489        writer.print(mStopped); writer.print(" mReallyStopped=");
490        writer.println(mReallyStopped);
491        mFragments.dumpLoaders(innerPrefix, fd, writer, args);
492        mFragments.getSupportFragmentManager().dump(prefix, fd, writer, args);
493        writer.print(prefix); writer.println("View Hierarchy:");
494        dumpViewHierarchy(prefix + "  ", writer, getWindow().getDecorView());
495    }
496
497    private static String viewToString(View view) {
498        StringBuilder out = new StringBuilder(128);
499        out.append(view.getClass().getName());
500        out.append('{');
501        out.append(Integer.toHexString(System.identityHashCode(view)));
502        out.append(' ');
503        switch (view.getVisibility()) {
504            case View.VISIBLE: out.append('V'); break;
505            case View.INVISIBLE: out.append('I'); break;
506            case View.GONE: out.append('G'); break;
507            default: out.append('.'); break;
508        }
509        out.append(view.isFocusable() ? 'F' : '.');
510        out.append(view.isEnabled() ? 'E' : '.');
511        out.append(view.willNotDraw() ? '.' : 'D');
512        out.append(view.isHorizontalScrollBarEnabled()? 'H' : '.');
513        out.append(view.isVerticalScrollBarEnabled() ? 'V' : '.');
514        out.append(view.isClickable() ? 'C' : '.');
515        out.append(view.isLongClickable() ? 'L' : '.');
516        out.append(' ');
517        out.append(view.isFocused() ? 'F' : '.');
518        out.append(view.isSelected() ? 'S' : '.');
519        out.append(view.isPressed() ? 'P' : '.');
520        out.append(' ');
521        out.append(view.getLeft());
522        out.append(',');
523        out.append(view.getTop());
524        out.append('-');
525        out.append(view.getRight());
526        out.append(',');
527        out.append(view.getBottom());
528        final int id = view.getId();
529        if (id != View.NO_ID) {
530            out.append(" #");
531            out.append(Integer.toHexString(id));
532            final Resources r = view.getResources();
533            if (id != 0 && r != null) {
534                try {
535                    String pkgname;
536                    switch (id&0xff000000) {
537                        case 0x7f000000:
538                            pkgname="app";
539                            break;
540                        case 0x01000000:
541                            pkgname="android";
542                            break;
543                        default:
544                            pkgname = r.getResourcePackageName(id);
545                            break;
546                    }
547                    String typename = r.getResourceTypeName(id);
548                    String entryname = r.getResourceEntryName(id);
549                    out.append(" ");
550                    out.append(pkgname);
551                    out.append(":");
552                    out.append(typename);
553                    out.append("/");
554                    out.append(entryname);
555                } catch (Resources.NotFoundException e) {
556                }
557            }
558        }
559        out.append("}");
560        return out.toString();
561    }
562
563    private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
564        writer.print(prefix);
565        if (view == null) {
566            writer.println("null");
567            return;
568        }
569        writer.println(viewToString(view));
570        if (!(view instanceof ViewGroup)) {
571            return;
572        }
573        ViewGroup grp = (ViewGroup)view;
574        final int N = grp.getChildCount();
575        if (N <= 0) {
576            return;
577        }
578        prefix = prefix + "  ";
579        for (int i=0; i<N; i++) {
580            dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
581        }
582    }
583
584    void doReallyStop(boolean retaining) {
585        if (!mReallyStopped) {
586            mReallyStopped = true;
587            mRetaining = retaining;
588            mHandler.removeMessages(MSG_REALLY_STOPPED);
589            onReallyStop();
590        }
591    }
592
593    /**
594     * Pre-HC, we didn't have a way to determine whether an activity was
595     * being stopped for a config change or not until we saw
596     * onRetainNonConfigurationInstance() called after onStop().  However
597     * we need to know this, to know whether to retain fragments.  This will
598     * tell us what we need to know.
599     */
600    void onReallyStop() {
601        mFragments.doLoaderStop(mRetaining);
602
603        mFragments.dispatchReallyStop();
604    }
605
606    // ------------------------------------------------------------------------
607    // FRAGMENT SUPPORT
608    // ------------------------------------------------------------------------
609
610    /**
611     * Returns the index of a fragment inside {@link #mFragments}.
612     *
613     * This is a workaround for getting {@link android.support.v4.app.Fragment}'s internal index,
614     * which is package visible.
615     */
616    private int getFragmentIndex(Fragment f) {
617        List<Fragment> fragments = mFragments.getActiveFragments(null);
618        int index = 0;
619        boolean found = false;
620        for (Fragment frag : fragments) {
621            if (frag == f) {
622                found = true;
623                break;
624            }
625            index++;
626        }
627        if (found) {
628            return index;
629        }
630        return -1;
631    }
632
633    @Override
634    public final void validateRequestPermissionsRequestCode(int requestCode) {
635        // We use 8 bits of the request code to encode the fragment id when
636        // requesting permissions from a fragment. Hence, requestPermissions()
637        // should validate the code against that but we cannot override it as
638        // we can not then call super and also the ActivityCompat would call
639        // back to this override. To handle this we use dependency inversion
640        // where we are the validator of request codes when requesting
641        // permissions in ActivityCompat.
642        if (mRequestedPermissionsFromFragment) {
643            mRequestedPermissionsFromFragment = false;
644        } else if ((requestCode & 0xffffff00) != 0) {
645            throw new IllegalArgumentException("Can only use lower 8 bits for requestCode");
646        }
647    }
648
649    /**
650     * Callback for the result from requesting permissions. This method
651     * is invoked for every call on {@link #requestPermissions(String[], int)}.
652     * <p>
653     * <strong>Note:</strong> It is possible that the permissions request interaction
654     * with the user is interrupted. In this case you will receive empty permissions
655     * and results arrays which should be treated as a cancellation.
656     * </p>
657     *
658     * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
659     * @param permissions The requested permissions. Never null.
660     * @param grantResults The grant results for the corresponding permissions
661     *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
662     *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
663     *
664     * @see #requestPermissions(String[], int)
665     */
666    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
667                                           @NonNull int[] grantResults) {
668        int index = (requestCode>>8)&0xff;
669        if (index != 0) {
670            index--;
671            final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
672            if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
673                Log.w(TAG, "Activity result fragment index out of range: 0x"
674                        + Integer.toHexString(requestCode));
675                return;
676            }
677            final List<Fragment> activeFragments =
678                    mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
679            Fragment frag = activeFragments.get(index);
680            if (frag == null) {
681                Log.w(TAG, "Activity result no fragment exists for index: 0x"
682                        + Integer.toHexString(requestCode));
683            } else {
684                frag.onRequestPermissionsResult(requestCode&0xff, permissions, grantResults);
685            }
686        }
687    }
688
689    /**
690     * Called by Fragment.requestPermissions() to implement its behavior.
691     */
692    private void requestPermissionsFromFragment(Fragment fragment, String[] permissions,
693                                                int requestCode) {
694        if (requestCode == -1) {
695            super.requestPermissions(permissions, requestCode);
696            return;
697        }
698
699        if ((requestCode&0xffffff00) != 0) {
700            throw new IllegalArgumentException("Can only use lower 8 bits for requestCode");
701        }
702        mRequestedPermissionsFromFragment = true;
703        super.requestPermissions(permissions,
704                ((getFragmentIndex(fragment) + 1) << 8) + (requestCode & 0xff));
705    }
706
707    /**
708     * Return the FragmentManager for interacting with fragments associated
709     * with this activity.
710     */
711    public FragmentManager getSupportFragmentManager() {
712        return mFragments.getSupportFragmentManager();
713    }
714
715    class HostCallbacks extends FragmentHostCallback<CarFragmentActivity> {
716        public HostCallbacks() {
717            super(CarFragmentActivity.this.getContext(), mHandler, 0 /*window animation*/);
718        }
719
720        @Override
721        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
722            CarFragmentActivity.this.dump(prefix, fd, writer, args);
723        }
724
725        @Override
726        public boolean onShouldSaveFragmentState(Fragment fragment) {
727            return !isFinishing();
728        }
729
730        @Override
731        public LayoutInflater onGetLayoutInflater() {
732            return CarFragmentActivity.this.getLayoutInflater()
733                    .cloneInContext(CarFragmentActivity.this.getContext());
734        }
735
736        @Override
737        public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
738            CarFragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode);
739        }
740
741        @Override
742        public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
743                @NonNull String[] permissions, int requestCode) {
744            CarFragmentActivity.this.requestPermissionsFromFragment(fragment,
745                    permissions, requestCode);
746        }
747
748        @Override
749        public CarFragmentActivity onGetHost() {
750            return CarFragmentActivity.this;
751        }
752
753        @Override
754        public boolean onHasWindowAnimations() {
755            return getWindow() != null;
756        }
757
758        @Override
759        public int onGetWindowAnimations() {
760            final Window w = getWindow();
761            return (w == null) ? 0 : w.getAttributes().windowAnimations;
762        }
763
764        @Nullable
765        @Override
766        public View onFindViewById(int id) {
767            return CarFragmentActivity.this.findViewById(id);
768        }
769
770        @Override
771        public boolean onHasView() {
772            final Window w = getWindow();
773            return (w != null && w.peekDecorView() != null);
774        }
775    }
776}
777