BackStackRecord.java revision eedc67283a5a49dce86c625e54596dfdea9465a7
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v4.app; 18 19import android.os.Parcel; 20import android.os.Parcelable; 21import android.text.TextUtils; 22import android.util.Log; 23 24import java.io.FileDescriptor; 25import java.io.PrintWriter; 26import java.util.ArrayList; 27 28final class BackStackState implements Parcelable { 29 final int[] mOps; 30 final int mTransition; 31 final int mTransitionStyle; 32 final String mName; 33 final int mIndex; 34 final int mBreadCrumbTitleRes; 35 final CharSequence mBreadCrumbTitleText; 36 final int mBreadCrumbShortTitleRes; 37 final CharSequence mBreadCrumbShortTitleText; 38 39 public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { 40 int numRemoved = 0; 41 BackStackRecord.Op op = bse.mHead; 42 while (op != null) { 43 if (op.removed != null) numRemoved += op.removed.size(); 44 op = op.next; 45 } 46 mOps = new int[bse.mNumOp*5 + numRemoved]; 47 48 if (!bse.mAddToBackStack) { 49 throw new IllegalStateException("Not on back stack"); 50 } 51 52 op = bse.mHead; 53 int pos = 0; 54 while (op != null) { 55 mOps[pos++] = op.cmd; 56 mOps[pos++] = op.fragment.mIndex; 57 mOps[pos++] = op.enterAnim; 58 mOps[pos++] = op.exitAnim; 59 if (op.removed != null) { 60 final int N = op.removed.size(); 61 mOps[pos++] = N; 62 for (int i=0; i<N; i++) { 63 mOps[pos++] = op.removed.get(i).mIndex; 64 } 65 } else { 66 mOps[pos++] = 0; 67 } 68 op = op.next; 69 } 70 mTransition = bse.mTransition; 71 mTransitionStyle = bse.mTransitionStyle; 72 mName = bse.mName; 73 mIndex = bse.mIndex; 74 mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes; 75 mBreadCrumbTitleText = bse.mBreadCrumbTitleText; 76 mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; 77 mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; 78 } 79 80 public BackStackState(Parcel in) { 81 mOps = in.createIntArray(); 82 mTransition = in.readInt(); 83 mTransitionStyle = in.readInt(); 84 mName = in.readString(); 85 mIndex = in.readInt(); 86 mBreadCrumbTitleRes = in.readInt(); 87 mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 88 mBreadCrumbShortTitleRes = in.readInt(); 89 mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 90 } 91 92 public BackStackRecord instantiate(FragmentManagerImpl fm) { 93 BackStackRecord bse = new BackStackRecord(fm); 94 int pos = 0; 95 while (pos < mOps.length) { 96 BackStackRecord.Op op = new BackStackRecord.Op(); 97 op.cmd = mOps[pos++]; 98 if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, 99 "BSE " + bse + " set base fragment #" + mOps[pos]); 100 Fragment f = fm.mActive.get(mOps[pos++]); 101 op.fragment = f; 102 op.enterAnim = mOps[pos++]; 103 op.exitAnim = mOps[pos++]; 104 final int N = mOps[pos++]; 105 if (N > 0) { 106 op.removed = new ArrayList<Fragment>(N); 107 for (int i=0; i<N; i++) { 108 if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, 109 "BSE " + bse + " set remove fragment #" + mOps[pos]); 110 Fragment r = fm.mActive.get(mOps[pos++]); 111 op.removed.add(r); 112 } 113 } 114 bse.addOp(op); 115 } 116 bse.mTransition = mTransition; 117 bse.mTransitionStyle = mTransitionStyle; 118 bse.mName = mName; 119 bse.mIndex = mIndex; 120 bse.mAddToBackStack = true; 121 bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes; 122 bse.mBreadCrumbTitleText = mBreadCrumbTitleText; 123 bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; 124 bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; 125 bse.bumpBackStackNesting(1); 126 return bse; 127 } 128 129 public int describeContents() { 130 return 0; 131 } 132 133 public void writeToParcel(Parcel dest, int flags) { 134 dest.writeIntArray(mOps); 135 dest.writeInt(mTransition); 136 dest.writeInt(mTransitionStyle); 137 dest.writeString(mName); 138 dest.writeInt(mIndex); 139 dest.writeInt(mBreadCrumbTitleRes); 140 TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); 141 dest.writeInt(mBreadCrumbShortTitleRes); 142 TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); 143 } 144 145 public static final Parcelable.Creator<BackStackState> CREATOR 146 = new Parcelable.Creator<BackStackState>() { 147 public BackStackState createFromParcel(Parcel in) { 148 return new BackStackState(in); 149 } 150 151 public BackStackState[] newArray(int size) { 152 return new BackStackState[size]; 153 } 154 }; 155} 156 157/** 158 * @hide Entry of an operation on the fragment back stack. 159 */ 160final class BackStackRecord extends FragmentTransaction implements 161 FragmentManager.BackStackEntry, Runnable { 162 static final String TAG = "BackStackEntry"; 163 164 final FragmentManagerImpl mManager; 165 166 static final int OP_NULL = 0; 167 static final int OP_ADD = 1; 168 static final int OP_REPLACE = 2; 169 static final int OP_REMOVE = 3; 170 static final int OP_HIDE = 4; 171 static final int OP_SHOW = 5; 172 static final int OP_DETACH = 6; 173 static final int OP_ATTACH = 7; 174 175 static final class Op { 176 Op next; 177 Op prev; 178 int cmd; 179 Fragment fragment; 180 int enterAnim; 181 int exitAnim; 182 ArrayList<Fragment> removed; 183 } 184 185 Op mHead; 186 Op mTail; 187 int mNumOp; 188 int mEnterAnim; 189 int mExitAnim; 190 int mTransition; 191 int mTransitionStyle; 192 boolean mAddToBackStack; 193 boolean mAllowAddToBackStack = true; 194 String mName; 195 boolean mCommitted; 196 int mIndex; 197 198 int mBreadCrumbTitleRes; 199 CharSequence mBreadCrumbTitleText; 200 int mBreadCrumbShortTitleRes; 201 CharSequence mBreadCrumbShortTitleText; 202 203 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 204 writer.print(prefix); writer.print("mName="); writer.print(mName); 205 writer.print(" mIndex="); writer.print(mIndex); 206 writer.print(" mCommitted="); writer.println(mCommitted); 207 if (mTransition != FragmentTransaction.TRANSIT_NONE) { 208 writer.print(prefix); writer.print("mTransition=#"); 209 writer.print(Integer.toHexString(mTransition)); 210 writer.print(" mTransitionStyle=#"); 211 writer.println(Integer.toHexString(mTransitionStyle)); 212 } 213 if (mEnterAnim != 0 || mExitAnim !=0) { 214 writer.print(prefix); writer.print("mEnterAnim=#"); 215 writer.print(Integer.toHexString(mEnterAnim)); 216 writer.print(" mExitAnim=#"); 217 writer.println(Integer.toHexString(mExitAnim)); 218 } 219 if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { 220 writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); 221 writer.print(Integer.toHexString(mBreadCrumbTitleRes)); 222 writer.print(" mBreadCrumbTitleText="); 223 writer.println(mBreadCrumbTitleText); 224 } 225 if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { 226 writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); 227 writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); 228 writer.print(" mBreadCrumbShortTitleText="); 229 writer.println(mBreadCrumbShortTitleText); 230 } 231 232 if (mHead != null) { 233 writer.print(prefix); writer.println("Operations:"); 234 String innerPrefix = prefix + " "; 235 Op op = mHead; 236 int num = 0; 237 while (op != null) { 238 writer.print(prefix); writer.print(" Op #"); writer.print(num); 239 writer.println(":"); 240 writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd); 241 writer.print(" fragment="); writer.println(op.fragment); 242 if (op.enterAnim != 0 || op.exitAnim != 0) { 243 writer.print(prefix); writer.print("enterAnim="); writer.print(op.enterAnim); 244 writer.print(" exitAnim="); writer.println(op.exitAnim); 245 } 246 if (op.removed != null && op.removed.size() > 0) { 247 for (int i=0; i<op.removed.size(); i++) { 248 writer.print(innerPrefix); 249 if (op.removed.size() == 1) { 250 writer.print("Removed: "); 251 } else { 252 writer.println("Removed:"); 253 writer.print(innerPrefix); writer.print(" #"); writer.print(num); 254 writer.print(": "); 255 } 256 writer.println(op.removed.get(i)); 257 } 258 } 259 op = op.next; 260 } 261 } 262 } 263 264 public BackStackRecord(FragmentManagerImpl manager) { 265 mManager = manager; 266 } 267 268 public int getId() { 269 return mIndex; 270 } 271 272 public int getBreadCrumbTitleRes() { 273 return mBreadCrumbTitleRes; 274 } 275 276 public int getBreadCrumbShortTitleRes() { 277 return mBreadCrumbShortTitleRes; 278 } 279 280 public CharSequence getBreadCrumbTitle() { 281 if (mBreadCrumbTitleRes != 0) { 282 return mManager.mActivity.getText(mBreadCrumbTitleRes); 283 } 284 return mBreadCrumbTitleText; 285 } 286 287 public CharSequence getBreadCrumbShortTitle() { 288 if (mBreadCrumbShortTitleRes != 0) { 289 return mManager.mActivity.getText(mBreadCrumbShortTitleRes); 290 } 291 return mBreadCrumbShortTitleText; 292 } 293 294 void addOp(Op op) { 295 if (mHead == null) { 296 mHead = mTail = op; 297 } else { 298 op.prev = mTail; 299 mTail.next = op; 300 mTail = op; 301 } 302 op.enterAnim = mEnterAnim; 303 op.exitAnim = mExitAnim; 304 mNumOp++; 305 } 306 307 public FragmentTransaction add(Fragment fragment, String tag) { 308 doAddOp(0, fragment, tag, OP_ADD); 309 return this; 310 } 311 312 public FragmentTransaction add(int containerViewId, Fragment fragment) { 313 doAddOp(containerViewId, fragment, null, OP_ADD); 314 return this; 315 } 316 317 public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { 318 doAddOp(containerViewId, fragment, tag, OP_ADD); 319 return this; 320 } 321 322 private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { 323 if (fragment.mImmediateActivity != null) { 324 throw new IllegalStateException("Fragment already added: " + fragment); 325 } 326 fragment.mImmediateActivity = mManager.mActivity; 327 fragment.mFragmentManager = mManager; 328 329 if (tag != null) { 330 if (fragment.mTag != null && !tag.equals(fragment.mTag)) { 331 throw new IllegalStateException("Can't change tag of fragment " 332 + fragment + ": was " + fragment.mTag 333 + " now " + tag); 334 } 335 fragment.mTag = tag; 336 } 337 338 if (containerViewId != 0) { 339 if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { 340 throw new IllegalStateException("Can't change container ID of fragment " 341 + fragment + ": was " + fragment.mFragmentId 342 + " now " + containerViewId); 343 } 344 fragment.mContainerId = fragment.mFragmentId = containerViewId; 345 } 346 347 Op op = new Op(); 348 op.cmd = opcmd; 349 op.fragment = fragment; 350 addOp(op); 351 } 352 353 public FragmentTransaction replace(int containerViewId, Fragment fragment) { 354 return replace(containerViewId, fragment, null); 355 } 356 357 public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { 358 if (containerViewId == 0) { 359 throw new IllegalArgumentException("Must use non-zero containerViewId"); 360 } 361 362 doAddOp(containerViewId, fragment, tag, OP_REPLACE); 363 return this; 364 } 365 366 public FragmentTransaction remove(Fragment fragment) { 367 if (fragment.mImmediateActivity == null) { 368 throw new IllegalStateException("Fragment not added: " + fragment); 369 } 370 fragment.mImmediateActivity = null; 371 372 Op op = new Op(); 373 op.cmd = OP_REMOVE; 374 op.fragment = fragment; 375 addOp(op); 376 377 return this; 378 } 379 380 public FragmentTransaction hide(Fragment fragment) { 381 if (fragment.mImmediateActivity == null) { 382 throw new IllegalStateException("Fragment not added: " + fragment); 383 } 384 385 Op op = new Op(); 386 op.cmd = OP_HIDE; 387 op.fragment = fragment; 388 addOp(op); 389 390 return this; 391 } 392 393 public FragmentTransaction show(Fragment fragment) { 394 if (fragment.mImmediateActivity == null) { 395 throw new IllegalStateException("Fragment not added: " + fragment); 396 } 397 398 Op op = new Op(); 399 op.cmd = OP_SHOW; 400 op.fragment = fragment; 401 addOp(op); 402 403 return this; 404 } 405 406 public FragmentTransaction detach(Fragment fragment) { 407 //if (fragment.mImmediateActivity == null) { 408 // throw new IllegalStateException("Fragment not added: " + fragment); 409 //} 410 411 Op op = new Op(); 412 op.cmd = OP_DETACH; 413 op.fragment = fragment; 414 addOp(op); 415 416 return this; 417 } 418 419 public FragmentTransaction attach(Fragment fragment) { 420 //if (fragment.mImmediateActivity == null) { 421 // throw new IllegalStateException("Fragment not added: " + fragment); 422 //} 423 424 Op op = new Op(); 425 op.cmd = OP_ATTACH; 426 op.fragment = fragment; 427 addOp(op); 428 429 return this; 430 } 431 432 public FragmentTransaction setCustomAnimations(int enter, int exit) { 433 mEnterAnim = enter; 434 mExitAnim = exit; 435 return this; 436 } 437 438 public FragmentTransaction setTransition(int transition) { 439 mTransition = transition; 440 return this; 441 } 442 443 public FragmentTransaction setTransitionStyle(int styleRes) { 444 mTransitionStyle = styleRes; 445 return this; 446 } 447 448 public FragmentTransaction addToBackStack(String name) { 449 if (!mAllowAddToBackStack) { 450 throw new IllegalStateException( 451 "This FragmentTransaction is not allowed to be added to the back stack."); 452 } 453 mAddToBackStack = true; 454 mName = name; 455 return this; 456 } 457 458 public boolean isAddToBackStackAllowed() { 459 return mAllowAddToBackStack; 460 } 461 462 public FragmentTransaction disallowAddToBackStack() { 463 if (mAddToBackStack) { 464 throw new IllegalStateException( 465 "This transaction is already being added to the back stack"); 466 } 467 mAllowAddToBackStack = false; 468 return this; 469 } 470 471 public FragmentTransaction setBreadCrumbTitle(int res) { 472 mBreadCrumbTitleRes = res; 473 mBreadCrumbTitleText = null; 474 return this; 475 } 476 477 public FragmentTransaction setBreadCrumbTitle(CharSequence text) { 478 mBreadCrumbTitleRes = 0; 479 mBreadCrumbTitleText = text; 480 return this; 481 } 482 483 public FragmentTransaction setBreadCrumbShortTitle(int res) { 484 mBreadCrumbShortTitleRes = res; 485 mBreadCrumbShortTitleText = null; 486 return this; 487 } 488 489 public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) { 490 mBreadCrumbShortTitleRes = 0; 491 mBreadCrumbShortTitleText = text; 492 return this; 493 } 494 495 void bumpBackStackNesting(int amt) { 496 if (!mAddToBackStack) { 497 return; 498 } 499 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this 500 + " by " + amt); 501 Op op = mHead; 502 while (op != null) { 503 op.fragment.mBackStackNesting += amt; 504 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 505 + op.fragment + " to " + op.fragment.mBackStackNesting); 506 if (op.removed != null) { 507 for (int i=op.removed.size()-1; i>=0; i--) { 508 Fragment r = op.removed.get(i); 509 r.mBackStackNesting += amt; 510 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 511 + r + " to " + r.mBackStackNesting); 512 } 513 } 514 op = op.next; 515 } 516 } 517 518 public int commit() { 519 return commitInternal(false); 520 } 521 522 public int commitAllowingStateLoss() { 523 return commitInternal(true); 524 } 525 526 int commitInternal(boolean allowStateLoss) { 527 if (mCommitted) throw new IllegalStateException("commit already called"); 528 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this); 529 mCommitted = true; 530 if (mAddToBackStack) { 531 mIndex = mManager.allocBackStackIndex(this); 532 } else { 533 mIndex = -1; 534 } 535 mManager.enqueueAction(this, allowStateLoss); 536 return mIndex; 537 } 538 539 public void run() { 540 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this); 541 542 if (mAddToBackStack) { 543 if (mIndex < 0) { 544 throw new IllegalStateException("addToBackStack() called after commit()"); 545 } 546 } 547 548 bumpBackStackNesting(1); 549 550 Op op = mHead; 551 while (op != null) { 552 switch (op.cmd) { 553 case OP_ADD: { 554 Fragment f = op.fragment; 555 f.mNextAnim = op.enterAnim; 556 mManager.addFragment(f, false); 557 } break; 558 case OP_REPLACE: { 559 Fragment f = op.fragment; 560 if (mManager.mAdded != null) { 561 for (int i=0; i<mManager.mAdded.size(); i++) { 562 Fragment old = mManager.mAdded.get(i); 563 if (FragmentManagerImpl.DEBUG) Log.v(TAG, 564 "OP_REPLACE: adding=" + f + " old=" + old); 565 if (old.mContainerId == f.mContainerId) { 566 if (op.removed == null) { 567 op.removed = new ArrayList<Fragment>(); 568 } 569 op.removed.add(old); 570 old.mNextAnim = op.exitAnim; 571 if (mAddToBackStack) { 572 old.mBackStackNesting += 1; 573 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 574 + old + " to " + old.mBackStackNesting); 575 } 576 mManager.removeFragment(old, mTransition, mTransitionStyle); 577 } 578 } 579 } 580 f.mNextAnim = op.enterAnim; 581 mManager.addFragment(f, false); 582 } break; 583 case OP_REMOVE: { 584 Fragment f = op.fragment; 585 f.mNextAnim = op.exitAnim; 586 mManager.removeFragment(f, mTransition, mTransitionStyle); 587 } break; 588 case OP_HIDE: { 589 Fragment f = op.fragment; 590 f.mNextAnim = op.exitAnim; 591 mManager.hideFragment(f, mTransition, mTransitionStyle); 592 } break; 593 case OP_SHOW: { 594 Fragment f = op.fragment; 595 f.mNextAnim = op.enterAnim; 596 mManager.showFragment(f, mTransition, mTransitionStyle); 597 } break; 598 case OP_DETACH: { 599 Fragment f = op.fragment; 600 f.mNextAnim = op.exitAnim; 601 mManager.detachFragment(f, mTransition, mTransitionStyle); 602 } break; 603 case OP_ATTACH: { 604 Fragment f = op.fragment; 605 f.mNextAnim = op.enterAnim; 606 mManager.attachFragment(f, mTransition, mTransitionStyle); 607 } break; 608 default: { 609 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 610 } 611 } 612 613 op = op.next; 614 } 615 616 mManager.moveToState(mManager.mCurState, mTransition, 617 mTransitionStyle, true); 618 619 if (mAddToBackStack) { 620 mManager.addBackStackState(this); 621 } 622 } 623 624 public void popFromBackStack(boolean doStateMove) { 625 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this); 626 627 bumpBackStackNesting(-1); 628 629 Op op = mTail; 630 while (op != null) { 631 switch (op.cmd) { 632 case OP_ADD: { 633 Fragment f = op.fragment; 634 f.mImmediateActivity = null; 635 mManager.removeFragment(f, 636 FragmentManagerImpl.reverseTransit(mTransition), 637 mTransitionStyle); 638 } break; 639 case OP_REPLACE: { 640 Fragment f = op.fragment; 641 f.mImmediateActivity = null; 642 mManager.removeFragment(f, 643 FragmentManagerImpl.reverseTransit(mTransition), 644 mTransitionStyle); 645 if (op.removed != null) { 646 for (int i=0; i<op.removed.size(); i++) { 647 Fragment old = op.removed.get(i); 648 f.mImmediateActivity = mManager.mActivity; 649 mManager.addFragment(old, false); 650 } 651 } 652 } break; 653 case OP_REMOVE: { 654 Fragment f = op.fragment; 655 f.mImmediateActivity = mManager.mActivity; 656 mManager.addFragment(f, false); 657 } break; 658 case OP_HIDE: { 659 Fragment f = op.fragment; 660 mManager.showFragment(f, 661 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 662 } break; 663 case OP_SHOW: { 664 Fragment f = op.fragment; 665 mManager.hideFragment(f, 666 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 667 } break; 668 case OP_DETACH: { 669 Fragment f = op.fragment; 670 mManager.attachFragment(f, 671 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 672 } break; 673 case OP_ATTACH: { 674 Fragment f = op.fragment; 675 mManager.detachFragment(f, 676 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 677 } break; 678 default: { 679 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 680 } 681 } 682 683 op = op.prev; 684 } 685 686 if (doStateMove) { 687 mManager.moveToState(mManager.mCurState, 688 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); 689 } 690 691 if (mIndex >= 0) { 692 mManager.freeBackStackIndex(mIndex); 693 mIndex = -1; 694 } 695 } 696 697 public String getName() { 698 return mName; 699 } 700 701 public int getTransition() { 702 return mTransition; 703 } 704 705 public int getTransitionStyle() { 706 return mTransitionStyle; 707 } 708 709 public boolean isEmpty() { 710 return mNumOp == 0; 711 } 712} 713