blob: 1d647d1776c5c7eda4b9b428a5312f1c2d855600 [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
2 * Copyright (C) 2007 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
Bijan Amirzada41242f22014-03-21 12:12:18 -070017package com.android.browser;
The Android Open Source Project0c908882009-03-03 19:32:16 -080018
Pankaj Garg75273bb2015-08-20 11:43:49 -070019import android.net.Uri;
The Android Open Source Project0c908882009-03-03 19:32:16 -080020import android.os.Bundle;
Vivek Sekhar13198ac2016-01-08 14:47:21 -080021import android.text.TextUtils;
The Android Open Source Project0c908882009-03-03 19:32:16 -080022import android.util.Log;
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080023
Axesh R. Ajmera2e241242014-05-19 15:53:38 -070024import org.codeaurora.swe.GeolocationPermissions;
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080025import org.codeaurora.swe.WebView;
Vivek Sekhar2f2ff572015-12-21 11:55:17 -080026import org.codeaurora.swe.Engine;
Enrico Ros1f5a0952014-11-18 20:15:48 -080027import org.codeaurora.swe.util.Observable;
The Android Open Source Project0c908882009-03-03 19:32:16 -080028
The Android Open Source Project0c908882009-03-03 19:32:16 -080029import java.util.ArrayList;
Elliott Slaughter3d6df162010-08-25 13:17:44 -070030import java.util.HashMap;
Michael Kolb1bf23132010-11-19 12:55:12 -080031import java.util.List;
The Android Open Source Project0c908882009-03-03 19:32:16 -080032import java.util.Vector;
33
34class TabControl {
35 // Log Tag
36 private static final String LOGTAG = "TabControl";
Michael Kolbc831b632011-05-11 09:30:34 -070037
John Reckd8c74522011-06-14 08:45:00 -070038 // next Tab ID, starting at 1
39 private static long sNextId = 1;
Michael Kolbc831b632011-05-11 09:30:34 -070040
41 private static final String POSITIONS = "positions";
42 private static final String CURRENT = "current";
43
Pankaj Garg75273bb2015-08-20 11:43:49 -070044
45 /*
46 Find and reload any live tabs that have loaded the given URL.
47 Note - Upto 2 tabs are live at any given moment.
48 */
49 public void findAndReload(String origin) {
50 for (Tab tab : mTabs){
Vivek Sekhar13198ac2016-01-08 14:47:21 -080051 WebView wv = tab.getWebView();
52 if (wv != null && !TextUtils.isEmpty(wv.getUrl())) {
53 Uri url = Uri.parse(wv.getUrl());
54 if ((url != null && url.getHost() != null) &&
55 url.getHost().equals(origin) ) {
Pankaj Garg75273bb2015-08-20 11:43:49 -070056 tab.getWebView().reload();
57 }
58 }
59 }
60 }
61
62 // Reload the all the live tabs
63 public void reloadLiveTabs() {
64 for (Tab tab : mTabs) {
65 if (tab.getWebView() != null) {
66 tab.getWebView().reload();
67 }
68 }
69 }
70
John Reck8ee633f2011-08-09 16:00:35 -070071 public static interface OnThumbnailUpdatedListener {
72 void onThumbnailUpdated(Tab t);
73 }
74
The Android Open Source Project0c908882009-03-03 19:32:16 -080075 // Maximum number of tabs.
Michael Kolb6e4653e2010-09-27 16:22:38 -070076 private int mMaxTabs;
The Android Open Source Project0c908882009-03-03 19:32:16 -080077 // Private array of WebViews that are used as tabs.
Michael Kolb6e4653e2010-09-27 16:22:38 -070078 private ArrayList<Tab> mTabs;
The Android Open Source Project0c908882009-03-03 19:32:16 -080079 // Queue of most recently viewed tabs.
Michael Kolb6e4653e2010-09-27 16:22:38 -070080 private ArrayList<Tab> mTabQueue;
The Android Open Source Project0c908882009-03-03 19:32:16 -080081 // Current position in mTabs.
82 private int mCurrentTab = -1;
Michael Kolb8233fac2010-10-26 16:08:53 -070083 // the main browser controller
84 private final Controller mController;
Axesh R. Ajmera2e241242014-05-19 15:53:38 -070085 // number of incognito tabs
86 private int mNumIncognito = 0;
Michael Kolb8233fac2010-10-26 16:08:53 -070087
John Reck8ee633f2011-08-09 16:00:35 -070088 private OnThumbnailUpdatedListener mOnThumbnailUpdatedListener;
The Android Open Source Project0c908882009-03-03 19:32:16 -080089
Enrico Ros1f5a0952014-11-18 20:15:48 -080090 private Observable mTabCountObservable;
91
The Android Open Source Project0c908882009-03-03 19:32:16 -080092 /**
Michael Kolb8233fac2010-10-26 16:08:53 -070093 * Construct a new TabControl object
The Android Open Source Project0c908882009-03-03 19:32:16 -080094 */
Michael Kolb8233fac2010-10-26 16:08:53 -070095 TabControl(Controller controller) {
96 mController = controller;
Michael Kolb8233fac2010-10-26 16:08:53 -070097 mMaxTabs = mController.getMaxTabs();
Michael Kolb6e4653e2010-09-27 16:22:38 -070098 mTabs = new ArrayList<Tab>(mMaxTabs);
99 mTabQueue = new ArrayList<Tab>(mMaxTabs);
Enrico Ros1f5a0952014-11-18 20:15:48 -0800100 mTabCountObservable = new Observable();
101 mTabCountObservable.set(0);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800102 }
103
John Reck52be4782011-08-26 15:37:29 -0700104 synchronized static long getNextId() {
Michael Kolbc831b632011-05-11 09:30:34 -0700105 return sNextId++;
106 }
107
Enrico Ros1f5a0952014-11-18 20:15:48 -0800108 Observable getTabCountObservable() {
109 return mTabCountObservable;
110 }
111
Michael Kolb68775752010-08-19 12:42:01 -0700112 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800113 * Return the current tab's main WebView. This will always return the main
114 * WebView for a given tab and not a subwindow.
115 * @return The current tab's WebView.
116 */
117 WebView getCurrentWebView() {
118 Tab t = getTab(mCurrentTab);
119 if (t == null) {
120 return null;
121 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700122 return t.getWebView();
Ben Murdochbff2d602009-07-01 20:19:05 +0100123 }
124
125 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800126 * Return the current tab's top-level WebView. This can return a subwindow
127 * if one exists.
128 * @return The top-level WebView of the current tab.
129 */
130 WebView getCurrentTopWebView() {
131 Tab t = getTab(mCurrentTab);
132 if (t == null) {
133 return null;
134 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700135 return t.getTopWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800136 }
137
138 /**
139 * Return the current tab's subwindow if it exists.
140 * @return The subwindow of the current tab or null if it doesn't exist.
141 */
142 WebView getCurrentSubWindow() {
143 Tab t = getTab(mCurrentTab);
144 if (t == null) {
145 return null;
146 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700147 return t.getSubWebView();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800148 }
149
150 /**
Michael Kolb1bf23132010-11-19 12:55:12 -0800151 * return the list of tabs
152 */
153 List<Tab> getTabs() {
154 return mTabs;
155 }
156
157 /**
Michael Kolbc831b632011-05-11 09:30:34 -0700158 * Return the tab at the specified position.
159 * @return The Tab for the specified position or null if the tab does not
The Android Open Source Project0c908882009-03-03 19:32:16 -0800160 * exist.
161 */
Michael Kolbc831b632011-05-11 09:30:34 -0700162 Tab getTab(int position) {
163 if (position >= 0 && position < mTabs.size()) {
164 return mTabs.get(position);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800165 }
166 return null;
167 }
168
169 /**
170 * Return the current tab.
171 * @return The current tab.
172 */
173 Tab getCurrentTab() {
174 return getTab(mCurrentTab);
175 }
176
177 /**
Michael Kolbc831b632011-05-11 09:30:34 -0700178 * Return the current tab position.
179 * @return The current tab position
The Android Open Source Project0c908882009-03-03 19:32:16 -0800180 */
Michael Kolbc831b632011-05-11 09:30:34 -0700181 int getCurrentPosition() {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800182 return mCurrentTab;
183 }
Michael Kolbfe251992010-07-08 15:41:55 -0700184
The Android Open Source Project0c908882009-03-03 19:32:16 -0800185 /**
Michael Kolbc831b632011-05-11 09:30:34 -0700186 * Given a Tab, find it's position
The Android Open Source Project0c908882009-03-03 19:32:16 -0800187 * @param Tab to find
Michael Kolbc831b632011-05-11 09:30:34 -0700188 * @return position of Tab or -1 if not found
The Android Open Source Project0c908882009-03-03 19:32:16 -0800189 */
Michael Kolbc831b632011-05-11 09:30:34 -0700190 int getTabPosition(Tab tab) {
Patrick Scottae641ac2009-04-20 13:51:49 -0400191 if (tab == null) {
192 return -1;
193 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800194 return mTabs.indexOf(tab);
195 }
196
Grace Kloba22ac16e2009-10-07 18:00:23 -0700197 boolean canCreateNewTab() {
Narayan Kamathc3ad9ea2011-07-26 15:20:21 +0100198 return mMaxTabs > mTabs.size();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700199 }
200
The Android Open Source Project0c908882009-03-03 19:32:16 -0800201 /**
Elliott Slaughtere440a882010-08-20 13:54:45 -0700202 * Returns true if there are any incognito tabs open.
203 * @return True when any incognito tabs are open, false otherwise.
204 */
205 boolean hasAnyOpenIncognitoTabs() {
206 for (Tab tab : mTabs) {
Michael Kolbc831b632011-05-11 09:30:34 -0700207 if (tab.getWebView() != null
208 && tab.getWebView().isPrivateBrowsingEnabled()) {
Elliott Slaughtere440a882010-08-20 13:54:45 -0700209 return true;
210 }
211 }
212 return false;
213 }
214
Michael Kolb14612442011-06-24 13:06:29 -0700215 void addPreloadedTab(Tab tab) {
Mathew Inwoode09305e2011-09-02 12:03:26 +0100216 for (Tab current : mTabs) {
217 if (current != null && current.getId() == tab.getId()) {
218 throw new IllegalStateException("Tab with id " + tab.getId() + " already exists: "
219 + current.toString());
220 }
221 }
Michael Kolb14612442011-06-24 13:06:29 -0700222 mTabs.add(tab);
Enrico Ros1f5a0952014-11-18 20:15:48 -0800223 mTabCountObservable.set(mTabs.size());
Michael Kolb14612442011-06-24 13:06:29 -0700224 tab.setController(mController);
225 mController.onSetWebView(tab, tab.getWebView());
226 tab.putInBackground();
227 }
228
Elliott Slaughtere440a882010-08-20 13:54:45 -0700229 /**
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700230 * Create a new tab.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800231 * @return The newly createTab or null if we have reached the maximum
232 * number of open tabs.
233 */
Michael Kolb7bcafde2011-05-09 13:55:59 -0700234 Tab createNewTab(boolean privateBrowsing) {
Stewart Chaoe0e132e2014-12-01 18:28:22 -0500235 return createNewTab(null, privateBrowsing, false);
236 }
237
238 Tab createNewTab(boolean privateBrowsing, boolean backgroundTab) {
239 return createNewTab(null, privateBrowsing, backgroundTab);
John Reck1cf4b792011-07-26 10:22:22 -0700240 }
241
242 Tab createNewTab(Bundle state, boolean privateBrowsing) {
Stewart Chao3f628f72015-01-07 10:03:18 -0500243 return createNewTab(state, privateBrowsing, false);
Stewart Chaoe0e132e2014-12-01 18:28:22 -0500244 }
245
246 Tab createNewTab(Bundle state, boolean privateBrowsing, boolean backgroundTab) {
John Reck1cf4b792011-07-26 10:22:22 -0700247 int size = mTabs.size();
248 // Return false if we have maxed out on tabs
Narayan Kamathc3ad9ea2011-07-26 15:20:21 +0100249 if (!canCreateNewTab()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800250 return null;
251 }
Narayan Kamathc3ad9ea2011-07-26 15:20:21 +0100252
Stewart Chaoe0e132e2014-12-01 18:28:22 -0500253 final WebView w = createNewWebView(privateBrowsing, backgroundTab);
Steve Block2bc69912009-07-30 14:45:13 +0100254
The Android Open Source Project0c908882009-03-03 19:32:16 -0800255 // Create a new tab and add it to the tab list
Vivek Sekhar0c609cd2015-12-10 16:00:05 -0800256 Tab t = new Tab(mController, w, state, backgroundTab);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800257 mTabs.add(t);
Enrico Ros1f5a0952014-11-18 20:15:48 -0800258 mTabCountObservable.set(mTabs.size());
Axesh R. Ajmera2e241242014-05-19 15:53:38 -0700259 if (privateBrowsing) {
260 mNumIncognito += 1;
261 }
The Android Open Source Project4e5f5872009-03-09 11:52:14 -0700262 // Initially put the tab in the background.
Grace Kloba22ac16e2009-10-07 18:00:23 -0700263 t.putInBackground();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800264 return t;
265 }
266
267 /**
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700268 * Create a new tab with default values for closeOnExit(false),
Elliott Slaughterf0f03952010-07-14 18:10:36 -0700269 * appId(null), url(null), and privateBrowsing(false).
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700270 */
271 Tab createNewTab() {
Michael Kolb7bcafde2011-05-09 13:55:59 -0700272 return createNewTab(false);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700273 }
274
Axesh R. Ajmera4b000312015-07-23 10:00:16 -0700275 SnapshotTab createSnapshotTab(long snapshotId, Bundle state) {
276 SnapshotTab t = new SnapshotTab(mController, snapshotId, state);
John Reckd8c74522011-06-14 08:45:00 -0700277 mTabs.add(t);
Enrico Ros1f5a0952014-11-18 20:15:48 -0800278 mTabCountObservable.set(mTabs.size());
John Reckd8c74522011-06-14 08:45:00 -0700279 return t;
280 }
281
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700282 /**
Leon Scrogginsfde97462010-01-11 13:06:21 -0500283 * Remove the parent child relationships from all tabs.
284 */
285 void removeParentChildRelationShips() {
286 for (Tab tab : mTabs) {
287 tab.removeFromTree();
288 }
289 }
290
291 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800292 * Remove the tab from the list. If the tab is the current tab shown, the
293 * last created tab will be shown.
294 * @param t The tab to be removed.
295 */
296 boolean removeTab(Tab t) {
297 if (t == null) {
298 return false;
299 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700300
Patrick Scottd944d4d2010-01-27 16:39:11 -0500301 // Grab the current tab before modifying the list.
302 Tab current = getCurrentTab();
303
304 // Remove t from our list of tabs.
305 mTabs.remove(t);
Enrico Ros1f5a0952014-11-18 20:15:48 -0800306 mTabCountObservable.set(mTabs.size());
Patrick Scottd944d4d2010-01-27 16:39:11 -0500307
Axesh R. Ajmera2e241242014-05-19 15:53:38 -0700308 //Clear incognito geolocation state if this is the last incognito tab.
309 if (t.isPrivateBrowsingEnabled()) {
310 mNumIncognito -= 1;
311 if (mNumIncognito == 0) {
312 GeolocationPermissions.onIncognitoTabsRemoved();
Vivek Sekhar2f2ff572015-12-21 11:55:17 -0800313 Engine.destroyIncognitoProfile();
Axesh R. Ajmera2e241242014-05-19 15:53:38 -0700314 }
315 }
316
Patrick Scottd944d4d2010-01-27 16:39:11 -0500317 // Put the tab in the background only if it is the current one.
318 if (current == t) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700319 t.putInBackground();
320 mCurrentTab = -1;
Patrick Scottd944d4d2010-01-27 16:39:11 -0500321 } else {
322 // If a tab that is earlier in the list gets removed, the current
323 // index no longer points to the correct tab.
Michael Kolbc831b632011-05-11 09:30:34 -0700324 mCurrentTab = getTabPosition(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800325 }
326
Grace Kloba22ac16e2009-10-07 18:00:23 -0700327 // destroy the tab
328 t.destroy();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800329 // clear it's references to parent and children
330 t.removeFromTree();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800331
The Android Open Source Project0c908882009-03-03 19:32:16 -0800332 // Remove it from the queue of viewed tabs.
333 mTabQueue.remove(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800334 return true;
335 }
336
337 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800338 * Destroy all the tabs and subwindows
339 */
340 void destroy() {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800341 for (Tab t : mTabs) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700342 t.destroy();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800343 }
344 mTabs.clear();
345 mTabQueue.clear();
346 }
347
348 /**
349 * Returns the number of tabs created.
350 * @return The number of tabs created.
351 */
352 int getTabCount() {
353 return mTabs.size();
354 }
355
The Android Open Source Project0c908882009-03-03 19:32:16 -0800356 /**
Michael Kolbc831b632011-05-11 09:30:34 -0700357 * save the tab state:
358 * current position
359 * position sorted array of tab ids
360 * for each tab id, save the tab state
361 * @param outState
John Reckaed9c542011-05-27 16:08:53 -0700362 * @param saveImages
The Android Open Source Project0c908882009-03-03 19:32:16 -0800363 */
John Reck1cf4b792011-07-26 10:22:22 -0700364 void saveState(Bundle outState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800365 final int numTabs = getTabCount();
John Reck24f18262011-06-17 14:47:20 -0700366 if (numTabs == 0) {
367 return;
368 }
Michael Kolbc831b632011-05-11 09:30:34 -0700369 long[] ids = new long[numTabs];
370 int i = 0;
371 for (Tab tab : mTabs) {
John Reck1cf4b792011-07-26 10:22:22 -0700372 Bundle tabState = tab.saveState();
Tarun Nainani8eb00912014-07-17 12:28:32 -0700373 if (tabState != null && tab.isPrivateBrowsingEnabled() == false) {
Michael Kolb3ae7f742011-07-13 15:18:25 -0700374 ids[i++] = tab.getId();
John Reck52be4782011-08-26 15:37:29 -0700375 String key = Long.toString(tab.getId());
376 if (outState.containsKey(key)) {
377 // Dump the tab state for debugging purposes
378 for (Tab dt : mTabs) {
379 Log.e(LOGTAG, dt.toString());
380 }
381 throw new IllegalStateException(
382 "Error saving state, duplicate tab ids!");
383 }
384 outState.putBundle(key, tabState);
Michael Kolb3ae7f742011-07-13 15:18:25 -0700385 } else {
386 ids[i++] = -1;
John Reck52be4782011-08-26 15:37:29 -0700387 // Since we won't be restoring the thumbnail, delete it
388 tab.deleteThumbnail();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800389 }
390 }
John Reck24f18262011-06-17 14:47:20 -0700391 if (!outState.isEmpty()) {
392 outState.putLongArray(POSITIONS, ids);
393 Tab current = getCurrentTab();
394 long cid = -1;
395 if (current != null) {
396 cid = current.getId();
397 }
398 outState.putLong(CURRENT, cid);
Michael Kolbdbf39812011-06-10 14:06:40 -0700399 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800400 }
401
402 /**
Patrick Scott7d50a932011-02-04 09:27:26 -0500403 * Check if the state can be restored. If the state can be restored, the
Michael Kolbc831b632011-05-11 09:30:34 -0700404 * current tab id is returned. This can be passed to restoreState below
Patrick Scott7d50a932011-02-04 09:27:26 -0500405 * in order to restore the correct tab. Otherwise, -1 is returned and the
406 * state cannot be restored.
407 */
Michael Kolbc831b632011-05-11 09:30:34 -0700408 long canRestoreState(Bundle inState, boolean restoreIncognitoTabs) {
409 final long[] ids = (inState == null) ? null : inState.getLongArray(POSITIONS);
410 if (ids == null) {
Patrick Scott7d50a932011-02-04 09:27:26 -0500411 return -1;
412 }
Michael Kolbc831b632011-05-11 09:30:34 -0700413 final long oldcurrent = inState.getLong(CURRENT);
414 long current = -1;
Michael Kolb3ae7f742011-07-13 15:18:25 -0700415 if (restoreIncognitoTabs || (hasState(oldcurrent, inState) && !isIncognito(oldcurrent, inState))) {
John Reck1cf4b792011-07-26 10:22:22 -0700416 current = oldcurrent;
Patrick Scott7d50a932011-02-04 09:27:26 -0500417 } else {
Michael Kolbc831b632011-05-11 09:30:34 -0700418 // pick first non incognito tab
419 for (long id : ids) {
Michael Kolb3ae7f742011-07-13 15:18:25 -0700420 if (hasState(id, inState) && !isIncognito(id, inState)) {
Michael Kolbc831b632011-05-11 09:30:34 -0700421 current = id;
Patrick Scott7d50a932011-02-04 09:27:26 -0500422 break;
423 }
424 }
425 }
Michael Kolbc831b632011-05-11 09:30:34 -0700426 return current;
Patrick Scott7d50a932011-02-04 09:27:26 -0500427 }
428
Michael Kolb3ae7f742011-07-13 15:18:25 -0700429 private boolean hasState(long id, Bundle state) {
430 if (id == -1) return false;
431 Bundle tab = state.getBundle(Long.toString(id));
432 return ((tab != null) && !tab.isEmpty());
433 }
434
435 private boolean isIncognito(long id, Bundle state) {
436 Bundle tabstate = state.getBundle(Long.toString(id));
437 if ((tabstate != null) && !tabstate.isEmpty()) {
438 return tabstate.getBoolean(Tab.INCOGNITO);
439 }
440 return false;
441 }
442
Patrick Scott7d50a932011-02-04 09:27:26 -0500443 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800444 * Restore the state of all the tabs.
Michael Kolbc831b632011-05-11 09:30:34 -0700445 * @param currentId The tab id to restore.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800446 * @param inState The saved state of all the tabs.
Michael Kolb1bf23132010-11-19 12:55:12 -0800447 * @param restoreIncognitoTabs Restoring private browsing tabs
448 * @param restoreAll All webviews get restored, not just the current tab
449 * (this does not override handling of incognito tabs)
The Android Open Source Project0c908882009-03-03 19:32:16 -0800450 */
Michael Kolbc831b632011-05-11 09:30:34 -0700451 void restoreState(Bundle inState, long currentId,
Patrick Scott7d50a932011-02-04 09:27:26 -0500452 boolean restoreIncognitoTabs, boolean restoreAll) {
Michael Kolbc831b632011-05-11 09:30:34 -0700453 if (currentId == -1) {
Patrick Scott7d50a932011-02-04 09:27:26 -0500454 return;
455 }
Michael Kolbc831b632011-05-11 09:30:34 -0700456 long[] ids = inState.getLongArray(POSITIONS);
457 long maxId = -Long.MAX_VALUE;
458 HashMap<Long, Tab> tabMap = new HashMap<Long, Tab>();
459 for (long id : ids) {
460 if (id > maxId) {
461 maxId = id;
462 }
463 final String idkey = Long.toString(id);
464 Bundle state = inState.getBundle(idkey);
John Reckd8c74522011-06-14 08:45:00 -0700465 if (state == null || state.isEmpty()) {
466 // Skip tab
467 continue;
468 } else if (!restoreIncognitoTabs
Michael Kolbc831b632011-05-11 09:30:34 -0700469 && state.getBoolean(Tab.INCOGNITO)) {
470 // ignore tab
471 } else if (id == currentId || restoreAll) {
Axesh R. Ajmerae6b11aa2015-03-16 18:20:41 -0700472 Tab t = null;
473 // Add special check to restore Snapshot Tab if needed
474 if (state.getLong(SnapshotTab.SNAPSHOT_ID, -1) != -1 ) {
Axesh R. Ajmera4b000312015-07-23 10:00:16 -0700475 t = (SnapshotTab) createSnapshotTab( state.getLong(SnapshotTab.SNAPSHOT_ID), state);
Axesh R. Ajmerae6b11aa2015-03-16 18:20:41 -0700476 } else {
477 // presume its a normal Tab
478 t = createNewTab(state, false);
479 }
480
Narayan Kamath79e5d8d2011-07-20 14:12:38 +0100481 if (t == null) {
482 // We could "break" at this point, but we want
483 // sNextId to be set correctly.
484 continue;
485 }
Michael Kolbc831b632011-05-11 09:30:34 -0700486 tabMap.put(id, t);
Patrick Scott7d50a932011-02-04 09:27:26 -0500487 // Me must set the current tab before restoring the state
488 // so that all the client classes are set.
Michael Kolbc831b632011-05-11 09:30:34 -0700489 if (id == currentId) {
Patrick Scott7d50a932011-02-04 09:27:26 -0500490 setCurrentTab(t);
491 }
Elliott Slaughter3d6df162010-08-25 13:17:44 -0700492 } else {
Patrick Scott7d50a932011-02-04 09:27:26 -0500493 // Create a new tab and don't restore the state yet, add it
494 // to the tab list
John Reck1cf4b792011-07-26 10:22:22 -0700495 Tab t = new Tab(mController, state);
Michael Kolbc831b632011-05-11 09:30:34 -0700496 tabMap.put(id, t);
Patrick Scott7d50a932011-02-04 09:27:26 -0500497 mTabs.add(t);
Enrico Ros1f5a0952014-11-18 20:15:48 -0800498 mTabCountObservable.set(mTabs.size());
Patrick Scott7d50a932011-02-04 09:27:26 -0500499 // added the tab to the front as they are not current
500 mTabQueue.add(0, t);
Elliott Slaughter3d6df162010-08-25 13:17:44 -0700501 }
Michael Kolbc831b632011-05-11 09:30:34 -0700502 }
Narayan Kamath79e5d8d2011-07-20 14:12:38 +0100503
504 // make sure that there is no id overlap between the restored
505 // and new tabs
506 sNextId = maxId + 1;
507
John Reckd8c74522011-06-14 08:45:00 -0700508 if (mCurrentTab == -1) {
509 if (getTabCount() > 0) {
510 setCurrentTab(getTab(0));
John Reckd8c74522011-06-14 08:45:00 -0700511 }
512 }
Michael Kolbc831b632011-05-11 09:30:34 -0700513 // restore parent/child relationships
514 for (long id : ids) {
515 final Tab tab = tabMap.get(id);
516 final Bundle b = inState.getBundle(Long.toString(id));
517 if ((b != null) && (tab != null)) {
518 final long parentId = b.getLong(Tab.PARENTTAB, -1);
519 if (parentId != -1) {
520 final Tab parent = tabMap.get(parentId);
Patrick Scott7d50a932011-02-04 09:27:26 -0500521 if (parent != null) {
Michael Kolbc831b632011-05-11 09:30:34 -0700522 parent.addChildTab(tab);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800523 }
524 }
525 }
526 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800527 }
528
529 /**
Grace Klobaf56f68d2010-04-11 22:53:42 -0700530 * Free the memory in this order, 1) free the background tabs; 2) free the
The Android Open Source Project0c908882009-03-03 19:32:16 -0800531 * WebView cache;
532 */
533 void freeMemory() {
Grace Kloba92c18a52009-07-31 23:48:32 -0700534 if (getTabCount() == 0) return;
535
Grace Klobaf56f68d2010-04-11 22:53:42 -0700536 // free the least frequently used background tabs
537 Vector<Tab> tabs = getHalfLeastUsedTabs(getCurrentTab());
538 if (tabs.size() > 0) {
539 Log.w(LOGTAG, "Free " + tabs.size() + " tabs in the browser");
540 for (Tab t : tabs) {
541 // store the WebView's state.
542 t.saveState();
543 // destroy the tab
544 t.destroy();
545 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800546 return;
547 }
548
Derek Sollenberger4433d032009-06-10 15:37:21 -0400549 // free the WebView's unused memory (this includes the cache)
550 Log.w(LOGTAG, "Free WebView's unused memory and cache");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800551 WebView view = getCurrentWebView();
552 if (view != null) {
Derek Sollenberger4433d032009-06-10 15:37:21 -0400553 view.freeMemory();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800554 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800555 }
556
Grace Klobaf56f68d2010-04-11 22:53:42 -0700557 private Vector<Tab> getHalfLeastUsedTabs(Tab current) {
558 Vector<Tab> tabsToGo = new Vector<Tab>();
559
Patrick Scott2a67de42009-08-31 09:48:37 -0400560 // Don't do anything if we only have 1 tab or if the current tab is
561 // null.
562 if (getTabCount() == 1 || current == null) {
Grace Klobaf56f68d2010-04-11 22:53:42 -0700563 return tabsToGo;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800564 }
565
Grace Klobaf56f68d2010-04-11 22:53:42 -0700566 if (mTabQueue.size() == 0) {
567 return tabsToGo;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800568 }
569
Grace Klobaf56f68d2010-04-11 22:53:42 -0700570 // Rip through the queue starting at the beginning and tear down half of
571 // available tabs which are not the current tab or the parent of the
572 // current tab.
573 int openTabCount = 0;
574 for (Tab t : mTabQueue) {
575 if (t != null && t.getWebView() != null) {
576 openTabCount++;
Michael Kolbc831b632011-05-11 09:30:34 -0700577 if (t != current && t != current.getParent()) {
Grace Klobaf56f68d2010-04-11 22:53:42 -0700578 tabsToGo.add(t);
579 }
580 }
581 }
582
583 openTabCount /= 2;
584 if (tabsToGo.size() > openTabCount) {
585 tabsToGo.setSize(openTabCount);
586 }
587
588 return tabsToGo;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800589 }
590
Michael Kolbff6a7482011-07-26 16:37:15 -0700591 Tab getLeastUsedTab(Tab current) {
592 if (getTabCount() == 1 || current == null) {
593 return null;
594 }
595 if (mTabQueue.size() == 0) {
596 return null;
597 }
598 // find a tab which is not the current tab or the parent of the
599 // current tab
600 for (Tab t : mTabQueue) {
601 if (t != null && t.getWebView() != null) {
602 if (t != current && t != current.getParent()) {
603 return t;
604 }
605 }
606 }
607 return null;
608 }
609
The Android Open Source Project0c908882009-03-03 19:32:16 -0800610 /**
611 * Show the tab that contains the given WebView.
612 * @param view The WebView used to find the tab.
613 */
614 Tab getTabFromView(WebView view) {
Michael Kolbc831b632011-05-11 09:30:34 -0700615 for (Tab t : mTabs) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700616 if (t.getSubWebView() == view || t.getWebView() == view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800617 return t;
618 }
619 }
620 return null;
621 }
622
623 /**
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700624 * Return the tab with the matching application id.
625 * @param id The application identifier.
626 */
Michael Kolbc831b632011-05-11 09:30:34 -0700627 Tab getTabFromAppId(String id) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700628 if (id == null) {
629 return null;
630 }
Michael Kolbc831b632011-05-11 09:30:34 -0700631 for (Tab t : mTabs) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700632 if (id.equals(t.getAppId())) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700633 return t;
634 }
635 }
636 return null;
637 }
638
Grace Kloba22ac16e2009-10-07 18:00:23 -0700639 /**
640 * Stop loading in all opened WebView including subWindows.
641 */
Grace Kloba5d0e02e2009-10-05 15:15:36 -0700642 void stopAllLoading() {
Michael Kolbc831b632011-05-11 09:30:34 -0700643 for (Tab t : mTabs) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700644 final WebView webview = t.getWebView();
Grace Kloba5d0e02e2009-10-05 15:15:36 -0700645 if (webview != null) {
646 webview.stopLoading();
647 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700648 final WebView subview = t.getSubWebView();
649 if (subview != null) {
Mattias Nilsson9727af82012-06-14 17:07:56 +0200650 subview.stopLoading();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700651 }
Grace Kloba5d0e02e2009-10-05 15:15:36 -0700652 }
653 }
654
John Reckdb22ec42011-06-29 11:31:24 -0700655 // This method checks if a tab matches the given url.
Patrick Scottcd115892009-07-16 09:42:58 -0400656 private boolean tabMatchesUrl(Tab t, String url) {
John Reckdb22ec42011-06-29 11:31:24 -0700657 return url.equals(t.getUrl()) || url.equals(t.getOriginalUrl());
Patrick Scottcd115892009-07-16 09:42:58 -0400658 }
659
660 /**
John Reckdb22ec42011-06-29 11:31:24 -0700661 * Return the tab that matches the given url.
Patrick Scottcd115892009-07-16 09:42:58 -0400662 * @param url The url to search for.
663 */
John Reckdb22ec42011-06-29 11:31:24 -0700664 Tab findTabWithUrl(String url) {
Patrick Scottcd115892009-07-16 09:42:58 -0400665 if (url == null) {
666 return null;
667 }
668 // Check the current tab first.
John Reckdb22ec42011-06-29 11:31:24 -0700669 Tab currentTab = getCurrentTab();
670 if (currentTab != null && tabMatchesUrl(currentTab, url)) {
671 return currentTab;
Patrick Scottcd115892009-07-16 09:42:58 -0400672 }
673 // Now check all the rest.
Michael Kolbc831b632011-05-11 09:30:34 -0700674 for (Tab tab : mTabs) {
675 if (tabMatchesUrl(tab, url)) {
John Reckdb22ec42011-06-29 11:31:24 -0700676 return tab;
Patrick Scottcd115892009-07-16 09:42:58 -0400677 }
678 }
679 return null;
680 }
681
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700682 /**
John Reck30c714c2010-12-16 17:30:34 -0800683 * Recreate the main WebView of the given tab.
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700684 */
John Reck30c714c2010-12-16 17:30:34 -0800685 void recreateWebView(Tab t) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700686 final WebView w = t.getWebView();
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700687 if (w != null) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700688 t.destroy();
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700689 }
690 // Create a new WebView. If this tab is the current tab, we need to put
691 // back all the clients so force it to be the current tab.
Panos Thomasa9a5a582014-03-18 19:20:08 -0700692 t.setWebView(createNewWebView(t.isPrivateBrowsingEnabled()), false);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700693 if (getCurrentTab() == t) {
694 setCurrentTab(t, true);
695 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700696 }
697
698 /**
699 * Creates a new WebView and registers it with the global settings.
700 */
701 private WebView createNewWebView() {
Elliott Slaughterf0f03952010-07-14 18:10:36 -0700702 return createNewWebView(false);
703 }
704
705 /**
706 * Creates a new WebView and registers it with the global settings.
707 * @param privateBrowsing When true, enables private browsing in the new
708 * WebView.
709 */
710 private WebView createNewWebView(boolean privateBrowsing) {
Stewart Chaoe0e132e2014-12-01 18:28:22 -0500711 return createNewWebView(privateBrowsing, false);
712 }
713
714 private WebView createNewWebView(boolean privateBrowsing, boolean backgroundTab) {
715 return mController.getWebViewFactory().createWebView(privateBrowsing, backgroundTab);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700716 }
717
718 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800719 * Put the current tab in the background and set newTab as the current tab.
720 * @param newTab The new tab. If newTab is null, the current tab is not
721 * set.
722 */
723 boolean setCurrentTab(Tab newTab) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700724 return setCurrentTab(newTab, false);
725 }
726
727 /**
728 * If force is true, this method skips the check for newTab == current.
729 */
730 private boolean setCurrentTab(Tab newTab, boolean force) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800731 Tab current = getTab(mCurrentTab);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700732 if (current == newTab && !force) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800733 return true;
734 }
735 if (current != null) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700736 current.putInBackground();
737 mCurrentTab = -1;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800738 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800739 if (newTab == null) {
740 return false;
741 }
742
743 // Move the newTab to the end of the queue
744 int index = mTabQueue.indexOf(newTab);
745 if (index != -1) {
746 mTabQueue.remove(index);
747 }
748 mTabQueue.add(newTab);
749
The Android Open Source Project0c908882009-03-03 19:32:16 -0800750 // Display the new current tab
751 mCurrentTab = mTabs.indexOf(newTab);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700752 WebView mainView = newTab.getWebView();
John Reckd8c74522011-06-14 08:45:00 -0700753 boolean needRestore = !newTab.isSnapshot() && (mainView == null);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800754 if (needRestore) {
755 // Same work as in createNewTab() except don't do new Tab()
Panos Thomasa9a5a582014-03-18 19:20:08 -0700756 mainView = createNewWebView(newTab.isPrivateBrowsingEnabled());
Steve Block2bc69912009-07-30 14:45:13 +0100757 newTab.setWebView(mainView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800758 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700759 newTab.putInForeground();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800760 return true;
761 }
Michael Kolbfe251992010-07-08 15:41:55 -0700762
John Reck8ee633f2011-08-09 16:00:35 -0700763 public void setOnThumbnailUpdatedListener(OnThumbnailUpdatedListener listener) {
764 mOnThumbnailUpdatedListener = listener;
765 for (Tab t : mTabs) {
766 WebView web = t.getWebView();
767 if (web != null) {
768 web.setPictureListener(listener != null ? t : null);
769 }
770 }
771 }
772
773 public OnThumbnailUpdatedListener getOnThumbnailUpdatedListener() {
774 return mOnThumbnailUpdatedListener;
775 }
776
The Android Open Source Project0c908882009-03-03 19:32:16 -0800777}