/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.linkbubble.ui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v7.graphics.Palette;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.FrameLayout;
import com.linkbubble.Constant;
import com.linkbubble.MainApplication;
import com.linkbubble.R;
import com.linkbubble.Settings;
import com.linkbubble.util.Util;
import com.squareup.picasso.Transformation;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.LoadFaviconTask;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.widget.FaviconView;
import java.net.MalformedURLException;
import java.net.URL;
public class BubbleView extends FrameLayout {
private static final String TAG = "BubbleView";
private static final boolean DEBUG = false;
public interface OnApplyFaviconListener {
boolean applyFavicon(String faviconURL);
}
protected FaviconView mFavicon;
protected int mFaviconLoadId;
private ProgressIndicatorView mProgressIndicator;
protected URL mUrl;
private BubbleView mImitator; //
private OnApplyFaviconListener mOnApplyFaviconListener;
public BubbleView(Context context) {
this(context, null);
}
public BubbleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
void configure() {
mFavicon = (FaviconView) findViewById(R.id.favicon);
mFavicon.setOnPaletteChangeListener(mOnPaletteChangeListener);
mFavicon.mFavicons = MainApplication.sFavicons;
mProgressIndicator = (ProgressIndicatorView) findViewById(R.id.progressIndicator);
showProgressBar(0);
}
void configure(String url) throws MalformedURLException {
mUrl = new URL(url);
configure();
}
void setOnApplyFaviconListener(OnApplyFaviconListener onApplyFaviconListener) {
mOnApplyFaviconListener = onApplyFaviconListener;
}
public URL getUrl() {
return mUrl;
}
public Drawable getFavicon() {
return mFavicon.getDrawable();
}
private void setFavicon(Bitmap bitmap, String faviconUrl) {
mFavicon.updateImage(bitmap, faviconUrl, mThemeColor == null);
mFavicon.setTag(faviconUrl);
}
private void setFallbackFavicon() {
mFavicon.showDefaultFavicon();
mFavicon.setTag(null);
}
public void setImitator(BubbleView bubbleView) {
mImitator = bubbleView;
if (mImitator != null) {
String tag = (String)mFavicon.getTag();
Drawable drawable = mFavicon.getDrawable();
if (tag != null && drawable != null) {
mImitator.setFavicon(((BitmapDrawable)drawable).getBitmap(), tag);
} else {
mImitator.setFallbackFavicon();
}
mImitator.mProgressIndicator.setProgress(mProgressIndicator.getProgress(), mUrl);
}
}
public void setFaviconLoadId(int faviconLoadId) {
mFaviconLoadId = faviconLoadId;
}
public int getFaviconLoadId() {
return mFaviconLoadId;
}
protected void loadFavicon() {
mThemeColor = null;
maybeCancelFaviconLoad();
final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
//int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
int flags = Settings.get().isIncognitoMode() ? 0 : LoadFaviconTask.FLAG_PERSIST;
flags |= Favicons.FLAG_OFFLINE_NO_CACHE;
String faviconUrl = Util.getDefaultFaviconUrl(mUrl);
int faviconLoadIdBefore = mFaviconLoadId;
int id = MainApplication.sFavicons.getFaviconForSize(mUrl.toString(), faviconUrl, Integer.MAX_VALUE, flags, mOnFaviconLoadedListener);
// If the favicon is cached, mOnFaviconLoadedListener.onFaviconLoaded() will be called before this check is reached,
// and this call will have already set mFaviconLoadId. Thus only act on the id return value if the value was not already changed
if (faviconLoadIdBefore == mFaviconLoadId) {
setFaviconLoadId(id);
if (id != Favicons.LOADED) {
setFallbackFavicon();
if (mImitator != null) {
mImitator.setFallbackFavicon();
}
if (DEBUG) {
Log.d(TAG, "[favicon] loadFavicon: setImageBitmap() FALLBACK for " + faviconUrl);
}
}
}
}
OnFaviconLoadedListener mOnFaviconLoadedListener = new OnFaviconLoadedListener() {
@Override
public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
if (favicon != null) {
if (mOnApplyFaviconListener != null && mOnApplyFaviconListener.applyFavicon(faviconURL) == false) {
return;
}
// Note: don't upsize favicon because Favicons.getFaviconForSize() already does this
setFavicon(favicon, faviconURL);
setFaviconLoadId(Favicons.LOADED);
if (mImitator != null) {
mImitator.setFavicon(favicon, faviconURL);
mImitator.setFaviconLoadId(Favicons.LOADED);
}
if (DEBUG) {
Log.d(TAG, "[favicon] mOnFaviconLoadedListener: setImageBitmap() size:" + favicon.getWidth() + " for " + faviconURL);
}
}
}
};
private void maybeCancelFaviconLoad() {
int faviconLoadId = getFaviconLoadId();
if (Favicons.NOT_LOADING == faviconLoadId) {
return;
}
// Cancel load task and reset favicon load state if it wasn't already
// in NOT_LOADING state.
MainApplication.sFavicons.cancelFaviconLoad(faviconLoadId);
setFaviconLoadId(Favicons.NOT_LOADING);
}
private static class FaviconTransformation implements Transformation {
@Override
public Bitmap transform(Bitmap source) {
int w = source.getWidth();
int h = source.getHeight();
int reqW = Math.min(Constant.DESIRED_FAVICON_SIZE, w*2);
int reqH = Math.min(Constant.DESIRED_FAVICON_SIZE, h*2);
if (w != reqW || h != reqH) {
w = reqW;
h = reqH;
Bitmap result = source;
try {
result = Bitmap.createScaledBitmap(source, w, h, true);
} catch (OutOfMemoryError e) {
}
return result;
}
return source;
}
@Override
public String key() { return "faviconTransformation()"; }
}
protected FaviconTransformation mFaviconTransformation = new FaviconTransformation();
protected void onPageLoaded(boolean withError) {
showProgressBar(100);
}
protected boolean onReceivedIcon(Bitmap favicon, boolean forceSet) {
boolean appliedFavicon = false;
if (favicon == null) {
// Don't update if an image already exists. Optimization as the fallback favicon is already set via loadFavicon()
if (mFavicon.getDrawable() == null || forceSet) {
mFaviconLoadId = Favicons.NOT_LOADING;
setFallbackFavicon();
if (mImitator != null) {
mImitator.mFaviconLoadId = Favicons.NOT_LOADING;
mImitator.setFallbackFavicon();
}
if (DEBUG) {
Log.d(TAG, "[favicon] onReceivedIcon: setImageBitmap() FALLBACK on host " + mUrl.getHost());
}
}
} else {
MainApplication mainApplication = (MainApplication) getContext().getApplicationContext();
String faviconUrl = Util.getDefaultFaviconUrl(mUrl);
String faviconTag = mFavicon.getTag() instanceof String ? (String)mFavicon.getTag() : null;
// We will ignore this if we are already using a larger icon which was retrieved as a TouchIcon specified
// in the page URL. Technically this is probably incorrect behaviour for a browser,
// but the larger icons look better, so I'm going with it.
boolean applyFavicon = true;
if (faviconTag != null && faviconTag.equals(faviconUrl)) {
Drawable currentFavicon = mFavicon.getDrawable();
if (currentFavicon != null && currentFavicon instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable)currentFavicon;
if (bitmapDrawable.getBitmap().getWidth() > favicon.getWidth()) {
applyFavicon = false;
}
}
}
if (applyFavicon || forceSet) {
if(MainApplication.sDatabaseHelper.faviconExists(faviconUrl, favicon) == false) {
MainApplication.sDatabaseHelper.addFaviconForUrl(faviconUrl, favicon, mUrl.toString());
}
favicon = mFaviconTransformation.transform(favicon);
mFaviconLoadId = Favicons.LOADED;
setFavicon(favicon, faviconUrl);
if (mImitator != null) {
mImitator.mFaviconLoadId = Favicons.LOADED;
mImitator.setFavicon(favicon, faviconUrl);
}
if (DEBUG) {
Log.d(TAG, "[favicon] onReceivedIcon: setImageBitmap() size:" + favicon.getWidth() + " on host " + mUrl.getHost());
}
appliedFavicon = true;
}
}
mFavicon.setVisibility(VISIBLE);
return appliedFavicon;
}
Integer mThemeColor;
public void onThemeColor(Integer color) {
mThemeColor = color;
mProgressIndicator.setColor(color);
if (mImitator != null) {
mImitator.onThemeColor(color);
}
}
public void onProgressChanged(int progress) {
showProgressBar(progress);
}
/*
Handler mHandler = new Handler();
float mTempProgress = 0.f;
Runnable mProgressRunnable = new Runnable() {
@Override
public void run() {
mProgressIndicator.setProgress((int) mTempProgress);
mTempProgress += .3f;
if (mTempProgress >= 100) {
mTempProgress -= 100;
}
mHandler.postDelayed(mProgressRunnable, 33);
}
};*/
void showProgressBar(int progress) {
mProgressIndicator.setProgress(progress, mUrl);
if (mImitator != null) {
mImitator.mProgressIndicator.setProgress(progress, mUrl);
}
}
FaviconView.OnPaletteChangeListener mOnPaletteChangeListener = new FaviconView.OnPaletteChangeListener() {
@Override
public void onPaletteChange(Palette palette) {
if (mThemeColor != null) {
return;
}
if (palette == null) {
setProgressColor(Settings.get().getThemedDefaultProgressColor());
return;
}
if (palette.getDarkVibrantSwatch() != null) {
setProgressColor(palette.getDarkVibrantSwatch().getRgb());
} else {
if (palette.getDarkMutedSwatch() != null) {
setProgressColor(palette.getDarkMutedSwatch().getRgb());
} else {
if (palette.getMutedSwatch() != null) {
setProgressColor(palette.getMutedSwatch().getRgb());
} else {
if (palette.getVibrantSwatch() != null) {
setProgressColor(palette.getVibrantSwatch().getRgb());
} else {
setProgressColor(Settings.get().getThemedDefaultProgressColor());
}
}
}
}
}
};
void setProgressColor(int color) {
mProgressIndicator.setColor(color);
}
}