1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.chrome.browser.tabmodel; 6 7import android.os.SystemClock; 8 9import org.chromium.base.CalledByNative; 10import org.chromium.base.ObserverList; 11import org.chromium.base.TraceEvent; 12import org.chromium.chrome.browser.Tab; 13import org.chromium.chrome.browser.profiles.Profile; 14import org.chromium.chrome.browser.util.MathUtils; 15import org.chromium.content_public.browser.WebContents; 16 17import java.util.ArrayList; 18import java.util.List; 19 20/** 21 * This is the default implementation of the {@link TabModel} interface. 22 */ 23public abstract class TabModelBase implements TabModel { 24 private static final String TAG = "TabModelBase"; 25 26 // TODO(dtrainor, simonb): Make these non-static so we don't break if we have multiple instances 27 // of chrome running. Also investigate how this affects document mode. 28 private static long sTabSwitchStartTime; 29 private static TabSelectionType sTabSelectionType; 30 private static boolean sTabSwitchLatencyMetricRequired; 31 private static boolean sPerceivedTabSwitchLatencyMetricLogged; 32 33 /** 34 * The main list of tabs. Note that when this changes, all pending closures must be committed 35 * via {@link #commitAllTabClosures()} as the indices are no longer valid. Also 36 * {@link RewoundList#resetRewoundState()} must be called so that the full model will be up to 37 * date. 38 */ 39 private final List<Tab> mTabs = new ArrayList<Tab>(); 40 41 private final boolean mIsIncognito; 42 43 private final TabModelOrderController mOrderController; 44 45 protected final TabModelDelegate mModelDelegate; 46 47 private final ObserverList<TabModelObserver> mObservers; 48 49 // Undo State Tracking ------------------------------------------------------------------------- 50 51 /** 52 * A {@link TabList} that represents the complete list of {@link Tab}s. This is so that 53 * certain UI elements can call {@link TabModel#getComprehensiveModel()} to get a full list of 54 * {@link Tab}s that includes rewindable entries, as the typical {@link TabModel} does not 55 * return rewindable entries. 56 */ 57 private final RewoundList mRewoundList = new RewoundList(); 58 59 /** 60 * This specifies the current {@link Tab} in {@link #mTabs}. 61 */ 62 private int mIndex = INVALID_TAB_INDEX; 63 64 /** Native Tab pointer which will be set by nativeInit(). */ 65 private long mNativeTabModelImpl = 0; 66 67 public TabModelBase(boolean incognito, TabModelOrderController orderController, 68 TabModelDelegate modelDelegate) { 69 mIsIncognito = incognito; 70 mNativeTabModelImpl = nativeInit(incognito); 71 mOrderController = orderController; 72 mModelDelegate = modelDelegate; 73 mObservers = new ObserverList<TabModelObserver>(); 74 } 75 76 @Override 77 public Profile getProfile() { 78 return nativeGetProfileAndroid(mNativeTabModelImpl); 79 } 80 81 @Override 82 public boolean isIncognito() { 83 return mIsIncognito; 84 } 85 86 @Override 87 public void destroy() { 88 for (Tab tab : mTabs) { 89 if (tab.isInitialized()) tab.destroy(); 90 } 91 92 mRewoundList.destroy(); 93 94 if (mNativeTabModelImpl != 0) { 95 nativeDestroy(mNativeTabModelImpl); 96 mNativeTabModelImpl = 0; 97 } 98 99 mTabs.clear(); 100 mObservers.clear(); 101 } 102 103 @Override 104 public void addObserver(TabModelObserver observer) { 105 mObservers.addObserver(observer); 106 } 107 108 @Override 109 public void removeObserver(TabModelObserver observer) { 110 mObservers.removeObserver(observer); 111 } 112 113 /** 114 * Initializes the newly created tab, adds it to controller, and dispatches creation 115 * step notifications. 116 */ 117 @Override 118 public void addTab(Tab tab, int index, TabLaunchType type) { 119 TraceEvent.begin(); 120 121 for (TabModelObserver obs : mObservers) obs.willAddTab(tab, type); 122 123 boolean selectTab = mOrderController.willOpenInForeground(type, mIsIncognito); 124 125 index = mOrderController.determineInsertionIndex(type, index, tab); 126 assert index <= mTabs.size(); 127 128 assert tab.isIncognito() == mIsIncognito; 129 130 // TODO(dtrainor): Update the list of undoable tabs instead of committing it. 131 commitAllTabClosures(); 132 133 if (index < 0 || index > mTabs.size()) { 134 mTabs.add(tab); 135 } else { 136 mTabs.add(index, tab); 137 if (index <= mIndex) { 138 mIndex++; 139 } 140 } 141 142 if (!isCurrentModel()) { 143 // When adding new tabs in the background, make sure we set a valid index when the 144 // first one is added. When in the foreground, calls to setIndex will take care of 145 // this. 146 mIndex = Math.max(mIndex, 0); 147 } 148 149 mRewoundList.resetRewoundState(); 150 151 int newIndex = indexOf(tab); 152 mModelDelegate.didChange(); 153 mModelDelegate.didCreateNewTab(tab); 154 155 if (mNativeTabModelImpl != 0) nativeTabAddedToModel(mNativeTabModelImpl, tab); 156 157 for (TabModelObserver obs : mObservers) obs.didAddTab(tab, type); 158 159 if (selectTab) { 160 mModelDelegate.selectModel(mIsIncognito); 161 setIndex(newIndex, TabModel.TabSelectionType.FROM_NEW); 162 } 163 164 TraceEvent.end(); 165 } 166 167 @Override 168 public void moveTab(int id, int newIndex) { 169 newIndex = MathUtils.clamp(newIndex, 0, mTabs.size()); 170 171 int curIndex = TabModelUtils.getTabIndexById(this, id); 172 173 if (curIndex == INVALID_TAB_INDEX || curIndex == newIndex || curIndex + 1 == newIndex) { 174 return; 175 } 176 177 // TODO(dtrainor): Update the list of undoable tabs instead of committing it. 178 commitAllTabClosures(); 179 180 Tab tab = mTabs.remove(curIndex); 181 if (curIndex < newIndex) --newIndex; 182 183 mTabs.add(newIndex, tab); 184 185 if (curIndex == mIndex) { 186 mIndex = newIndex; 187 } else if (curIndex < mIndex && newIndex >= mIndex) { 188 --mIndex; 189 } else if (curIndex > mIndex && newIndex <= mIndex) { 190 ++mIndex; 191 } 192 193 mRewoundList.resetRewoundState(); 194 195 mModelDelegate.didChange(); 196 for (TabModelObserver obs : mObservers) obs.didMoveTab(tab, newIndex, curIndex); 197 } 198 199 @Override 200 @CalledByNative 201 public boolean closeTab(Tab tab) { 202 return closeTab(tab, true, false, false); 203 } 204 205 private Tab findTabInAllTabModels(int tabId) { 206 Tab tab = TabModelUtils.getTabById(mModelDelegate.getModel(mIsIncognito), tabId); 207 if (tab != null) return tab; 208 return TabModelUtils.getTabById(mModelDelegate.getModel(!mIsIncognito), tabId); 209 } 210 211 @Override 212 public Tab getNextTabIfClosed(int id) { 213 Tab tabToClose = TabModelUtils.getTabById(this, id); 214 Tab currentTab = TabModelUtils.getCurrentTab(this); 215 if (tabToClose == null) return currentTab; 216 217 int closingTabIndex = indexOf(tabToClose); 218 Tab adjacentTab = getTabAt((closingTabIndex == 0) ? 1 : closingTabIndex - 1); 219 Tab parentTab = findTabInAllTabModels(tabToClose.getParentId()); 220 221 // Determine which tab to select next according to these rules: 222 // * If closing a background tab, keep the current tab selected. 223 // * Otherwise, if not in overview mode, select the parent tab if it exists. 224 // * Otherwise, select an adjacent tab if one exists. 225 // * Otherwise, if closing the last incognito tab, select the current normal tab. 226 // * Otherwise, select nothing. 227 Tab nextTab = null; 228 if (tabToClose != currentTab && currentTab != null) { 229 nextTab = currentTab; 230 } else if (parentTab != null && !mModelDelegate.isInOverviewMode()) { 231 nextTab = parentTab; 232 } else if (adjacentTab != null) { 233 nextTab = adjacentTab; 234 } else if (mIsIncognito) { 235 nextTab = TabModelUtils.getCurrentTab(mModelDelegate.getModel(false)); 236 } 237 238 return nextTab; 239 } 240 241 @Override 242 public boolean isClosurePending(int tabId) { 243 return mRewoundList.getPendingRewindTab(tabId) != null; 244 } 245 246 @Override 247 public boolean supportsPendingClosures() { 248 return !mIsIncognito; 249 } 250 251 @Override 252 public TabList getComprehensiveModel() { 253 if (!supportsPendingClosures()) return this; 254 return mRewoundList; 255 } 256 257 @Override 258 public void cancelTabClosure(int tabId) { 259 Tab tab = mRewoundList.getPendingRewindTab(tabId); 260 if (tab == null) return; 261 262 tab.setClosing(false); 263 264 // Find a valid previous tab entry so we know what tab to insert after. With the following 265 // example, calling cancelTabClosure(4) would need to know to insert after 2. So we have to 266 // track across mRewoundTabs and mTabs and see what the last valid mTabs entry was (2) when 267 // we hit the 4 in the rewound list. An insertIndex of -1 represents the beginning of the 268 // list, as this is the index of tab to insert after. 269 // mTabs: 0 2 5 270 // mRewoundTabs 0 1 2 3 4 5 271 int prevIndex = -1; 272 final int stopIndex = mRewoundList.indexOf(tab); 273 for (int rewoundIndex = 0; rewoundIndex < stopIndex; rewoundIndex++) { 274 Tab rewoundTab = mRewoundList.getTabAt(rewoundIndex); 275 if (prevIndex == mTabs.size() - 1) break; 276 if (rewoundTab == mTabs.get(prevIndex + 1)) prevIndex++; 277 } 278 279 // Figure out where to insert the tab. Just add one to prevIndex, as -1 represents the 280 // beginning of the list, so we'll insert at 0. 281 int insertIndex = prevIndex + 1; 282 if (mIndex >= insertIndex) mIndex++; 283 mTabs.add(insertIndex, tab); 284 285 boolean activeModel = mModelDelegate.getCurrentModel() == this; 286 287 // If we're the active model call setIndex to actually select this tab, otherwise just set 288 // mIndex but don't kick off everything that happens when calling setIndex(). 289 if (activeModel) { 290 setIndex(insertIndex); 291 } else { 292 mIndex = insertIndex; 293 } 294 295 for (TabModelObserver obs : mObservers) obs.tabClosureUndone(tab); 296 } 297 298 @Override 299 public void commitTabClosure(int tabId) { 300 Tab tab = mRewoundList.getPendingRewindTab(tabId); 301 if (tab == null) return; 302 303 // We're committing the close, actually remove it from the lists and finalize the closing 304 // operation. 305 mRewoundList.removeTab(tab); 306 finalizeTabClosure(tab); 307 for (TabModelObserver obs : mObservers) obs.tabClosureCommitted(tab); 308 } 309 310 @Override 311 public void commitAllTabClosures() { 312 while (mRewoundList.getCount() > mTabs.size()) { 313 commitTabClosure(mRewoundList.getNextRewindableTab().getId()); 314 } 315 316 assert !mRewoundList.hasPendingClosures(); 317 } 318 319 @Override 320 public boolean closeTab(Tab tabToClose, boolean animate, boolean uponExit, boolean canUndo) { 321 if (tabToClose == null) { 322 assert false : "Tab is null!"; 323 return false; 324 } 325 326 if (!mTabs.contains(tabToClose)) { 327 assert false : "Tried to close a tab from another model!"; 328 return false; 329 } 330 331 canUndo &= supportsPendingClosures(); 332 333 if (canUndo) { 334 for (TabModelObserver obs : mObservers) obs.tabPendingClosure(tabToClose); 335 } 336 startTabClosure(tabToClose, animate, uponExit, canUndo); 337 if (!canUndo) finalizeTabClosure(tabToClose); 338 339 return true; 340 } 341 342 @Override 343 public void closeAllTabs() { 344 commitAllTabClosures(); 345 346 while (getCount() > 0) { 347 TabModelUtils.closeTabByIndex(this, 0); 348 } 349 } 350 351 @Override 352 @CalledByNative 353 public Tab getTabAt(int index) { 354 // This will catch INVALID_TAB_INDEX and return null 355 if (index < 0 || index >= mTabs.size()) return null; 356 return mTabs.get(index); 357 } 358 359 // Index of the given tab in the order of the tab stack. 360 @Override 361 public int indexOf(Tab tab) { 362 return mTabs.indexOf(tab); 363 } 364 365 /** 366 * @return true if this is the current model according to the model selector 367 */ 368 private boolean isCurrentModel() { 369 return mModelDelegate.getCurrentModel() == this; 370 } 371 372 // TODO(aurimas): Move this method to TabModelSelector when notifications move there. 373 private int getLastId(TabSelectionType type) { 374 if (type == TabSelectionType.FROM_CLOSE) return Tab.INVALID_TAB_ID; 375 376 // Get the current tab in the current tab model. 377 Tab currentTab = TabModelUtils.getCurrentTab(mModelDelegate.getCurrentModel()); 378 return currentTab != null ? currentTab.getId() : Tab.INVALID_TAB_ID; 379 } 380 381 // This function is complex and its behavior depends on persisted state, including mIndex. 382 @Override 383 public void setIndex(int i, final TabSelectionType type) { 384 TraceEvent.begin(); 385 int lastId = getLastId(type); 386 387 if (!isCurrentModel()) { 388 mModelDelegate.selectModel(isIncognito()); 389 } 390 391 if (mTabs.size() <= 0) { 392 mIndex = INVALID_TAB_INDEX; 393 } else { 394 mIndex = MathUtils.clamp(i, 0, mTabs.size() - 1); 395 } 396 397 Tab tab = TabModelUtils.getCurrentTab(this); 398 399 mModelDelegate.requestToShowTab(tab, type); 400 401 if (tab != null) { 402 for (TabModelObserver obs : mObservers) obs.didSelectTab(tab, type, lastId); 403 } 404 405 // notifyDataSetChanged() can call into 406 // ChromeViewHolderTablet.handleTabChangeExternal(), which will eventually move the 407 // ContentView onto the current view hierarchy (with addView()). 408 mModelDelegate.didChange(); 409 TraceEvent.end(); 410 } 411 412 /** 413 * @param incognito 414 * @param nativeWebContents 415 * @param parentId 416 * @return 417 */ 418 @CalledByNative 419 protected abstract Tab createTabWithNativeContents(boolean incognito, long nativeWebContents, 420 int parentId); 421 422 /** 423 * Performs the necessary actions to remove this {@link Tab} from this {@link TabModel}. 424 * This does not actually destroy the {@link Tab} (see 425 * {@link #finalizeTabClosure(Tab)}. 426 * 427 * @param tab The {@link Tab} to remove from this {@link TabModel}. 428 * @param animate Whether or not to animate the closing. 429 * @param uponExit Whether or not this is closing while the Activity is exiting. 430 * @param canUndo Whether or not this operation can be undone. Note that if this is {@code true} 431 * and {@link #supportsPendingClosures()} is {@code true}, 432 * {@link #commitTabClosure(int)} or {@link #commitAllTabClosures()} needs to be 433 * called to actually delete and clean up {@code tab}. 434 */ 435 private void startTabClosure(Tab tab, boolean animate, boolean uponExit, boolean canUndo) { 436 final int closingTabId = tab.getId(); 437 final int closingTabIndex = indexOf(tab); 438 439 tab.setClosing(true); 440 441 for (TabModelObserver obs : mObservers) obs.willCloseTab(tab, animate); 442 443 Tab currentTab = TabModelUtils.getCurrentTab(this); 444 Tab adjacentTab = getTabAt(closingTabIndex == 0 ? 1 : closingTabIndex - 1); 445 Tab nextTab = getNextTabIfClosed(closingTabId); 446 447 // TODO(dtrainor): Update the list of undoable tabs instead of committing it. 448 if (!canUndo) commitAllTabClosures(); 449 450 // Cancel any media currently playing. 451 if (canUndo) { 452 WebContents webContents = tab.getWebContents(); 453 if (webContents != null) webContents.releaseMediaPlayers(); 454 } 455 456 mTabs.remove(tab); 457 458 boolean nextIsIncognito = nextTab == null ? false : nextTab.isIncognito(); 459 int nextTabId = nextTab == null ? Tab.INVALID_TAB_ID : nextTab.getId(); 460 int nextTabIndex = nextTab == null ? INVALID_TAB_INDEX : TabModelUtils.getTabIndexById( 461 mModelDelegate.getModel(nextIsIncognito), nextTabId); 462 463 if (nextTab != currentTab) { 464 if (nextIsIncognito != isIncognito()) mIndex = indexOf(adjacentTab); 465 466 TabModel nextModel = mModelDelegate.getModel(nextIsIncognito); 467 nextModel.setIndex(nextTabIndex, 468 uponExit ? TabSelectionType.FROM_EXIT : TabSelectionType.FROM_CLOSE); 469 } else { 470 mIndex = nextTabIndex; 471 } 472 473 if (!canUndo) mRewoundList.resetRewoundState(); 474 } 475 476 /** 477 * Actually closes and cleans up {@code tab}. 478 * @param tab The {@link Tab} to close. 479 */ 480 private void finalizeTabClosure(Tab tab) { 481 for (TabModelObserver obs : mObservers) obs.didCloseTab(tab); 482 tab.destroy(); 483 } 484 485 private class RewoundList implements TabList { 486 /** 487 * A list of {@link Tab}s that represents the completely rewound list (if all 488 * rewindable closes were undone). If there are no possible rewindable closes this list 489 * should match {@link #mTabs}. 490 */ 491 private List<Tab> mRewoundTabs = new ArrayList<Tab>(); 492 493 @Override 494 public boolean isIncognito() { 495 return TabModelBase.this.isIncognito(); 496 } 497 498 /** 499 * If {@link TabModel} has a valid selected tab, this will return that same tab in the 500 * context of the rewound list of tabs. If {@link TabModel} has no tabs but the rewound 501 * list is not empty, it will return 0, the first tab. Otherwise it will return 502 * {@link TabModel#INVALID_TAB_INDEX}. 503 * @return The selected index of the rewound list of tabs (includes all pending closures). 504 */ 505 @Override 506 public int index() { 507 if (TabModelBase.this.index() != INVALID_TAB_INDEX) { 508 return mRewoundTabs.indexOf(TabModelUtils.getCurrentTab(TabModelBase.this)); 509 } 510 if (!mRewoundTabs.isEmpty()) return 0; 511 return INVALID_TAB_INDEX; 512 } 513 514 @Override 515 public int getCount() { 516 return mRewoundTabs.size(); 517 } 518 519 @Override 520 public Tab getTabAt(int index) { 521 if (index < 0 || index >= mRewoundTabs.size()) return null; 522 return mRewoundTabs.get(index); 523 } 524 525 @Override 526 public int indexOf(Tab tab) { 527 return mRewoundTabs.indexOf(tab); 528 } 529 530 @Override 531 public boolean isClosurePending(int tabId) { 532 return TabModelBase.this.isClosurePending(tabId); 533 } 534 535 /** 536 * Resets this list to match the original {@link TabModel}. Note that if the 537 * {@link TabModel} doesn't support pending closures this model will be empty. This should 538 * be called whenever {@link #mTabs} changes. 539 */ 540 public void resetRewoundState() { 541 mRewoundTabs.clear(); 542 543 if (TabModelBase.this.supportsPendingClosures()) { 544 for (int i = 0; i < TabModelBase.this.getCount(); i++) { 545 mRewoundTabs.add(TabModelBase.this.getTabAt(i)); 546 } 547 } 548 } 549 550 /** 551 * Finds the {@link Tab} specified by {@code tabId} and only returns it if it is 552 * actually a {@link Tab} that is in the middle of being closed (which means that it 553 * is present in this model but not in {@link #mTabs}. 554 * 555 * @param tabId The id of the {@link Tab} to search for. 556 * @return The {@link Tab} specified by {@code tabId} as long as that tab only exists 557 * in this model and not in {@link #mTabs}. {@code null} otherwise. 558 */ 559 public Tab getPendingRewindTab(int tabId) { 560 if (!TabModelBase.this.supportsPendingClosures()) return null; 561 if (TabModelUtils.getTabById(TabModelBase.this, tabId) != null) return null; 562 return TabModelUtils.getTabById(this, tabId); 563 } 564 565 /** 566 * A utility method for easily finding a {@link Tab} that can be closed. 567 * @return The next tab that is in the middle of being closed. 568 */ 569 public Tab getNextRewindableTab() { 570 if (!hasPendingClosures()) return null; 571 572 for (int i = 0; i < mRewoundTabs.size(); i++) { 573 Tab tab = i < TabModelBase.this.getCount() ? TabModelBase.this.getTabAt(i) : null; 574 Tab rewoundTab = mRewoundTabs.get(i); 575 576 if (tab == null || rewoundTab.getId() != tab.getId()) return rewoundTab; 577 } 578 579 return null; 580 } 581 582 /** 583 * Removes a {@link Tab} from this internal list. 584 * @param tab The {@link Tab} to remove. 585 */ 586 public void removeTab(Tab tab) { 587 mRewoundTabs.remove(tab); 588 } 589 590 /** 591 * Destroy all tabs in this model. This will check to see if the tab is already destroyed 592 * before destroying it. 593 */ 594 public void destroy() { 595 for (Tab tab : mRewoundTabs) { 596 if (tab.isInitialized()) tab.destroy(); 597 } 598 } 599 600 public boolean hasPendingClosures() { 601 return TabModelBase.this.supportsPendingClosures() 602 && mRewoundTabs.size() > TabModelBase.this.getCount(); 603 } 604 } 605 606 /** 607 * Broadcast a notification (in native code) that all tabs are now loaded from storage. 608 */ 609 public void broadcastSessionRestoreComplete() { 610 nativeBroadcastSessionRestoreComplete(mNativeTabModelImpl); 611 } 612 613 // JNI related methods ------------------------------------------------------------------------- 614 615 @Override 616 @CalledByNative 617 public int getCount() { 618 return mTabs.size(); 619 } 620 621 @Override 622 @CalledByNative 623 public int index() { 624 return mIndex; 625 } 626 627 @SuppressWarnings("unused") 628 @CalledByNative 629 private void setIndex(int index) { 630 TabModelUtils.setIndex(this, index); 631 } 632 633 /** 634 * Used by Developer Tools to create a new tab with a given URL. 635 * 636 * @param url The URL to open. 637 * @return The new tab. 638 */ 639 @CalledByNative 640 protected abstract Tab createNewTabForDevTools(String url); 641 642 @CalledByNative 643 private boolean isSessionRestoreInProgress() { 644 return mModelDelegate.isSessionRestoreInProgress(); 645 } 646 647 /** 648 * Register the start of tab switch latency timing. Called when setIndex() indicates a tab 649 * switch event. 650 * @param type The type of action that triggered the tab selection. 651 */ 652 public static void startTabSwitchLatencyTiming(final TabSelectionType type) { 653 sTabSwitchStartTime = SystemClock.uptimeMillis(); 654 sTabSelectionType = type; 655 sTabSwitchLatencyMetricRequired = false; 656 sPerceivedTabSwitchLatencyMetricLogged = false; 657 } 658 659 /** 660 * Should be called a visible {@link ChromeTab} gets a frame to render in the browser process. 661 * If we don't get this call, we ignore requests to 662 * {@link #flushActualTabSwitchLatencyMetric()}. 663 */ 664 public static void setActualTabSwitchLatencyMetricRequired() { 665 if (sTabSwitchStartTime <= 0) return; 666 sTabSwitchLatencyMetricRequired = true; 667 } 668 669 /** 670 * Logs the perceived tab switching latency metric. This will automatically be logged if 671 * the actual metric is set and flushed. 672 */ 673 public static void logPerceivedTabSwitchLatencyMetric() { 674 if (sTabSwitchStartTime <= 0 || sPerceivedTabSwitchLatencyMetricLogged) return; 675 676 flushTabSwitchLatencyMetric(true); 677 sPerceivedTabSwitchLatencyMetricLogged = true; 678 } 679 680 /** 681 * Flush the latency metric if called after the indication that a frame is ready. 682 */ 683 public static void flushActualTabSwitchLatencyMetric() { 684 if (sTabSwitchStartTime <= 0 || !sTabSwitchLatencyMetricRequired) return; 685 logPerceivedTabSwitchLatencyMetric(); 686 flushTabSwitchLatencyMetric(false); 687 688 sTabSwitchStartTime = 0; 689 sTabSwitchLatencyMetricRequired = false; 690 } 691 692 private static void flushTabSwitchLatencyMetric(boolean perceived) { 693 if (sTabSwitchStartTime <= 0) return; 694 final long ms = SystemClock.uptimeMillis() - sTabSwitchStartTime; 695 switch (sTabSelectionType) { 696 case FROM_CLOSE: 697 nativeLogFromCloseMetric(ms, perceived); 698 break; 699 case FROM_EXIT: 700 nativeLogFromExitMetric(ms, perceived); 701 break; 702 case FROM_NEW: 703 nativeLogFromNewMetric(ms, perceived); 704 break; 705 case FROM_USER: 706 nativeLogFromUserMetric(ms, perceived); 707 break; 708 } 709 } 710 711 private native long nativeInit(boolean isIncognito); 712 private native void nativeDestroy(long nativeTabModelBase); 713 private native void nativeBroadcastSessionRestoreComplete(long nativeTabModelBase); 714 private native Profile nativeGetProfileAndroid(long nativeTabModelBase); 715 private native void nativeTabAddedToModel(long nativeTabModelBase, Tab tab); 716 // Native methods for tab switch latency metrics. 717 private static native void nativeLogFromCloseMetric(long ms, boolean perceived); 718 private static native void nativeLogFromExitMetric(long ms, boolean perceived); 719 private static native void nativeLogFromNewMetric(long ms, boolean perceived); 720 private static native void nativeLogFromUserMetric(long ms, boolean perceived); 721} 722