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  */
16 package android.support.car.app;
17 
18 import android.content.Context;
19 import android.content.Intent;
20 import android.content.res.Configuration;
21 import android.content.res.Resources;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.Message;
25 import android.os.Parcelable;
26 import android.support.annotation.NonNull;
27 import android.support.annotation.Nullable;
28 import android.support.car.Car;
29 import android.support.v4.app.Fragment;
30 import android.support.v4.app.FragmentController;
31 import android.support.v4.app.FragmentHostCallback;
32 import android.support.v4.app.FragmentManager;
33 import android.support.v4.app.LoaderManager;
34 import android.support.v4.util.SimpleArrayMap;
35 import android.util.AttributeSet;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.Menu;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.Window;
42 
43 import java.io.FileDescriptor;
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import 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  */
57 public class CarFragmentActivity extends CarActivity implements
58         CarActivity.RequestPermissionsRequestCodeValidator {
59 
CarFragmentActivity(Proxy proxy, Context context, Car car)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 
setContentFragment(Fragment fragment, int containerId)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
onActivityResult(int requestCode, int resultCode, Intent data)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      */
startActivityFromFragment(Fragment fragment, Intent intent, int requestCode)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
startActivityForResult(Intent intent, int requestCode)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      */
onBackPressed()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      */
supportFinishAfterTransition()196     public void supportFinishAfterTransition() {
197         super.finishAfterTransition();
198     }
199 
200     /**
201      * Dispatch configuration change to all fragments.
202      */
203     @Override
onConfigurationChanged(Configuration newConfig)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
onCreate(@ullable Bundle savedInstanceState)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
onCreatePanelMenu(int featureId, Menu menu)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 
dispatchFragmentsOnCreateView(View parent, String name, Context context, AttributeSet attrs)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
onCreateView(View parent, String name, Context context, AttributeSet attrs)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
onDestroy()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
onLowMemory()285     public void onLowMemory() {
286         mFragments.dispatchLowMemory();
287     }
288 
289     /**
290      * Dispatch onPause() to fragments.
291      */
292     @Override
onPause()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
onNewIntent(Intent intent)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      */
onStateNotSaved()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
onResume()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
onPostResume()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      */
onResumeFragments()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
onRetainNonConfigurationInstance()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
onSaveInstanceState(Bundle outState)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
onStart()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
onStop()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      */
onRetainCustomNonConfigurationInstance()452     public Object onRetainCustomNonConfigurationInstance() {
453         return null;
454     }
455 
456     /**
457      * Return the value previously returned from
458      * {@link #onRetainCustomNonConfigurationInstance()}.
459      */
460     @SuppressWarnings("deprecation")
getLastCustomNonConfigurationInstance()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      */
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)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 
viewToString(View view)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 
dumpViewHierarchy(String prefix, PrintWriter writer, View view)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 
doReallyStop(boolean retaining)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      */
onReallyStop()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      */
getFragmentIndex(Fragment f)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
validateRequestPermissionsRequestCode(int requestCode)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      */
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)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      */
requestPermissionsFromFragment(Fragment fragment, String[] permissions, int requestCode)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      */
getSupportFragmentManager()711     public FragmentManager getSupportFragmentManager() {
712         return mFragments.getSupportFragmentManager();
713     }
714 
715     class HostCallbacks extends FragmentHostCallback<CarFragmentActivity> {
HostCallbacks()716         public HostCallbacks() {
717             super(CarFragmentActivity.this.getContext(), mHandler, 0 /*window animation*/);
718         }
719 
720         @Override
onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)721         public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
722             CarFragmentActivity.this.dump(prefix, fd, writer, args);
723         }
724 
725         @Override
onShouldSaveFragmentState(Fragment fragment)726         public boolean onShouldSaveFragmentState(Fragment fragment) {
727             return !isFinishing();
728         }
729 
730         @Override
onGetLayoutInflater()731         public LayoutInflater onGetLayoutInflater() {
732             return CarFragmentActivity.this.getLayoutInflater()
733                     .cloneInContext(CarFragmentActivity.this.getContext());
734         }
735 
736         @Override
onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode)737         public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
738             CarFragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode);
739         }
740 
741         @Override
onRequestPermissionsFromFragment(@onNull Fragment fragment, @NonNull String[] permissions, int requestCode)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
onGetHost()749         public CarFragmentActivity onGetHost() {
750             return CarFragmentActivity.this;
751         }
752 
753         @Override
onHasWindowAnimations()754         public boolean onHasWindowAnimations() {
755             return getWindow() != null;
756         }
757 
758         @Override
onGetWindowAnimations()759         public int onGetWindowAnimations() {
760             final Window w = getWindow();
761             return (w == null) ? 0 : w.getAttributes().windowAnimations;
762         }
763 
764         @Nullable
765         @Override
onFindViewById(int id)766         public View onFindViewById(int id) {
767             return CarFragmentActivity.this.findViewById(id);
768         }
769 
770         @Override
onHasView()771         public boolean onHasView() {
772             final Window w = getWindow();
773             return (w != null && w.peekDecorView() != null);
774         }
775     }
776 }
777