Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko; |
michael@0 | 7 | |
michael@0 | 8 | import java.util.ArrayList; |
michael@0 | 9 | import java.util.Collection; |
michael@0 | 10 | import java.util.HashMap; |
michael@0 | 11 | import java.util.regex.Matcher; |
michael@0 | 12 | import java.util.regex.Pattern; |
michael@0 | 13 | |
michael@0 | 14 | import org.json.JSONException; |
michael@0 | 15 | import org.json.JSONObject; |
michael@0 | 16 | import org.mozilla.gecko.db.BrowserContract.Bookmarks; |
michael@0 | 17 | import org.mozilla.gecko.db.BrowserDB; |
michael@0 | 18 | import org.mozilla.gecko.gfx.Layer; |
michael@0 | 19 | import org.mozilla.gecko.util.ThreadUtils; |
michael@0 | 20 | |
michael@0 | 21 | import android.content.ContentResolver; |
michael@0 | 22 | import android.content.Context; |
michael@0 | 23 | import android.graphics.Bitmap; |
michael@0 | 24 | import android.graphics.Color; |
michael@0 | 25 | import android.graphics.drawable.BitmapDrawable; |
michael@0 | 26 | import android.os.Build; |
michael@0 | 27 | import android.text.TextUtils; |
michael@0 | 28 | import android.util.Log; |
michael@0 | 29 | import android.view.View; |
michael@0 | 30 | |
michael@0 | 31 | public class Tab { |
michael@0 | 32 | private static final String LOGTAG = "GeckoTab"; |
michael@0 | 33 | |
michael@0 | 34 | private static Pattern sColorPattern; |
michael@0 | 35 | private final int mId; |
michael@0 | 36 | private long mLastUsed; |
michael@0 | 37 | private String mUrl; |
michael@0 | 38 | private String mBaseDomain; |
michael@0 | 39 | private String mUserSearch; |
michael@0 | 40 | private String mTitle; |
michael@0 | 41 | private Bitmap mFavicon; |
michael@0 | 42 | private String mFaviconUrl; |
michael@0 | 43 | private int mFaviconSize; |
michael@0 | 44 | private boolean mHasFeeds; |
michael@0 | 45 | private boolean mHasOpenSearch; |
michael@0 | 46 | private SiteIdentity mSiteIdentity; |
michael@0 | 47 | private boolean mReaderEnabled; |
michael@0 | 48 | private BitmapDrawable mThumbnail; |
michael@0 | 49 | private int mHistoryIndex; |
michael@0 | 50 | private int mHistorySize; |
michael@0 | 51 | private int mParentId; |
michael@0 | 52 | private boolean mExternal; |
michael@0 | 53 | private boolean mBookmark; |
michael@0 | 54 | private boolean mReadingListItem; |
michael@0 | 55 | private int mFaviconLoadId; |
michael@0 | 56 | private String mContentType; |
michael@0 | 57 | private boolean mHasTouchListeners; |
michael@0 | 58 | private ZoomConstraints mZoomConstraints; |
michael@0 | 59 | private boolean mIsRTL; |
michael@0 | 60 | private ArrayList<View> mPluginViews; |
michael@0 | 61 | private HashMap<Object, Layer> mPluginLayers; |
michael@0 | 62 | private int mBackgroundColor; |
michael@0 | 63 | private int mState; |
michael@0 | 64 | private Bitmap mThumbnailBitmap; |
michael@0 | 65 | private boolean mDesktopMode; |
michael@0 | 66 | private boolean mEnteringReaderMode; |
michael@0 | 67 | private Context mAppContext; |
michael@0 | 68 | private ErrorType mErrorType = ErrorType.NONE; |
michael@0 | 69 | private static final int MAX_HISTORY_LIST_SIZE = 50; |
michael@0 | 70 | private volatile int mLoadProgress; |
michael@0 | 71 | private volatile int mRecordingCount = 0; |
michael@0 | 72 | private String mMostRecentHomePanel; |
michael@0 | 73 | |
michael@0 | 74 | public static final int STATE_DELAYED = 0; |
michael@0 | 75 | public static final int STATE_LOADING = 1; |
michael@0 | 76 | public static final int STATE_SUCCESS = 2; |
michael@0 | 77 | public static final int STATE_ERROR = 3; |
michael@0 | 78 | |
michael@0 | 79 | public static final int LOAD_PROGRESS_INIT = 10; |
michael@0 | 80 | public static final int LOAD_PROGRESS_START = 20; |
michael@0 | 81 | public static final int LOAD_PROGRESS_LOCATION_CHANGE = 60; |
michael@0 | 82 | public static final int LOAD_PROGRESS_LOADED = 80; |
michael@0 | 83 | public static final int LOAD_PROGRESS_STOP = 100; |
michael@0 | 84 | |
michael@0 | 85 | private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE; |
michael@0 | 86 | |
michael@0 | 87 | public enum ErrorType { |
michael@0 | 88 | CERT_ERROR, // Pages with certificate problems |
michael@0 | 89 | BLOCKED, // Pages blocked for phishing or malware warnings |
michael@0 | 90 | NET_ERROR, // All other types of error |
michael@0 | 91 | NONE // Non error pages |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | public Tab(Context context, int id, String url, boolean external, int parentId, String title) { |
michael@0 | 95 | mAppContext = context.getApplicationContext(); |
michael@0 | 96 | mId = id; |
michael@0 | 97 | mLastUsed = 0; |
michael@0 | 98 | mUrl = url; |
michael@0 | 99 | mBaseDomain = ""; |
michael@0 | 100 | mUserSearch = ""; |
michael@0 | 101 | mExternal = external; |
michael@0 | 102 | mParentId = parentId; |
michael@0 | 103 | mTitle = title == null ? "" : title; |
michael@0 | 104 | mFavicon = null; |
michael@0 | 105 | mFaviconUrl = null; |
michael@0 | 106 | mFaviconSize = 0; |
michael@0 | 107 | mHasFeeds = false; |
michael@0 | 108 | mHasOpenSearch = false; |
michael@0 | 109 | mSiteIdentity = new SiteIdentity(); |
michael@0 | 110 | mReaderEnabled = false; |
michael@0 | 111 | mEnteringReaderMode = false; |
michael@0 | 112 | mThumbnail = null; |
michael@0 | 113 | mHistoryIndex = -1; |
michael@0 | 114 | mHistorySize = 0; |
michael@0 | 115 | mBookmark = false; |
michael@0 | 116 | mReadingListItem = false; |
michael@0 | 117 | mFaviconLoadId = 0; |
michael@0 | 118 | mContentType = ""; |
michael@0 | 119 | mZoomConstraints = new ZoomConstraints(false); |
michael@0 | 120 | mPluginViews = new ArrayList<View>(); |
michael@0 | 121 | mPluginLayers = new HashMap<Object, Layer>(); |
michael@0 | 122 | mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS; |
michael@0 | 123 | mLoadProgress = LOAD_PROGRESS_INIT; |
michael@0 | 124 | |
michael@0 | 125 | // At startup, the background is set to a color specified by LayerView |
michael@0 | 126 | // when the LayerView is created. Shortly after, this background color |
michael@0 | 127 | // will be used before the tab's content is shown. |
michael@0 | 128 | mBackgroundColor = DEFAULT_BACKGROUND_COLOR; |
michael@0 | 129 | |
michael@0 | 130 | updateBookmark(); |
michael@0 | 131 | } |
michael@0 | 132 | |
michael@0 | 133 | private ContentResolver getContentResolver() { |
michael@0 | 134 | return mAppContext.getContentResolver(); |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | public void onDestroy() { |
michael@0 | 138 | Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | public int getId() { |
michael@0 | 142 | return mId; |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | public synchronized void onChange() { |
michael@0 | 146 | mLastUsed = System.currentTimeMillis(); |
michael@0 | 147 | } |
michael@0 | 148 | |
michael@0 | 149 | public synchronized long getLastUsed() { |
michael@0 | 150 | return mLastUsed; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | public int getParentId() { |
michael@0 | 154 | return mParentId; |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | // may be null if user-entered query hasn't yet been resolved to a URI |
michael@0 | 158 | public synchronized String getURL() { |
michael@0 | 159 | return mUrl; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | // mUserSearch should never be null, but it may be an empty string |
michael@0 | 163 | public synchronized String getUserSearch() { |
michael@0 | 164 | return mUserSearch; |
michael@0 | 165 | } |
michael@0 | 166 | |
michael@0 | 167 | // mTitle should never be null, but it may be an empty string |
michael@0 | 168 | public synchronized String getTitle() { |
michael@0 | 169 | return mTitle; |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | public String getDisplayTitle() { |
michael@0 | 173 | if (mTitle != null && mTitle.length() > 0) { |
michael@0 | 174 | return mTitle; |
michael@0 | 175 | } |
michael@0 | 176 | |
michael@0 | 177 | return mUrl; |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | public String getBaseDomain() { |
michael@0 | 181 | return mBaseDomain; |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | public Bitmap getFavicon() { |
michael@0 | 185 | return mFavicon; |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | public BitmapDrawable getThumbnail() { |
michael@0 | 189 | return mThumbnail; |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | public String getMostRecentHomePanel() { |
michael@0 | 193 | return mMostRecentHomePanel; |
michael@0 | 194 | } |
michael@0 | 195 | |
michael@0 | 196 | public void setMostRecentHomePanel(String panelId) { |
michael@0 | 197 | mMostRecentHomePanel = panelId; |
michael@0 | 198 | } |
michael@0 | 199 | |
michael@0 | 200 | public Bitmap getThumbnailBitmap(int width, int height) { |
michael@0 | 201 | if (mThumbnailBitmap != null) { |
michael@0 | 202 | // Bug 787318 - Honeycomb has a bug with bitmap caching, we can't |
michael@0 | 203 | // reuse the bitmap there. |
michael@0 | 204 | boolean honeycomb = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB |
michael@0 | 205 | && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2); |
michael@0 | 206 | boolean sizeChange = mThumbnailBitmap.getWidth() != width |
michael@0 | 207 | || mThumbnailBitmap.getHeight() != height; |
michael@0 | 208 | if (honeycomb || sizeChange) { |
michael@0 | 209 | mThumbnailBitmap = null; |
michael@0 | 210 | } |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | if (mThumbnailBitmap == null) { |
michael@0 | 214 | Bitmap.Config config = (GeckoAppShell.getScreenDepth() == 24) ? |
michael@0 | 215 | Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; |
michael@0 | 216 | mThumbnailBitmap = Bitmap.createBitmap(width, height, config); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | return mThumbnailBitmap; |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | public void updateThumbnail(final Bitmap b) { |
michael@0 | 223 | ThreadUtils.postToBackgroundThread(new Runnable() { |
michael@0 | 224 | @Override |
michael@0 | 225 | public void run() { |
michael@0 | 226 | if (b != null) { |
michael@0 | 227 | try { |
michael@0 | 228 | mThumbnail = new BitmapDrawable(mAppContext.getResources(), b); |
michael@0 | 229 | if (mState == Tab.STATE_SUCCESS) |
michael@0 | 230 | saveThumbnailToDB(); |
michael@0 | 231 | } catch (OutOfMemoryError oom) { |
michael@0 | 232 | Log.w(LOGTAG, "Unable to create/scale bitmap.", oom); |
michael@0 | 233 | mThumbnail = null; |
michael@0 | 234 | } |
michael@0 | 235 | } else { |
michael@0 | 236 | mThumbnail = null; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.THUMBNAIL); |
michael@0 | 240 | } |
michael@0 | 241 | }); |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | public synchronized String getFaviconURL() { |
michael@0 | 245 | return mFaviconUrl; |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | public boolean hasFeeds() { |
michael@0 | 249 | return mHasFeeds; |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | public boolean hasOpenSearch() { |
michael@0 | 253 | return mHasOpenSearch; |
michael@0 | 254 | } |
michael@0 | 255 | |
michael@0 | 256 | public SiteIdentity getSiteIdentity() { |
michael@0 | 257 | return mSiteIdentity; |
michael@0 | 258 | } |
michael@0 | 259 | |
michael@0 | 260 | public boolean getReaderEnabled() { |
michael@0 | 261 | return mReaderEnabled; |
michael@0 | 262 | } |
michael@0 | 263 | |
michael@0 | 264 | public boolean isBookmark() { |
michael@0 | 265 | return mBookmark; |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | public boolean isReadingListItem() { |
michael@0 | 269 | return mReadingListItem; |
michael@0 | 270 | } |
michael@0 | 271 | |
michael@0 | 272 | public boolean isExternal() { |
michael@0 | 273 | return mExternal; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | public synchronized void updateURL(String url) { |
michael@0 | 277 | if (url != null && url.length() > 0) { |
michael@0 | 278 | mUrl = url; |
michael@0 | 279 | } |
michael@0 | 280 | } |
michael@0 | 281 | |
michael@0 | 282 | private synchronized void updateUserSearch(String userSearch) { |
michael@0 | 283 | mUserSearch = userSearch; |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | public void setErrorType(String type) { |
michael@0 | 287 | if ("blocked".equals(type)) |
michael@0 | 288 | setErrorType(ErrorType.BLOCKED); |
michael@0 | 289 | else if ("certerror".equals(type)) |
michael@0 | 290 | setErrorType(ErrorType.CERT_ERROR); |
michael@0 | 291 | else if ("neterror".equals(type)) |
michael@0 | 292 | setErrorType(ErrorType.NET_ERROR); |
michael@0 | 293 | else |
michael@0 | 294 | setErrorType(ErrorType.NONE); |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | public void setErrorType(ErrorType type) { |
michael@0 | 298 | mErrorType = type; |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | public ErrorType getErrorType() { |
michael@0 | 302 | return mErrorType; |
michael@0 | 303 | } |
michael@0 | 304 | |
michael@0 | 305 | public void setContentType(String contentType) { |
michael@0 | 306 | mContentType = (contentType == null) ? "" : contentType; |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | public String getContentType() { |
michael@0 | 310 | return mContentType; |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | public synchronized void updateTitle(String title) { |
michael@0 | 314 | // Keep the title unchanged while entering reader mode. |
michael@0 | 315 | if (mEnteringReaderMode) { |
michael@0 | 316 | return; |
michael@0 | 317 | } |
michael@0 | 318 | |
michael@0 | 319 | // If there was a title, but it hasn't changed, do nothing. |
michael@0 | 320 | if (mTitle != null && |
michael@0 | 321 | TextUtils.equals(mTitle, title)) { |
michael@0 | 322 | return; |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | mTitle = (title == null ? "" : title); |
michael@0 | 326 | Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.TITLE); |
michael@0 | 327 | } |
michael@0 | 328 | |
michael@0 | 329 | public void setState(int state) { |
michael@0 | 330 | mState = state; |
michael@0 | 331 | |
michael@0 | 332 | if (mState != Tab.STATE_LOADING) |
michael@0 | 333 | mEnteringReaderMode = false; |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | public int getState() { |
michael@0 | 337 | return mState; |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | public void setZoomConstraints(ZoomConstraints constraints) { |
michael@0 | 341 | mZoomConstraints = constraints; |
michael@0 | 342 | } |
michael@0 | 343 | |
michael@0 | 344 | public ZoomConstraints getZoomConstraints() { |
michael@0 | 345 | return mZoomConstraints; |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | public void setIsRTL(boolean aIsRTL) { |
michael@0 | 349 | mIsRTL = aIsRTL; |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | public boolean getIsRTL() { |
michael@0 | 353 | return mIsRTL; |
michael@0 | 354 | } |
michael@0 | 355 | |
michael@0 | 356 | public void setHasTouchListeners(boolean aValue) { |
michael@0 | 357 | mHasTouchListeners = aValue; |
michael@0 | 358 | } |
michael@0 | 359 | |
michael@0 | 360 | public boolean getHasTouchListeners() { |
michael@0 | 361 | return mHasTouchListeners; |
michael@0 | 362 | } |
michael@0 | 363 | |
michael@0 | 364 | public void setFaviconLoadId(int faviconLoadId) { |
michael@0 | 365 | mFaviconLoadId = faviconLoadId; |
michael@0 | 366 | } |
michael@0 | 367 | |
michael@0 | 368 | public int getFaviconLoadId() { |
michael@0 | 369 | return mFaviconLoadId; |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | /** |
michael@0 | 373 | * Returns true if the favicon changed. |
michael@0 | 374 | */ |
michael@0 | 375 | public boolean updateFavicon(Bitmap favicon) { |
michael@0 | 376 | if (mFavicon == favicon) { |
michael@0 | 377 | return false; |
michael@0 | 378 | } |
michael@0 | 379 | mFavicon = favicon; |
michael@0 | 380 | return true; |
michael@0 | 381 | } |
michael@0 | 382 | |
michael@0 | 383 | public synchronized void updateFaviconURL(String faviconUrl, int size) { |
michael@0 | 384 | // If we already have an "any" sized icon, don't update the icon. |
michael@0 | 385 | if (mFaviconSize == -1) |
michael@0 | 386 | return; |
michael@0 | 387 | |
michael@0 | 388 | // Only update the favicon if it's bigger than the current favicon. |
michael@0 | 389 | // We use -1 to represent icons with sizes="any". |
michael@0 | 390 | if (size == -1 || size >= mFaviconSize) { |
michael@0 | 391 | mFaviconUrl = faviconUrl; |
michael@0 | 392 | mFaviconSize = size; |
michael@0 | 393 | } |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | public synchronized void clearFavicon() { |
michael@0 | 397 | // Keep the favicon unchanged while entering reader mode |
michael@0 | 398 | if (mEnteringReaderMode) |
michael@0 | 399 | return; |
michael@0 | 400 | |
michael@0 | 401 | mFavicon = null; |
michael@0 | 402 | mFaviconUrl = null; |
michael@0 | 403 | mFaviconSize = 0; |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | public void setHasFeeds(boolean hasFeeds) { |
michael@0 | 407 | mHasFeeds = hasFeeds; |
michael@0 | 408 | } |
michael@0 | 409 | |
michael@0 | 410 | public void setHasOpenSearch(boolean hasOpenSearch) { |
michael@0 | 411 | mHasOpenSearch = hasOpenSearch; |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | public void updateIdentityData(JSONObject identityData) { |
michael@0 | 415 | mSiteIdentity.update(identityData); |
michael@0 | 416 | } |
michael@0 | 417 | |
michael@0 | 418 | public void setReaderEnabled(boolean readerEnabled) { |
michael@0 | 419 | mReaderEnabled = readerEnabled; |
michael@0 | 420 | Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.MENU_UPDATED); |
michael@0 | 421 | } |
michael@0 | 422 | |
michael@0 | 423 | void updateBookmark() { |
michael@0 | 424 | if (getURL() == null) { |
michael@0 | 425 | return; |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | ThreadUtils.postToBackgroundThread(new Runnable() { |
michael@0 | 429 | @Override |
michael@0 | 430 | public void run() { |
michael@0 | 431 | final String url = getURL(); |
michael@0 | 432 | if (url == null) { |
michael@0 | 433 | return; |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | final int flags = BrowserDB.getItemFlags(getContentResolver(), url); |
michael@0 | 437 | mBookmark = (flags & Bookmarks.FLAG_BOOKMARK) > 0; |
michael@0 | 438 | mReadingListItem = (flags & Bookmarks.FLAG_READING) > 0; |
michael@0 | 439 | Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED); |
michael@0 | 440 | } |
michael@0 | 441 | }); |
michael@0 | 442 | } |
michael@0 | 443 | |
michael@0 | 444 | public void addBookmark() { |
michael@0 | 445 | ThreadUtils.postToBackgroundThread(new Runnable() { |
michael@0 | 446 | @Override |
michael@0 | 447 | public void run() { |
michael@0 | 448 | String url = getURL(); |
michael@0 | 449 | if (url == null) |
michael@0 | 450 | return; |
michael@0 | 451 | |
michael@0 | 452 | BrowserDB.addBookmark(getContentResolver(), mTitle, url); |
michael@0 | 453 | } |
michael@0 | 454 | }); |
michael@0 | 455 | } |
michael@0 | 456 | |
michael@0 | 457 | public void removeBookmark() { |
michael@0 | 458 | ThreadUtils.postToBackgroundThread(new Runnable() { |
michael@0 | 459 | @Override |
michael@0 | 460 | public void run() { |
michael@0 | 461 | String url = getURL(); |
michael@0 | 462 | if (url == null) |
michael@0 | 463 | return; |
michael@0 | 464 | |
michael@0 | 465 | BrowserDB.removeBookmarksWithURL(getContentResolver(), url); |
michael@0 | 466 | } |
michael@0 | 467 | }); |
michael@0 | 468 | } |
michael@0 | 469 | |
michael@0 | 470 | public void addToReadingList() { |
michael@0 | 471 | if (!mReaderEnabled) |
michael@0 | 472 | return; |
michael@0 | 473 | |
michael@0 | 474 | JSONObject json = new JSONObject(); |
michael@0 | 475 | try { |
michael@0 | 476 | json.put("tabID", String.valueOf(getId())); |
michael@0 | 477 | } catch (JSONException e) { |
michael@0 | 478 | Log.e(LOGTAG, "JSON error - failing to add to reading list", e); |
michael@0 | 479 | return; |
michael@0 | 480 | } |
michael@0 | 481 | |
michael@0 | 482 | GeckoEvent e = GeckoEvent.createBroadcastEvent("Reader:Add", json.toString()); |
michael@0 | 483 | GeckoAppShell.sendEventToGecko(e); |
michael@0 | 484 | } |
michael@0 | 485 | |
michael@0 | 486 | public void toggleReaderMode() { |
michael@0 | 487 | if (AboutPages.isAboutReader(mUrl)) { |
michael@0 | 488 | Tabs.getInstance().loadUrl(ReaderModeUtils.getUrlFromAboutReader(mUrl)); |
michael@0 | 489 | } else if (mReaderEnabled) { |
michael@0 | 490 | mEnteringReaderMode = true; |
michael@0 | 491 | Tabs.getInstance().loadUrl(ReaderModeUtils.getAboutReaderForUrl(mUrl, mId)); |
michael@0 | 492 | } |
michael@0 | 493 | } |
michael@0 | 494 | |
michael@0 | 495 | public boolean isEnteringReaderMode() { |
michael@0 | 496 | return mEnteringReaderMode; |
michael@0 | 497 | } |
michael@0 | 498 | |
michael@0 | 499 | public void doReload() { |
michael@0 | 500 | GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", ""); |
michael@0 | 501 | GeckoAppShell.sendEventToGecko(e); |
michael@0 | 502 | } |
michael@0 | 503 | |
michael@0 | 504 | // Our version of nsSHistory::GetCanGoBack |
michael@0 | 505 | public boolean canDoBack() { |
michael@0 | 506 | return mHistoryIndex > 0; |
michael@0 | 507 | } |
michael@0 | 508 | |
michael@0 | 509 | public boolean doBack() { |
michael@0 | 510 | if (!canDoBack()) |
michael@0 | 511 | return false; |
michael@0 | 512 | |
michael@0 | 513 | GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Back", ""); |
michael@0 | 514 | GeckoAppShell.sendEventToGecko(e); |
michael@0 | 515 | return true; |
michael@0 | 516 | } |
michael@0 | 517 | |
michael@0 | 518 | public boolean showBackHistory() { |
michael@0 | 519 | if (!canDoBack()) |
michael@0 | 520 | return false; |
michael@0 | 521 | return this.showHistory(Math.max(mHistoryIndex - MAX_HISTORY_LIST_SIZE, 0), mHistoryIndex, mHistoryIndex); |
michael@0 | 522 | } |
michael@0 | 523 | |
michael@0 | 524 | public boolean showForwardHistory() { |
michael@0 | 525 | if (!canDoForward()) |
michael@0 | 526 | return false; |
michael@0 | 527 | return this.showHistory(mHistoryIndex, Math.min(mHistorySize - 1, mHistoryIndex + MAX_HISTORY_LIST_SIZE), mHistoryIndex); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | public boolean showAllHistory() { |
michael@0 | 531 | if (!canDoForward() && !canDoBack()) |
michael@0 | 532 | return false; |
michael@0 | 533 | |
michael@0 | 534 | int min = mHistoryIndex - MAX_HISTORY_LIST_SIZE / 2; |
michael@0 | 535 | int max = mHistoryIndex + MAX_HISTORY_LIST_SIZE / 2; |
michael@0 | 536 | if (min < 0) { |
michael@0 | 537 | max -= min; |
michael@0 | 538 | } |
michael@0 | 539 | if (max > mHistorySize - 1) { |
michael@0 | 540 | min -= max - (mHistorySize - 1); |
michael@0 | 541 | max = mHistorySize - 1; |
michael@0 | 542 | } |
michael@0 | 543 | min = Math.max(min, 0); |
michael@0 | 544 | |
michael@0 | 545 | return this.showHistory(min, max, mHistoryIndex); |
michael@0 | 546 | } |
michael@0 | 547 | |
michael@0 | 548 | /** |
michael@0 | 549 | * This method will show the history starting on fromIndex until toIndex of the history. |
michael@0 | 550 | */ |
michael@0 | 551 | public boolean showHistory(int fromIndex, int toIndex, int selIndex) { |
michael@0 | 552 | JSONObject json = new JSONObject(); |
michael@0 | 553 | try { |
michael@0 | 554 | json.put("fromIndex", fromIndex); |
michael@0 | 555 | json.put("toIndex", toIndex); |
michael@0 | 556 | json.put("selIndex", selIndex); |
michael@0 | 557 | } catch (JSONException e) { |
michael@0 | 558 | Log.e(LOGTAG, "JSON error", e); |
michael@0 | 559 | } |
michael@0 | 560 | GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:ShowHistory", json.toString()); |
michael@0 | 561 | GeckoAppShell.sendEventToGecko(e); |
michael@0 | 562 | return true; |
michael@0 | 563 | } |
michael@0 | 564 | |
michael@0 | 565 | public void doStop() { |
michael@0 | 566 | GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Stop", ""); |
michael@0 | 567 | GeckoAppShell.sendEventToGecko(e); |
michael@0 | 568 | } |
michael@0 | 569 | |
michael@0 | 570 | // Our version of nsSHistory::GetCanGoForward |
michael@0 | 571 | public boolean canDoForward() { |
michael@0 | 572 | return mHistoryIndex < mHistorySize - 1; |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | public boolean doForward() { |
michael@0 | 576 | if (!canDoForward()) |
michael@0 | 577 | return false; |
michael@0 | 578 | |
michael@0 | 579 | GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Forward", ""); |
michael@0 | 580 | GeckoAppShell.sendEventToGecko(e); |
michael@0 | 581 | return true; |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | void handleSessionHistoryMessage(String event, JSONObject message) throws JSONException { |
michael@0 | 585 | if (event.equals("New")) { |
michael@0 | 586 | final String url = message.getString("url"); |
michael@0 | 587 | mHistoryIndex++; |
michael@0 | 588 | mHistorySize = mHistoryIndex + 1; |
michael@0 | 589 | } else if (event.equals("Back")) { |
michael@0 | 590 | if (!canDoBack()) { |
michael@0 | 591 | Log.w(LOGTAG, "Received unexpected back notification"); |
michael@0 | 592 | return; |
michael@0 | 593 | } |
michael@0 | 594 | mHistoryIndex--; |
michael@0 | 595 | } else if (event.equals("Forward")) { |
michael@0 | 596 | if (!canDoForward()) { |
michael@0 | 597 | Log.w(LOGTAG, "Received unexpected forward notification"); |
michael@0 | 598 | return; |
michael@0 | 599 | } |
michael@0 | 600 | mHistoryIndex++; |
michael@0 | 601 | } else if (event.equals("Goto")) { |
michael@0 | 602 | int index = message.getInt("index"); |
michael@0 | 603 | if (index < 0 || index >= mHistorySize) { |
michael@0 | 604 | Log.w(LOGTAG, "Received unexpected history-goto notification"); |
michael@0 | 605 | return; |
michael@0 | 606 | } |
michael@0 | 607 | mHistoryIndex = index; |
michael@0 | 608 | } else if (event.equals("Purge")) { |
michael@0 | 609 | int numEntries = message.getInt("numEntries"); |
michael@0 | 610 | if (numEntries > mHistorySize) { |
michael@0 | 611 | Log.w(LOGTAG, "Received unexpectedly large number of history entries to purge"); |
michael@0 | 612 | mHistoryIndex = -1; |
michael@0 | 613 | mHistorySize = 0; |
michael@0 | 614 | return; |
michael@0 | 615 | } |
michael@0 | 616 | |
michael@0 | 617 | mHistorySize -= numEntries; |
michael@0 | 618 | mHistoryIndex -= numEntries; |
michael@0 | 619 | |
michael@0 | 620 | // If we weren't at the last history entry, mHistoryIndex may have become too small |
michael@0 | 621 | if (mHistoryIndex < -1) |
michael@0 | 622 | mHistoryIndex = -1; |
michael@0 | 623 | } |
michael@0 | 624 | } |
michael@0 | 625 | |
michael@0 | 626 | void handleLocationChange(JSONObject message) throws JSONException { |
michael@0 | 627 | final String uri = message.getString("uri"); |
michael@0 | 628 | final String oldUrl = getURL(); |
michael@0 | 629 | final boolean sameDocument = message.getBoolean("sameDocument"); |
michael@0 | 630 | mEnteringReaderMode = ReaderModeUtils.isEnteringReaderMode(oldUrl, uri); |
michael@0 | 631 | |
michael@0 | 632 | if (!TextUtils.equals(oldUrl, uri)) { |
michael@0 | 633 | updateURL(uri); |
michael@0 | 634 | updateBookmark(); |
michael@0 | 635 | if (!sameDocument) { |
michael@0 | 636 | // We can unconditionally clear the favicon and title here: we |
michael@0 | 637 | // already filtered both cases in which this was a (pseudo-) |
michael@0 | 638 | // spurious location change, so we're definitely loading a new |
michael@0 | 639 | // page. |
michael@0 | 640 | clearFavicon(); |
michael@0 | 641 | updateTitle(null); |
michael@0 | 642 | } |
michael@0 | 643 | } |
michael@0 | 644 | |
michael@0 | 645 | if (sameDocument) { |
michael@0 | 646 | // We can get a location change event for the same document with an anchor tag |
michael@0 | 647 | // Notify listeners so that buttons like back or forward will update themselves |
michael@0 | 648 | Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl); |
michael@0 | 649 | return; |
michael@0 | 650 | } |
michael@0 | 651 | |
michael@0 | 652 | setContentType(message.getString("contentType")); |
michael@0 | 653 | updateUserSearch(message.getString("userSearch")); |
michael@0 | 654 | mBaseDomain = message.optString("baseDomain"); |
michael@0 | 655 | |
michael@0 | 656 | setHasFeeds(false); |
michael@0 | 657 | setHasOpenSearch(false); |
michael@0 | 658 | updateIdentityData(null); |
michael@0 | 659 | setReaderEnabled(false); |
michael@0 | 660 | setZoomConstraints(new ZoomConstraints(true)); |
michael@0 | 661 | setHasTouchListeners(false); |
michael@0 | 662 | setBackgroundColor(DEFAULT_BACKGROUND_COLOR); |
michael@0 | 663 | setErrorType(ErrorType.NONE); |
michael@0 | 664 | setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE); |
michael@0 | 665 | |
michael@0 | 666 | Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl); |
michael@0 | 667 | } |
michael@0 | 668 | |
michael@0 | 669 | private static boolean shouldShowProgress(final String url) { |
michael@0 | 670 | return !AboutPages.isAboutPage(url); |
michael@0 | 671 | } |
michael@0 | 672 | |
michael@0 | 673 | void handleDocumentStart(boolean restoring, String url) { |
michael@0 | 674 | setLoadProgress(LOAD_PROGRESS_START); |
michael@0 | 675 | setState((!restoring && shouldShowProgress(url)) ? STATE_LOADING : STATE_SUCCESS); |
michael@0 | 676 | updateIdentityData(null); |
michael@0 | 677 | setReaderEnabled(false); |
michael@0 | 678 | } |
michael@0 | 679 | |
michael@0 | 680 | void handleDocumentStop(boolean success) { |
michael@0 | 681 | setState(success ? STATE_SUCCESS : STATE_ERROR); |
michael@0 | 682 | |
michael@0 | 683 | final String oldURL = getURL(); |
michael@0 | 684 | final Tab tab = this; |
michael@0 | 685 | tab.setLoadProgress(LOAD_PROGRESS_STOP); |
michael@0 | 686 | ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() { |
michael@0 | 687 | @Override |
michael@0 | 688 | public void run() { |
michael@0 | 689 | // tab.getURL() may return null |
michael@0 | 690 | if (!TextUtils.equals(oldURL, getURL())) |
michael@0 | 691 | return; |
michael@0 | 692 | |
michael@0 | 693 | ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab); |
michael@0 | 694 | } |
michael@0 | 695 | }, 500); |
michael@0 | 696 | } |
michael@0 | 697 | |
michael@0 | 698 | void handleContentLoaded() { |
michael@0 | 699 | setLoadProgressIfLoading(LOAD_PROGRESS_LOADED); |
michael@0 | 700 | } |
michael@0 | 701 | |
michael@0 | 702 | protected void saveThumbnailToDB() { |
michael@0 | 703 | final BitmapDrawable thumbnail = mThumbnail; |
michael@0 | 704 | if (thumbnail == null) { |
michael@0 | 705 | return; |
michael@0 | 706 | } |
michael@0 | 707 | |
michael@0 | 708 | try { |
michael@0 | 709 | String url = getURL(); |
michael@0 | 710 | if (url == null) { |
michael@0 | 711 | return; |
michael@0 | 712 | } |
michael@0 | 713 | |
michael@0 | 714 | BrowserDB.updateThumbnailForUrl(getContentResolver(), url, thumbnail); |
michael@0 | 715 | } catch (Exception e) { |
michael@0 | 716 | // ignore |
michael@0 | 717 | } |
michael@0 | 718 | } |
michael@0 | 719 | |
michael@0 | 720 | public void addPluginView(View view) { |
michael@0 | 721 | mPluginViews.add(view); |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | public void removePluginView(View view) { |
michael@0 | 725 | mPluginViews.remove(view); |
michael@0 | 726 | } |
michael@0 | 727 | |
michael@0 | 728 | public View[] getPluginViews() { |
michael@0 | 729 | return mPluginViews.toArray(new View[mPluginViews.size()]); |
michael@0 | 730 | } |
michael@0 | 731 | |
michael@0 | 732 | public void addPluginLayer(Object surfaceOrView, Layer layer) { |
michael@0 | 733 | synchronized(mPluginLayers) { |
michael@0 | 734 | mPluginLayers.put(surfaceOrView, layer); |
michael@0 | 735 | } |
michael@0 | 736 | } |
michael@0 | 737 | |
michael@0 | 738 | public Layer getPluginLayer(Object surfaceOrView) { |
michael@0 | 739 | synchronized(mPluginLayers) { |
michael@0 | 740 | return mPluginLayers.get(surfaceOrView); |
michael@0 | 741 | } |
michael@0 | 742 | } |
michael@0 | 743 | |
michael@0 | 744 | public Collection<Layer> getPluginLayers() { |
michael@0 | 745 | synchronized(mPluginLayers) { |
michael@0 | 746 | return new ArrayList<Layer>(mPluginLayers.values()); |
michael@0 | 747 | } |
michael@0 | 748 | } |
michael@0 | 749 | |
michael@0 | 750 | public Layer removePluginLayer(Object surfaceOrView) { |
michael@0 | 751 | synchronized(mPluginLayers) { |
michael@0 | 752 | return mPluginLayers.remove(surfaceOrView); |
michael@0 | 753 | } |
michael@0 | 754 | } |
michael@0 | 755 | |
michael@0 | 756 | public int getBackgroundColor() { |
michael@0 | 757 | return mBackgroundColor; |
michael@0 | 758 | } |
michael@0 | 759 | |
michael@0 | 760 | /** Sets a new color for the background. */ |
michael@0 | 761 | public void setBackgroundColor(int color) { |
michael@0 | 762 | mBackgroundColor = color; |
michael@0 | 763 | } |
michael@0 | 764 | |
michael@0 | 765 | /** Parses and sets a new color for the background. */ |
michael@0 | 766 | public void setBackgroundColor(String newColor) { |
michael@0 | 767 | setBackgroundColor(parseColorFromGecko(newColor)); |
michael@0 | 768 | } |
michael@0 | 769 | |
michael@0 | 770 | // Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color |
michael@0 | 771 | // cannot be parsed, returns white. |
michael@0 | 772 | private static int parseColorFromGecko(String string) { |
michael@0 | 773 | if (sColorPattern == null) { |
michael@0 | 774 | sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)"); |
michael@0 | 775 | } |
michael@0 | 776 | |
michael@0 | 777 | Matcher matcher = sColorPattern.matcher(string); |
michael@0 | 778 | if (!matcher.matches()) { |
michael@0 | 779 | return Color.WHITE; |
michael@0 | 780 | } |
michael@0 | 781 | |
michael@0 | 782 | int r = Integer.parseInt(matcher.group(1)); |
michael@0 | 783 | int g = Integer.parseInt(matcher.group(2)); |
michael@0 | 784 | int b = Integer.parseInt(matcher.group(3)); |
michael@0 | 785 | return Color.rgb(r, g, b); |
michael@0 | 786 | } |
michael@0 | 787 | |
michael@0 | 788 | public void setDesktopMode(boolean enabled) { |
michael@0 | 789 | mDesktopMode = enabled; |
michael@0 | 790 | } |
michael@0 | 791 | |
michael@0 | 792 | public boolean getDesktopMode() { |
michael@0 | 793 | return mDesktopMode; |
michael@0 | 794 | } |
michael@0 | 795 | |
michael@0 | 796 | public boolean isPrivate() { |
michael@0 | 797 | return false; |
michael@0 | 798 | } |
michael@0 | 799 | |
michael@0 | 800 | /** |
michael@0 | 801 | * Sets the tab load progress to the given percentage. |
michael@0 | 802 | * |
michael@0 | 803 | * @param progressPercentage Percentage to set progress to (0-100) |
michael@0 | 804 | */ |
michael@0 | 805 | void setLoadProgress(int progressPercentage) { |
michael@0 | 806 | mLoadProgress = progressPercentage; |
michael@0 | 807 | } |
michael@0 | 808 | |
michael@0 | 809 | /** |
michael@0 | 810 | * Sets the tab load progress to the given percentage only if the tab is |
michael@0 | 811 | * currently loading. |
michael@0 | 812 | * |
michael@0 | 813 | * about:neterror can trigger a STOP before other page load events (bug |
michael@0 | 814 | * 976426), so any post-START events should make sure the page is loading |
michael@0 | 815 | * before updating progress. |
michael@0 | 816 | * |
michael@0 | 817 | * @param progressPercentage Percentage to set progress to (0-100) |
michael@0 | 818 | */ |
michael@0 | 819 | void setLoadProgressIfLoading(int progressPercentage) { |
michael@0 | 820 | if (getState() == STATE_LOADING) { |
michael@0 | 821 | setLoadProgress(progressPercentage); |
michael@0 | 822 | } |
michael@0 | 823 | } |
michael@0 | 824 | |
michael@0 | 825 | /** |
michael@0 | 826 | * Gets the tab load progress percentage. |
michael@0 | 827 | * |
michael@0 | 828 | * @return Current progress percentage |
michael@0 | 829 | */ |
michael@0 | 830 | public int getLoadProgress() { |
michael@0 | 831 | return mLoadProgress; |
michael@0 | 832 | } |
michael@0 | 833 | |
michael@0 | 834 | public void setRecording(boolean isRecording) { |
michael@0 | 835 | if (isRecording) { |
michael@0 | 836 | mRecordingCount++; |
michael@0 | 837 | } else { |
michael@0 | 838 | mRecordingCount--; |
michael@0 | 839 | } |
michael@0 | 840 | } |
michael@0 | 841 | |
michael@0 | 842 | public boolean isRecording() { |
michael@0 | 843 | return mRecordingCount > 0; |
michael@0 | 844 | } |
michael@0 | 845 | } |