Home | History | Annotate | Download | only in app
      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 
     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