Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / tabmodel / TabModelBase.java
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
5 package org.chromium.chrome.browser.tabmodel;
6
7 import android.os.SystemClock;
8
9 import org.chromium.base.CalledByNative;
10 import org.chromium.base.ObserverList;
11 import org.chromium.base.TraceEvent;
12 import org.chromium.chrome.browser.Tab;
13 import org.chromium.chrome.browser.profiles.Profile;
14 import org.chromium.chrome.browser.util.MathUtils;
15 import org.chromium.content_public.browser.WebContents;
16
17 import java.util.ArrayList;
18 import java.util.List;
19
20 /**
21  * This is the default implementation of the {@link TabModel} interface.
22  */
23 public 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 }