/WebVox/src/com/marvin/webvox/TabControl.java

http://eyes-free.googlecode.com/ · Java · 1324 lines · 828 code · 115 blank · 381 comment · 224 complexity · 27e94971d75802ce457ab33c7bf2d2df MD5 · raw file

  1. /*
  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. package com.marvin.webvox;
  17. import com.marvin.webvox.R;
  18. import android.content.Context;
  19. import android.graphics.Bitmap;
  20. import android.graphics.Picture;
  21. //import android.net.http.SslError;
  22. import android.os.Bundle;
  23. import android.os.Message;
  24. import android.util.Log;
  25. import android.view.Gravity;
  26. import android.view.LayoutInflater;
  27. import android.view.View;
  28. import android.view.ViewGroup;
  29. import android.view.View.OnClickListener;
  30. import android.webkit.HttpAuthHandler;
  31. import android.webkit.JsPromptResult;
  32. import android.webkit.JsResult;
  33. import android.webkit.SslErrorHandler;
  34. import android.webkit.WebBackForwardList;
  35. import android.webkit.WebChromeClient;
  36. import android.webkit.WebHistoryItem;
  37. import android.webkit.WebView;
  38. import android.webkit.WebViewClient;
  39. import android.widget.FrameLayout;
  40. import android.widget.ImageButton;
  41. import android.widget.LinearLayout;
  42. import java.io.File;
  43. import java.io.FileInputStream;
  44. import java.util.ArrayList;
  45. import java.util.Vector;
  46. class TabControl {
  47. // Log Tag
  48. private static final String LOGTAG = "TabControl";
  49. // Maximum number of tabs.
  50. static final int MAX_TABS = 8;
  51. // Static instance of an empty callback.
  52. private static final WebViewClient mEmptyClient =
  53. new WebViewClient();
  54. // Instance of BackgroundChromeClient for background tabs.
  55. private final BackgroundChromeClient mBackgroundChromeClient =
  56. new BackgroundChromeClient();
  57. // Private array of WebViews that are used as tabs.
  58. private ArrayList<Tab> mTabs = new ArrayList<Tab>(MAX_TABS);
  59. // Queue of most recently viewed tabs.
  60. private ArrayList<Tab> mTabQueue = new ArrayList<Tab>(MAX_TABS);
  61. // Current position in mTabs.
  62. private int mCurrentTab = -1;
  63. // A private instance of BrowserActivity to interface with when adding and
  64. // switching between tabs.
  65. private final BrowserActivity mActivity;
  66. // Inflation service for making subwindows.
  67. private final LayoutInflater mInflateService;
  68. // Subclass of WebViewClient used in subwindows to notify the main
  69. // WebViewClient of certain WebView activities.
  70. private static class SubWindowClient extends WebViewClient {
  71. // The main WebViewClient.
  72. private final WebViewClient mClient;
  73. SubWindowClient(WebViewClient client) {
  74. mClient = client;
  75. }
  76. @Override
  77. public void doUpdateVisitedHistory(WebView view, String url,
  78. boolean isReload) {
  79. mClient.doUpdateVisitedHistory(view, url, isReload);
  80. }
  81. @Override
  82. public boolean shouldOverrideUrlLoading(WebView view, String url) {
  83. return mClient.shouldOverrideUrlLoading(view, url);
  84. }
  85. // @Override
  86. // public void onReceivedSslError(WebView view, SslErrorHandler handler,
  87. // SslError error) {
  88. // mClient.onReceivedSslError(view, handler, error);
  89. // }
  90. @Override
  91. public void onReceivedHttpAuthRequest(WebView view,
  92. HttpAuthHandler handler, String host, String realm) {
  93. mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
  94. }
  95. @Override
  96. public void onFormResubmission(WebView view, Message dontResend,
  97. Message resend) {
  98. mClient.onFormResubmission(view, dontResend, resend);
  99. }
  100. @Override
  101. public void onReceivedError(WebView view, int errorCode,
  102. String description, String failingUrl) {
  103. mClient.onReceivedError(view, errorCode, description, failingUrl);
  104. }
  105. @Override
  106. public boolean shouldOverrideKeyEvent(WebView view,
  107. android.view.KeyEvent event) {
  108. return mClient.shouldOverrideKeyEvent(view, event);
  109. }
  110. @Override
  111. public void onUnhandledKeyEvent(WebView view,
  112. android.view.KeyEvent event) {
  113. mClient.onUnhandledKeyEvent(view, event);
  114. }
  115. }
  116. // Subclass of WebChromeClient to display javascript dialogs.
  117. private class SubWindowChromeClient extends WebChromeClient {
  118. // This subwindow's tab.
  119. private final Tab mTab;
  120. // The main WebChromeClient.
  121. private final WebChromeClient mClient;
  122. SubWindowChromeClient(Tab t, WebChromeClient client) {
  123. mTab = t;
  124. mClient = client;
  125. }
  126. @Override
  127. public void onProgressChanged(WebView view, int newProgress) {
  128. mClient.onProgressChanged(view, newProgress);
  129. }
  130. @Override
  131. public boolean onCreateWindow(WebView view, boolean dialog,
  132. boolean userGesture, android.os.Message resultMsg) {
  133. return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
  134. }
  135. @Override
  136. public void onCloseWindow(WebView window) {
  137. if (Browser.DEBUG && window != mTab.mSubView) {
  138. throw new AssertionError("Can't close the window");
  139. }
  140. mActivity.dismissSubWindow(mTab);
  141. }
  142. }
  143. // Background WebChromeClient for focusing tabs
  144. private class BackgroundChromeClient extends WebChromeClient {
  145. @Override
  146. public void onRequestFocus(WebView view) {
  147. Tab t = getTabFromView(view);
  148. if (t != getCurrentTab()) {
  149. mActivity.switchToTab(getTabIndex(t));
  150. }
  151. }
  152. }
  153. // Extra saved information for displaying the tab in the picker.
  154. public static class PickerData {
  155. String mUrl;
  156. String mTitle;
  157. Bitmap mFavicon;
  158. float mScale;
  159. int mScrollX;
  160. int mScrollY;
  161. }
  162. /**
  163. * Private class for maintaining Tabs with a main WebView and a subwindow.
  164. */
  165. public class Tab {
  166. // The Geolocation permissions prompt
  167. private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
  168. private View mContainer;
  169. // Main WebView
  170. private WebView mMainView;
  171. // Subwindow WebView
  172. private WebView mSubView;
  173. // Subwindow container
  174. private View mSubViewContainer;
  175. // Subwindow callback
  176. private SubWindowClient mSubViewClient;
  177. // Subwindow chrome callback
  178. private SubWindowChromeClient mSubViewChromeClient;
  179. // Saved bundle for when we are running low on memory. It contains the
  180. // information needed to restore the WebView if the user goes back to
  181. // the tab.
  182. private Bundle mSavedState;
  183. // Data used when displaying the tab in the picker.
  184. private PickerData mPickerData;
  185. // Parent Tab. This is the Tab that created this Tab, or null
  186. // if the Tab was created by the UI
  187. private Tab mParentTab;
  188. // Tab that constructed by this Tab. This is used when this
  189. // Tab is destroyed, it clears all mParentTab values in the
  190. // children.
  191. private Vector<Tab> mChildTabs;
  192. private Boolean mCloseOnExit;
  193. // Application identifier used to find tabs that another application
  194. // wants to reuse.
  195. private String mAppId;
  196. // Keep the original url around to avoid killing the old WebView if the
  197. // url has not changed.
  198. private String mOriginalUrl;
  199. private ErrorConsoleView mErrorConsole;
  200. // the lock icon type and previous lock icon type for the tab
  201. private int mSavedLockIconType;
  202. private int mSavedPrevLockIconType;
  203. // Construct a new tab
  204. private Tab(WebView w, boolean closeOnExit, String appId, String url, Context context) {
  205. mCloseOnExit = closeOnExit;
  206. mAppId = appId;
  207. mOriginalUrl = url;
  208. mSavedLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
  209. mSavedPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
  210. // The tab consists of a container view, which contains the main
  211. // WebView, as well as any other UI elements associated with the tab.
  212. LayoutInflater factory = LayoutInflater.from(context);
  213. mContainer = factory.inflate(R.layout.tab, null);
  214. // mGeolocationPermissionsPrompt =
  215. // (GeolocationPermissionsPrompt) mContainer.findViewById(
  216. // R.id.geolocation_permissions_prompt);
  217. setWebView(w);
  218. }
  219. /**
  220. * Sets the WebView for this tab, correctly removing the old WebView
  221. * from the container view.
  222. */
  223. public void setWebView(WebView w) {
  224. if (mMainView == w) {
  225. return;
  226. }
  227. // If the WebView is changing, the page will be reloaded, so any ongoing Geolocation
  228. // permission requests are void.
  229. //mGeolocationPermissionsPrompt.hide();
  230. // Just remove the old one.
  231. FrameLayout wrapper =
  232. (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
  233. wrapper.removeView(mMainView);
  234. mMainView = w;
  235. }
  236. /**
  237. * This method attaches both the WebView and any sub window to the
  238. * given content view.
  239. */
  240. public void attachTabToContentView(ViewGroup content) {
  241. if (mMainView == null) {
  242. return;
  243. }
  244. // Attach the WebView to the container and then attach the
  245. // container to the content view.
  246. FrameLayout wrapper =
  247. (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
  248. wrapper.addView(mMainView);
  249. content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
  250. attachSubWindow(content);
  251. }
  252. /**
  253. * Remove the WebView and any sub window from the given content view.
  254. */
  255. public void removeTabFromContentView(ViewGroup content) {
  256. if (mMainView == null) {
  257. return;
  258. }
  259. // Remove the container from the content and then remove the
  260. // WebView from the container. This will trigger a focus change
  261. // needed by WebView.
  262. FrameLayout wrapper =
  263. (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
  264. wrapper.removeView(mMainView);
  265. content.removeView(mContainer);
  266. removeSubWindow(content);
  267. }
  268. /**
  269. * Attach the sub window to the content view.
  270. */
  271. public void attachSubWindow(ViewGroup content) {
  272. if (mSubView != null) {
  273. content.addView(mSubViewContainer,
  274. BrowserActivity.COVER_SCREEN_PARAMS);
  275. }
  276. }
  277. /**
  278. * Remove the sub window from the content view.
  279. */
  280. public void removeSubWindow(ViewGroup content) {
  281. if (mSubView != null) {
  282. content.removeView(mSubViewContainer);
  283. }
  284. }
  285. /**
  286. * Return the top window of this tab; either the subwindow if it is not
  287. * null or the main window.
  288. * @return The top window of this tab.
  289. */
  290. public WebView getTopWindow() {
  291. if (mSubView != null) {
  292. return mSubView;
  293. }
  294. return mMainView;
  295. }
  296. /**
  297. * Return the main window of this tab. Note: if a tab is freed in the
  298. * background, this can return null. It is only guaranteed to be
  299. * non-null for the current tab.
  300. * @return The main WebView of this tab.
  301. */
  302. public WebView getWebView() {
  303. return mMainView;
  304. }
  305. /**
  306. * @return The geolocation permissions prompt for this tab.
  307. */
  308. public GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
  309. return mGeolocationPermissionsPrompt;
  310. }
  311. /**
  312. * Return the subwindow of this tab or null if there is no subwindow.
  313. * @return The subwindow of this tab or null.
  314. */
  315. public WebView getSubWebView() {
  316. return mSubView;
  317. }
  318. /**
  319. * Get the url of this tab. Valid after calling populatePickerData, but
  320. * before calling wipePickerData, or if the webview has been destroyed.
  321. *
  322. * @return The WebView's url or null.
  323. */
  324. public String getUrl() {
  325. if (mPickerData != null) {
  326. return mPickerData.mUrl;
  327. }
  328. return null;
  329. }
  330. /**
  331. * Get the title of this tab. Valid after calling populatePickerData,
  332. * but before calling wipePickerData, or if the webview has been
  333. * destroyed. If the url has no title, use the url instead.
  334. *
  335. * @return The WebView's title (or url) or null.
  336. */
  337. public String getTitle() {
  338. if (mPickerData != null) {
  339. return mPickerData.mTitle;
  340. }
  341. return null;
  342. }
  343. public Bitmap getFavicon() {
  344. if (mPickerData != null) {
  345. return mPickerData.mFavicon;
  346. }
  347. return null;
  348. }
  349. private void setParentTab(Tab parent) {
  350. mParentTab = parent;
  351. // This tab may have been freed due to low memory. If that is the
  352. // case, the parent tab index is already saved. If we are changing
  353. // that index (most likely due to removing the parent tab) we must
  354. // update the parent tab index in the saved Bundle.
  355. if (mSavedState != null) {
  356. if (parent == null) {
  357. mSavedState.remove(PARENTTAB);
  358. } else {
  359. mSavedState.putInt(PARENTTAB, getTabIndex(parent));
  360. }
  361. }
  362. }
  363. /**
  364. * When a Tab is created through the content of another Tab, then
  365. * we associate the Tabs.
  366. * @param child the Tab that was created from this Tab
  367. */
  368. public void addChildTab(Tab child) {
  369. if (mChildTabs == null) {
  370. mChildTabs = new Vector<Tab>();
  371. }
  372. mChildTabs.add(child);
  373. child.setParentTab(this);
  374. }
  375. private void removeFromTree() {
  376. // detach the children
  377. if (mChildTabs != null) {
  378. for(Tab t : mChildTabs) {
  379. t.setParentTab(null);
  380. }
  381. }
  382. // Find myself in my parent list
  383. if (mParentTab != null) {
  384. mParentTab.mChildTabs.remove(this);
  385. }
  386. }
  387. /**
  388. * If this Tab was created through another Tab, then this method
  389. * returns that Tab.
  390. * @return the Tab parent or null
  391. */
  392. public Tab getParentTab() {
  393. return mParentTab;
  394. }
  395. /**
  396. * Return whether this tab should be closed when it is backing out of
  397. * the first page.
  398. * @return TRUE if this tab should be closed when exit.
  399. */
  400. public boolean closeOnExit() {
  401. return mCloseOnExit;
  402. }
  403. void setLockIconType(int type) {
  404. mSavedLockIconType = type;
  405. }
  406. int getLockIconType() {
  407. return mSavedLockIconType;
  408. }
  409. void setPrevLockIconType(int type) {
  410. mSavedPrevLockIconType = type;
  411. }
  412. int getPrevLockIconType() {
  413. return mSavedPrevLockIconType;
  414. }
  415. };
  416. // Directory to store thumbnails for each WebView.
  417. private final File mThumbnailDir;
  418. /**
  419. * Construct a new TabControl object that interfaces with the given
  420. * BrowserActivity instance.
  421. * @param activity A BrowserActivity instance that TabControl will interface
  422. * with.
  423. */
  424. TabControl(BrowserActivity activity) {
  425. mActivity = activity;
  426. mInflateService =
  427. ((LayoutInflater) activity.getSystemService(
  428. Context.LAYOUT_INFLATER_SERVICE));
  429. mThumbnailDir = activity.getDir("thumbnails", 0);
  430. }
  431. File getThumbnailDir() {
  432. return mThumbnailDir;
  433. }
  434. BrowserActivity getBrowserActivity() {
  435. return mActivity;
  436. }
  437. /**
  438. * Return the current tab's main WebView. This will always return the main
  439. * WebView for a given tab and not a subwindow.
  440. * @return The current tab's WebView.
  441. */
  442. WebView getCurrentWebView() {
  443. Tab t = getTab(mCurrentTab);
  444. if (t == null) {
  445. return null;
  446. }
  447. return t.mMainView;
  448. }
  449. /**
  450. * Return the current tab's error console. Creates the console if createIfNEcessary
  451. * is true and we haven't already created the console.
  452. * @param createIfNecessary Flag to indicate if the console should be created if it has
  453. * not been already.
  454. * @return The current tab's error console, or null if one has not been created and
  455. * createIfNecessary is false.
  456. */
  457. ErrorConsoleView getCurrentErrorConsole(boolean createIfNecessary) {
  458. Tab t = getTab(mCurrentTab);
  459. if (t == null) {
  460. return null;
  461. }
  462. if (createIfNecessary && t.mErrorConsole == null) {
  463. t.mErrorConsole = new ErrorConsoleView(mActivity);
  464. t.mErrorConsole.setWebView(t.mMainView);
  465. }
  466. return t.mErrorConsole;
  467. }
  468. /**
  469. * Return the current tab's top-level WebView. This can return a subwindow
  470. * if one exists.
  471. * @return The top-level WebView of the current tab.
  472. */
  473. WebView getCurrentTopWebView() {
  474. Tab t = getTab(mCurrentTab);
  475. if (t == null) {
  476. return null;
  477. }
  478. return t.mSubView != null ? t.mSubView : t.mMainView;
  479. }
  480. /**
  481. * Return the current tab's subwindow if it exists.
  482. * @return The subwindow of the current tab or null if it doesn't exist.
  483. */
  484. WebView getCurrentSubWindow() {
  485. Tab t = getTab(mCurrentTab);
  486. if (t == null) {
  487. return null;
  488. }
  489. return t.mSubView;
  490. }
  491. /**
  492. * Return the tab at the specified index.
  493. * @return The Tab for the specified index or null if the tab does not
  494. * exist.
  495. */
  496. Tab getTab(int index) {
  497. if (index >= 0 && index < mTabs.size()) {
  498. return mTabs.get(index);
  499. }
  500. return null;
  501. }
  502. /**
  503. * Return the current tab.
  504. * @return The current tab.
  505. */
  506. Tab getCurrentTab() {
  507. return getTab(mCurrentTab);
  508. }
  509. /**
  510. * Return the current tab index.
  511. * @return The current tab index
  512. */
  513. int getCurrentIndex() {
  514. return mCurrentTab;
  515. }
  516. /**
  517. * Given a Tab, find it's index
  518. * @param Tab to find
  519. * @return index of Tab or -1 if not found
  520. */
  521. int getTabIndex(Tab tab) {
  522. if (tab == null) {
  523. return -1;
  524. }
  525. return mTabs.indexOf(tab);
  526. }
  527. /**
  528. * Create a new tab.
  529. * @return The newly createTab or null if we have reached the maximum
  530. * number of open tabs.
  531. */
  532. Tab createNewTab(boolean closeOnExit, String appId, String url) {
  533. int size = mTabs.size();
  534. // Return false if we have maxed out on tabs
  535. if (MAX_TABS == size) {
  536. return null;
  537. }
  538. final WebView w = createNewWebView();
  539. // Create a new tab and add it to the tab list
  540. Tab t = new Tab(w, closeOnExit, appId, url, mActivity);
  541. mTabs.add(t);
  542. // Initially put the tab in the background.
  543. putTabInBackground(t);
  544. return t;
  545. }
  546. /**
  547. * Create a new tab with default values for closeOnExit(false),
  548. * appId(null), and url(null).
  549. */
  550. Tab createNewTab() {
  551. return createNewTab(false, null, null);
  552. }
  553. /**
  554. * Remove the tab from the list. If the tab is the current tab shown, the
  555. * last created tab will be shown.
  556. * @param t The tab to be removed.
  557. */
  558. boolean removeTab(Tab t) {
  559. if (t == null) {
  560. return false;
  561. }
  562. // Only remove the tab if it is the current one.
  563. if (getCurrentTab() == t) {
  564. putTabInBackground(t);
  565. }
  566. // Only destroy the WebView if it still exists.
  567. if (t.mMainView != null) {
  568. // Take down the sub window.
  569. dismissSubWindow(t);
  570. // Remove the WebView's settings from the BrowserSettings list of
  571. // observers.
  572. BrowserSettings.getInstance().deleteObserver(
  573. t.mMainView.getSettings());
  574. WebView w = t.mMainView;
  575. t.setWebView(null);
  576. // Destroy the main view
  577. w.destroy();
  578. }
  579. // clear it's references to parent and children
  580. t.removeFromTree();
  581. // Remove it from our list of tabs.
  582. mTabs.remove(t);
  583. // The tab indices have shifted, update all the saved state so we point
  584. // to the correct index.
  585. for (Tab tab : mTabs) {
  586. if (tab.mChildTabs != null) {
  587. for (Tab child : tab.mChildTabs) {
  588. child.setParentTab(tab);
  589. }
  590. }
  591. }
  592. // This tab may have been pushed in to the background and then closed.
  593. // If the saved state contains a picture file, delete the file.
  594. if (t.mSavedState != null) {
  595. if (t.mSavedState.containsKey(CURRPICTURE)) {
  596. new File(t.mSavedState.getString(CURRPICTURE)).delete();
  597. }
  598. }
  599. // Remove it from the queue of viewed tabs.
  600. mTabQueue.remove(t);
  601. mCurrentTab = -1;
  602. return true;
  603. }
  604. /**
  605. * Clear the back/forward list for all the current tabs.
  606. */
  607. void clearHistory() {
  608. int size = getTabCount();
  609. for (int i = 0; i < size; i++) {
  610. Tab t = mTabs.get(i);
  611. // TODO: if a tab is freed due to low memory, its history is not
  612. // cleared here.
  613. if (t.mMainView != null) {
  614. t.mMainView.clearHistory();
  615. }
  616. if (t.mSubView != null) {
  617. t.mSubView.clearHistory();
  618. }
  619. }
  620. }
  621. /**
  622. * Destroy all the tabs and subwindows
  623. */
  624. void destroy() {
  625. BrowserSettings s = BrowserSettings.getInstance();
  626. for (Tab t : mTabs) {
  627. if (t.mMainView != null) {
  628. dismissSubWindow(t);
  629. s.deleteObserver(t.mMainView.getSettings());
  630. WebView w = t.mMainView;
  631. t.setWebView(null);
  632. w.destroy();
  633. }
  634. }
  635. mTabs.clear();
  636. mTabQueue.clear();
  637. }
  638. /**
  639. * Returns the number of tabs created.
  640. * @return The number of tabs created.
  641. */
  642. int getTabCount() {
  643. return mTabs.size();
  644. }
  645. // Used for saving and restoring each Tab
  646. private static final String WEBVIEW = "webview";
  647. private static final String NUMTABS = "numTabs";
  648. private static final String CURRTAB = "currentTab";
  649. private static final String CURRURL = "currentUrl";
  650. private static final String CURRTITLE = "currentTitle";
  651. private static final String CURRPICTURE = "currentPicture";
  652. private static final String CLOSEONEXIT = "closeonexit";
  653. private static final String PARENTTAB = "parentTab";
  654. private static final String APPID = "appid";
  655. private static final String ORIGINALURL = "originalUrl";
  656. /**
  657. * Save the state of all the Tabs.
  658. * @param outState The Bundle to save the state to.
  659. */
  660. void saveState(Bundle outState) {
  661. final int numTabs = getTabCount();
  662. outState.putInt(NUMTABS, numTabs);
  663. final int index = getCurrentIndex();
  664. outState.putInt(CURRTAB, (index >= 0 && index < numTabs) ? index : 0);
  665. for (int i = 0; i < numTabs; i++) {
  666. final Tab t = getTab(i);
  667. if (saveState(t)) {
  668. outState.putBundle(WEBVIEW + i, t.mSavedState);
  669. }
  670. }
  671. }
  672. /**
  673. * Restore the state of all the tabs.
  674. * @param inState The saved state of all the tabs.
  675. * @return True if there were previous tabs that were restored. False if
  676. * there was no saved state or restoring the state failed.
  677. */
  678. boolean restoreState(Bundle inState) {
  679. final int numTabs = (inState == null)
  680. ? -1 : inState.getInt(NUMTABS, -1);
  681. if (numTabs == -1) {
  682. return false;
  683. } else {
  684. final int currentTab = inState.getInt(CURRTAB, -1);
  685. for (int i = 0; i < numTabs; i++) {
  686. if (i == currentTab) {
  687. Tab t = createNewTab();
  688. // Me must set the current tab before restoring the state
  689. // so that all the client classes are set.
  690. setCurrentTab(t);
  691. if (!restoreState(inState.getBundle(WEBVIEW + i), t)) {
  692. Log.w(LOGTAG, "Fail in restoreState, load home page.");
  693. t.mMainView.loadUrl(BrowserSettings.getInstance()
  694. .getHomePage());
  695. }
  696. } else {
  697. // Create a new tab and don't restore the state yet, add it
  698. // to the tab list
  699. Tab t = new Tab(null, false, null, null, mActivity);
  700. t.mSavedState = inState.getBundle(WEBVIEW + i);
  701. if (t.mSavedState != null) {
  702. populatePickerDataFromSavedState(t);
  703. // Need to maintain the app id and original url so we
  704. // can possibly reuse this tab.
  705. t.mAppId = t.mSavedState.getString(APPID);
  706. t.mOriginalUrl = t.mSavedState.getString(ORIGINALURL);
  707. }
  708. mTabs.add(t);
  709. mTabQueue.add(t);
  710. }
  711. }
  712. // Rebuild the tree of tabs. Do this after all tabs have been
  713. // created/restored so that the parent tab exists.
  714. for (int i = 0; i < numTabs; i++) {
  715. final Bundle b = inState.getBundle(WEBVIEW + i);
  716. final Tab t = getTab(i);
  717. if (b != null && t != null) {
  718. final int parentIndex = b.getInt(PARENTTAB, -1);
  719. if (parentIndex != -1) {
  720. final Tab parent = getTab(parentIndex);
  721. if (parent != null) {
  722. parent.addChildTab(t);
  723. }
  724. }
  725. }
  726. }
  727. }
  728. return true;
  729. }
  730. /**
  731. * Free the memory in this order, 1) free the background tab; 2) free the
  732. * WebView cache;
  733. */
  734. void freeMemory() {
  735. if (getTabCount() == 0) return;
  736. // free the least frequently used background tab
  737. Tab t = getLeastUsedTab(getCurrentTab());
  738. if (t != null) {
  739. Log.w(LOGTAG, "Free a tab in the browser");
  740. freeTab(t);
  741. // force a gc
  742. System.gc();
  743. return;
  744. }
  745. // free the WebView's unused memory (this includes the cache)
  746. Log.w(LOGTAG, "Free WebView's unused memory and cache");
  747. WebView view = getCurrentWebView();
  748. if (view != null) {
  749. // view.freeMemory();
  750. }
  751. // force a gc
  752. System.gc();
  753. }
  754. private Tab getLeastUsedTab(Tab current) {
  755. // Don't do anything if we only have 1 tab or if the current tab is
  756. // null.
  757. if (getTabCount() == 1 || current == null) {
  758. return null;
  759. }
  760. // Rip through the queue starting at the beginning and teardown the
  761. // next available tab.
  762. Tab t = null;
  763. int i = 0;
  764. final int queueSize = mTabQueue.size();
  765. if (queueSize == 0) {
  766. return null;
  767. }
  768. do {
  769. t = mTabQueue.get(i++);
  770. } while (i < queueSize
  771. && ((t != null && t.mMainView == null)
  772. || t == current.mParentTab));
  773. // Don't do anything if the last remaining tab is the current one or if
  774. // the last tab has been freed already.
  775. if (t == current || t.mMainView == null) {
  776. return null;
  777. }
  778. return t;
  779. }
  780. private void freeTab(Tab t) {
  781. // Store the WebView's state.
  782. saveState(t);
  783. // Tear down the tab.
  784. dismissSubWindow(t);
  785. // Remove the WebView's settings from the BrowserSettings list of
  786. // observers.
  787. BrowserSettings.getInstance().deleteObserver(t.mMainView.getSettings());
  788. WebView w = t.mMainView;
  789. t.setWebView(null);
  790. w.destroy();
  791. }
  792. /**
  793. * Create a new subwindow unless a subwindow already exists.
  794. * @return True if a new subwindow was created. False if one already exists.
  795. */
  796. void createSubWindow() {
  797. Tab t = getTab(mCurrentTab);
  798. if (t != null && t.mSubView == null) {
  799. final View v = mInflateService.inflate(R.layout.browser_subwindow, null);
  800. final WebView w = (WebView) v.findViewById(R.id.webview);
  801. w.setMapTrackballToArrowKeys(false); // use trackball directly
  802. final SubWindowClient subClient =
  803. new SubWindowClient(mActivity.getWebViewClient());
  804. final SubWindowChromeClient subChromeClient =
  805. new SubWindowChromeClient(t,
  806. mActivity.getWebChromeClient());
  807. w.setWebViewClient(subClient);
  808. w.setWebChromeClient(subChromeClient);
  809. w.setDownloadListener(mActivity);
  810. w.setOnCreateContextMenuListener(mActivity);
  811. final BrowserSettings s = BrowserSettings.getInstance();
  812. s.addObserver(w.getSettings()).update(s, null);
  813. t.mSubView = w;
  814. t.mSubViewClient = subClient;
  815. t.mSubViewChromeClient = subChromeClient;
  816. // FIXME: I really hate having to know the name of the view
  817. // containing the webview.
  818. t.mSubViewContainer = v.findViewById(R.id.subwindow_container);
  819. final ImageButton cancel =
  820. (ImageButton) v.findViewById(R.id.subwindow_close);
  821. cancel.setOnClickListener(new OnClickListener() {
  822. public void onClick(View v) {
  823. subChromeClient.onCloseWindow(w);
  824. }
  825. });
  826. }
  827. }
  828. /**
  829. * Show the tab that contains the given WebView.
  830. * @param view The WebView used to find the tab.
  831. */
  832. Tab getTabFromView(WebView view) {
  833. final int size = getTabCount();
  834. for (int i = 0; i < size; i++) {
  835. final Tab t = getTab(i);
  836. if (t.mSubView == view || t.mMainView == view) {
  837. return t;
  838. }
  839. }
  840. return null;
  841. }
  842. /**
  843. * Return the tab with the matching application id.
  844. * @param id The application identifier.
  845. */
  846. Tab getTabFromId(String id) {
  847. if (id == null) {
  848. return null;
  849. }
  850. final int size = getTabCount();
  851. for (int i = 0; i < size; i++) {
  852. final Tab t = getTab(i);
  853. if (id.equals(t.mAppId)) {
  854. return t;
  855. }
  856. }
  857. return null;
  858. }
  859. // This method checks if a non-app tab (one created within the browser)
  860. // matches the given url.
  861. private boolean tabMatchesUrl(Tab t, String url) {
  862. if (t.mAppId != null) {
  863. return false;
  864. } else if (t.mMainView == null) {
  865. return false;
  866. } else if (url.equals(t.mMainView.getUrl()) ||
  867. url.equals(t.mMainView.getOriginalUrl())) {
  868. return true;
  869. }
  870. return false;
  871. }
  872. /**
  873. * Return the tab that has no app id associated with it and the url of the
  874. * tab matches the given url.
  875. * @param url The url to search for.
  876. */
  877. Tab findUnusedTabWithUrl(String url) {
  878. if (url == null) {
  879. return null;
  880. }
  881. // Check the current tab first.
  882. Tab t = getCurrentTab();
  883. if (t != null && tabMatchesUrl(t, url)) {
  884. return t;
  885. }
  886. // Now check all the rest.
  887. final int size = getTabCount();
  888. for (int i = 0; i < size; i++) {
  889. t = getTab(i);
  890. if (tabMatchesUrl(t, url)) {
  891. return t;
  892. }
  893. }
  894. return null;
  895. }
  896. /**
  897. * Recreate the main WebView of the given tab. Returns true if the WebView
  898. * was deleted.
  899. */
  900. boolean recreateWebView(Tab t, String url) {
  901. final WebView w = t.mMainView;
  902. if (w != null) {
  903. if (url != null && url.equals(t.mOriginalUrl)) {
  904. // The original url matches the current url. Just go back to the
  905. // first history item so we can load it faster than if we
  906. // rebuilt the WebView.
  907. final WebBackForwardList list = w.copyBackForwardList();
  908. if (list != null) {
  909. w.goBackOrForward(-list.getCurrentIndex());
  910. w.clearHistory(); // maintains the current page.
  911. return false;
  912. }
  913. }
  914. // Remove the settings object from the global settings and destroy
  915. // the WebView.
  916. BrowserSettings.getInstance().deleteObserver(
  917. t.mMainView.getSettings());
  918. t.mMainView.destroy();
  919. }
  920. // Create a new WebView. If this tab is the current tab, we need to put
  921. // back all the clients so force it to be the current tab.
  922. t.setWebView(createNewWebView());
  923. if (getCurrentTab() == t) {
  924. setCurrentTab(t, true);
  925. }
  926. // Clear the saved state except for the app id and close-on-exit
  927. // values.
  928. t.mSavedState = null;
  929. t.mPickerData = null;
  930. // Save the new url in order to avoid deleting the WebView.
  931. t.mOriginalUrl = url;
  932. return true;
  933. }
  934. /**
  935. * Creates a new WebView and registers it with the global settings.
  936. */
  937. private WebView createNewWebView() {
  938. // Create a new WebView
  939. WebView w = new WebView(mActivity);
  940. w.setScrollbarFadingEnabled(true);
  941. w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
  942. w.setMapTrackballToArrowKeys(false); // use trackball directly
  943. // Enable the built-in zoom
  944. w.getSettings().setBuiltInZoomControls(true);
  945. // Add this WebView to the settings observer list and update the
  946. // settings
  947. final BrowserSettings s = BrowserSettings.getInstance();
  948. s.addObserver(w.getSettings()).update(s, null);
  949. return w;
  950. }
  951. /**
  952. * Put the current tab in the background and set newTab as the current tab.
  953. * @param newTab The new tab. If newTab is null, the current tab is not
  954. * set.
  955. */
  956. boolean setCurrentTab(Tab newTab) {
  957. return setCurrentTab(newTab, false);
  958. }
  959. /*package*/ void pauseCurrentTab() {
  960. Tab t = getCurrentTab();
  961. if (t != null) {
  962. // t.mMainView.onPause();
  963. if (t.mSubView != null) {
  964. // t.mSubView.onPause();
  965. }
  966. }
  967. }
  968. /*package*/ void resumeCurrentTab() {
  969. Tab t = getCurrentTab();
  970. if (t != null) {
  971. // t.mMainView.onResume();
  972. if (t.mSubView != null) {
  973. // t.mSubView.onResume();
  974. }
  975. }
  976. }
  977. private void putViewInForeground(WebView v, WebViewClient vc,
  978. WebChromeClient cc) {
  979. v.setWebViewClient(vc);
  980. v.setWebChromeClient(cc);
  981. v.setOnCreateContextMenuListener(mActivity);
  982. v.setDownloadListener(mActivity);
  983. // v.onResume();
  984. }
  985. private void putViewInBackground(WebView v) {
  986. // Set an empty callback so that default actions are not triggered.
  987. v.setWebViewClient(mEmptyClient);
  988. v.setWebChromeClient(mBackgroundChromeClient);
  989. v.setOnCreateContextMenuListener(null);
  990. // Leave the DownloadManager attached so that downloads can start in
  991. // a non-active window. This can happen when going to a site that does
  992. // a redirect after a period of time. The user could have switched to
  993. // another tab while waiting for the download to start.
  994. v.setDownloadListener(mActivity);
  995. // v.onPause();
  996. }
  997. /**
  998. * If force is true, this method skips the check for newTab == current.
  999. */
  1000. private boolean setCurrentTab(Tab newTab, boolean force) {
  1001. Tab current = getTab(mCurrentTab);
  1002. if (current == newTab && !force) {
  1003. return true;
  1004. }
  1005. if (current != null) {
  1006. // Remove the current WebView and the container of the subwindow
  1007. putTabInBackground(current);
  1008. }
  1009. if (newTab == null) {
  1010. return false;
  1011. }
  1012. // Move the newTab to the end of the queue
  1013. int index = mTabQueue.indexOf(newTab);
  1014. if (index != -1) {
  1015. mTabQueue.remove(index);
  1016. }
  1017. mTabQueue.add(newTab);
  1018. WebView mainView;
  1019. // Display the new current tab
  1020. mCurrentTab = mTabs.indexOf(newTab);
  1021. mainView = newTab.mMainView;
  1022. boolean needRestore = (mainView == null);
  1023. if (needRestore) {
  1024. // Same work as in createNewTab() except don't do new Tab()
  1025. mainView = createNewWebView();
  1026. newTab.setWebView(mainView);
  1027. }
  1028. putViewInForeground(mainView, mActivity.getWebViewClient(),
  1029. mActivity.getWebChromeClient());
  1030. // Add the subwindow if it exists
  1031. if (newTab.mSubViewContainer != null) {
  1032. putViewInForeground(newTab.mSubView, newTab.mSubViewClient,
  1033. newTab.mSubViewChromeClient);
  1034. }
  1035. if (needRestore) {
  1036. // Have to finish setCurrentTab work before calling restoreState
  1037. if (!restoreState(newTab.mSavedState, newTab)) {
  1038. mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
  1039. }
  1040. }
  1041. return true;
  1042. }
  1043. /*
  1044. * Put the tab in the background using all the empty/background clients.
  1045. */
  1046. private void putTabInBackground(Tab t) {
  1047. putViewInBackground(t.mMainView);
  1048. if (t.mSubView != null) {
  1049. putViewInBackground(t.mSubView);
  1050. }
  1051. }
  1052. /*
  1053. * Dismiss the subwindow for the given tab.
  1054. */
  1055. void dismissSubWindow(Tab t) {
  1056. if (t != null && t.mSubView != null) {
  1057. BrowserSettings.getInstance().deleteObserver(
  1058. t.mSubView.getSettings());
  1059. t.mSubView.destroy();
  1060. t.mSubView = null;
  1061. t.mSubViewContainer = null;
  1062. }
  1063. }
  1064. /**
  1065. * Ensure that Tab t has data to display in the tab picker.
  1066. * @param t Tab to populate.
  1067. */
  1068. /* package */ void populatePickerData(Tab t) {
  1069. if (t == null) {
  1070. return;
  1071. }
  1072. // mMainView == null indicates that the tab has been freed.
  1073. if (t.mMainView == null) {
  1074. populatePickerDataFromSavedState(t);
  1075. return;
  1076. }
  1077. // FIXME: The only place we cared about subwindow was for
  1078. // bookmarking (i.e. not when saving state). Was this deliberate?
  1079. final WebBackForwardList list = t.mMainView.copyBackForwardList();
  1080. final WebHistoryItem item =
  1081. list != null ? list.getCurrentItem() : null;
  1082. populatePickerData(t, item);
  1083. }
  1084. // Create the PickerData and populate it using the saved state of the tab.
  1085. private void populatePickerDataFromSavedState(Tab t) {
  1086. if (t.mSavedState == null) {
  1087. return;
  1088. }
  1089. final PickerData data = new PickerData();
  1090. final Bundle state = t.mSavedState;
  1091. data.mUrl = state.getString(CURRURL);
  1092. data.mTitle = state.getString(CURRTITLE);
  1093. // XXX: These keys are from WebView.savePicture so if they change, this
  1094. // will break.
  1095. data.mScale = state.getFloat("scale", 1.0f);
  1096. data.mScrollX = state.getInt("scrollX", 0);
  1097. data.mScrollY = state.getInt("scrollY", 0);
  1098. // Set the tab's picker data.
  1099. t.mPickerData = data;
  1100. }
  1101. // Populate the picker data using the given history item and the current
  1102. // top WebView.
  1103. private void populatePickerData(Tab t, WebHistoryItem item) {
  1104. final PickerData data = new PickerData();
  1105. if (item != null) {
  1106. data.mUrl = item.getUrl();
  1107. data.mTitle = item.getTitle();
  1108. data.mFavicon = item.getFavicon();
  1109. if (data.mTitle == null) {
  1110. data.mTitle = data.mUrl;
  1111. }
  1112. }
  1113. // We want to display the top window in the tab picker but use the url
  1114. // and title of the main window.
  1115. final WebView w = t.getTopWindow();
  1116. data.mScale = w.getScale();
  1117. data.mScrollX = w.getScrollX();
  1118. data.mScrollY = w.getScrollY();
  1119. t.mPickerData = data;
  1120. }
  1121. /**
  1122. * Clean up the data for all tabs.
  1123. */
  1124. /* package */ void wipeAllPickerData() {
  1125. int size = getTabCount();
  1126. for (int i = 0; i < size; i++) {
  1127. final Tab t = getTab(i);
  1128. if (t != null && t.mSavedState == null) {
  1129. t.mPickerData = null;
  1130. }
  1131. }
  1132. }
  1133. /*
  1134. * Save the state for an individual tab.
  1135. */
  1136. private boolean saveState(Tab t) {
  1137. if (t != null) {
  1138. final WebView w = t.mMainView;
  1139. // If the WebView is null it means we ran low on memory and we
  1140. // already stored the saved state in mSavedState.
  1141. if (w == null) {
  1142. return true;
  1143. }
  1144. final Bundle b = new Bundle();
  1145. final WebBackForwardList list = w.saveState(b);
  1146. if (list != null) {
  1147. final File f = new File(mThumbnailDir, w.hashCode()
  1148. + "_pic.save");
  1149. if (w.savePicture(b, f)) {
  1150. b.putString(CURRPICTURE, f.getPath());
  1151. }
  1152. }
  1153. // Store some extra info for displaying the tab in the picker.
  1154. final WebHistoryItem item =
  1155. list != null ? list.getCurrentItem() : null;
  1156. populatePickerData(t, item);
  1157. // XXX: WebView.savePicture stores the scale and scroll positions
  1158. // in the bundle so we don't have to do it here.
  1159. final PickerData data = t.mPickerData;
  1160. if (data.mUrl != null) {
  1161. b.putString(CURRURL, data.mUrl);
  1162. }
  1163. if (data.mTitle != null) {
  1164. b.putString(CURRTITLE, data.mTitle);
  1165. }
  1166. b.putBoolean(CLOSEONEXIT, t.mCloseOnExit);
  1167. if (t.mAppId != null) {
  1168. b.putString(APPID, t.mAppId);
  1169. }
  1170. if (t.mOriginalUrl != null) {
  1171. b.putString(ORIGINALURL, t.mOriginalUrl);
  1172. }
  1173. // Remember the parent tab so the relationship can be restored.
  1174. if (t.mParentTab != null) {
  1175. b.putInt(PARENTTAB, getTabIndex(t.mParentTab));
  1176. }
  1177. // Remember the saved state.
  1178. t.mSavedState = b;
  1179. return true;
  1180. }
  1181. return false;
  1182. }
  1183. /*
  1184. * Restore the state of the tab.
  1185. */
  1186. private boolean restoreState(Bundle b, Tab t) {
  1187. if (b == null) {
  1188. return false;
  1189. }
  1190. // Restore the internal state even if the WebView fails to restore.
  1191. // This will maintain the app id, original url and close-on-exit values.
  1192. t.mSavedState = null;
  1193. t.mPickerData = null;
  1194. t.mCloseOnExit = b.getBoolean(CLOSEONEXIT);
  1195. t.mAppId = b.getString(APPID);
  1196. t.mOriginalUrl = b.getString(ORIGINALURL);
  1197. final WebView w = t.mMainView;
  1198. final WebBackForwardList list = w.restoreState(b);
  1199. if (list == null) {
  1200. return false;
  1201. }
  1202. if (b.containsKey(CURRPICTURE)) {
  1203. final File f = new File(b.getString(CURRPICTURE));
  1204. w.restorePicture(b, f);
  1205. f.delete();
  1206. }
  1207. return true;
  1208. }
  1209. }