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