Java tutorial
/* 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.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.annotation.DrawableRes; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.graphics.drawable.DrawableCompat; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.Patterns; import android.view.ContextMenu; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.webkit.CookieManager; import android.webkit.WebView; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.TextView; import com.linkbubble.BuildConfig; import com.linkbubble.Constant; import com.linkbubble.Constant.BubbleAction; import com.linkbubble.MainApplication; import com.linkbubble.MainController; import com.linkbubble.R; import com.linkbubble.Settings; import com.linkbubble.adblock.TPFilterParser; import com.linkbubble.adblock.WhiteListCollector; import com.linkbubble.adinsert.AdInserter; import com.linkbubble.articlerender.ArticleContent; import com.linkbubble.articlerender.ArticleRenderer; import com.linkbubble.httpseverywhere.HttpsEverywhere; import com.linkbubble.util.ActionItem; import com.linkbubble.util.Analytics; import com.linkbubble.util.CrashTracking; import com.linkbubble.util.DownloadImage; import com.linkbubble.util.Util; import com.linkbubble.webrender.CustomWebView; import com.linkbubble.webrender.WebRenderer; import com.linkbubble.adblock.ABPFilterParser; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; public class ContentView extends FrameLayout { private static final String TAG = "UrlLoad"; private static final Integer BLACK_LIST_MAX_REDIRECT_COUNT = 5; private static final int DEFAULT_TOOLBAR_SIZE = 112; private static int sNextArticleNotificationId = 1111; private enum LifeState { Init, Alive, Removed, Destroyed } private WebRenderer mWebRenderer; private ArticleRenderer mArticleRenderer; private int mArticleNotificationId = -1; private TabView mOwnerTabView; private View mCaretView; private CondensedTextView mTitleTextView; private CondensedTextView mUrlTextView; private ContentViewButton mShareButton; private ContentViewButton mReloadButton; private ArticleModeButton mArticleModeButton; private OpenInAppButton mOpenInAppButton; private OpenEmbedButton mOpenEmbedButton; private ContentViewButton mOverflowButton; private View mRequestLocationShadow; private View mRequestLocationContainer; private CondensedTextView mRequestLocationTextView; private Button mRequestLocationYesButton; private LinearLayout mToolbarLayout; private EventHandler mEventHandler; private int mCurrentProgress = 0; // Search URL functionality private CustomAutoCompleteTextView metUrl; private ImageButton mbtUrlClear; private FrameLayout mContentEditUrl; private boolean mPageFinishedLoading; private LifeState mLifeState = LifeState.Init; private Set<String> mAppPickersUrls = new HashSet<String>(); private List<AppForUrl> mAppsForUrl = new ArrayList<AppForUrl>(); private List<ResolveInfo> mTempAppsForUrl = new ArrayList<ResolveInfo>(); private PopupMenu mOverflowPopupMenu; private AlertDialog mLongPressAlertDialog; private long mInitialUrlLoadStartTime; private String mInitialUrlAsString; private String mLoadingString; private Context mContext; private MainController mController; private Stack<URL> mUrlStack = new Stack<URL>(); // We only want to handle this once per link. This prevents 3+ dialogs appearing for some links, which is a bad experience. #224 private boolean mHandledAppPickerForCurrentUrl = false; private boolean mUsingLinkBubbleAsDefaultForCurrentUrl = false; private SearchURLCustomAdapter mAdapter; private SearchURLSuggestions mFirstSuggestedItem; private float mOneRowAutoSuggestionsSize = 53f; private int mRowsToShowOnAutoSuggestions = 5; private boolean mApplyAutoSuggestionToUrlString = true; private boolean mSetTheRealUrlString = true; private boolean mFirstTimeUrlTyped = true; private boolean mHostInWhiteList = false; private int mToolBarHeight = DEFAULT_TOOLBAR_SIZE; private int[] mOriginalLocation = new int[2]; ConcurrentHashMap<String, Integer> mHostRedirectCounter; // Tracking protection third party hosts String[] mThirdPartyHosts = null; public ContentView(Context context) { this(context, null); } public ContentView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ContentView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; mLoadingString = getResources().getString(R.string.loading); } public long getTotalTrackedLoadTime() { if (mInitialUrlLoadStartTime > -1) { return System.currentTimeMillis() - mInitialUrlLoadStartTime; } return -1; } public WebRenderer getWebRenderer() { return mWebRenderer; } static class AppForUrl { ResolveInfo mResolveInfo; URL mUrl; Drawable mIcon; AppForUrl(ResolveInfo resolveInfo, URL url) { mResolveInfo = resolveInfo; mUrl = url; } Drawable getIcon(Context context) { if (mIcon == null) { // TODO: Handle OutOfMemory error if (mResolveInfo != null) { mIcon = mResolveInfo.loadIcon(context.getPackageManager()); } } return mIcon; } } public interface EventHandler { public void onPageLoading(URL url); public void onProgressChanged(int progress); public void onPageLoaded(boolean withError); public boolean onReceivedIcon(Bitmap bitmap); public void setDefaultFavicon(); public void onCanGoBackChanged(boolean canGoBack); public boolean hasHighQualityFavicon(); void onThemeColor(Integer color); } @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (isInEditMode()) { return; } /* float centerX = Config.mScreenCenterX; float indicatorEndY = 2.f; float indicatorStartX = centerX - mHeaderHeight + indicatorEndY; float indicatorEndX = centerX + mHeaderHeight - indicatorEndY; mTempPath.reset(); mTempPath.moveTo(indicatorStartX, mHeaderHeight); mTempPath.lineTo(centerX, indicatorEndY); mTempPath.lineTo(indicatorEndX, mHeaderHeight); canvas.drawPath(mTempPath, sIndicatorPaint); canvas.drawLine(indicatorEndY, mHeaderHeight, indicatorStartX, mHeaderHeight, sBorderPaint); canvas.drawLine(indicatorStartX, mHeaderHeight, centerX, 0, sBorderPaint); canvas.drawLine(centerX, indicatorEndY, indicatorEndX, mHeaderHeight, sBorderPaint); canvas.drawLine(indicatorEndX, mHeaderHeight, Config.mScreenWidth, mHeaderHeight, sBorderPaint); */ } public void destroy() { Log.d(TAG, "*** destroy() - url" + (mWebRenderer.getUrl() != null ? mWebRenderer.getUrl().toString() : "<null>")); mLifeState = LifeState.Destroyed; removeView(mWebRenderer.getView()); mWebRenderer.destroy(); if (mArticleRenderer != null) { removeView(mArticleRenderer.getView()); mArticleRenderer.destroy(); } //if (mDelayedAutoContentDisplayLinkLoadedScheduled) { // mDelayedAutoContentDisplayLinkLoadedScheduled = false; // Log.e(TAG, "*** set mDelayedAutoContentDisplayLinkLoadedScheduled=" + mDelayedAutoContentDisplayLinkLoadedScheduled); //} removeCallbacks(mDelayedAutoContentDisplayLinkLoadedRunnable); } public void onRemoved() { mLifeState = LifeState.Removed; cancelWearNotification(); } public void onRestored() { mLifeState = LifeState.Alive; // If we need to re-add the notification, do so here configureArticleModeButton(); } public void updateIncognitoMode(boolean incognito) { mWebRenderer.updateIncognitoMode(incognito); } // We need it to be a member, because we need to dismiss it on bubbles collapse private AlertDialog mShareAlertDialog = null; private void showSelectShareMethod(final String urlAsString, final boolean closeBubbleOnShare) { mShareAlertDialog = ActionItem.getShareAlert(getContext(), false, new ActionItem.OnActionItemSelectedListener() { @Override public void onSelected(ActionItem actionItem) { Intent intent = Util.getSendIntent(actionItem.mPackageName, actionItem.mActivityClassName, urlAsString); getContext().startActivity(intent); boolean isCopyToClipboardAction = actionItem.mPackageName .equals("com.google.android.apps.docs") && actionItem.mActivityClassName .equals("com.google.android.apps.docs.app.SendTextToClipboardActivity"); // L_WATCH: L currently lacks getRecentTasks(), so minimize here if (isCopyToClipboardAction == false && Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { MainController.get().switchToBubbleView(); } //if (closeBubbleOnShare && isCopyToClipboardAction == false && MainController.get() != null) { // MainController.get().closeTab(mOwnerTabView, true); //} } }); Util.showThemedDialog(mShareAlertDialog); } private void saveImage(final String urlAsString) { new DownloadImage(mContext, urlAsString).download(); } ArrayList<Drawable> mTintableDrawables = new ArrayList<>(); private Drawable getTintableDrawable(@DrawableRes int resId) { return getTintableDrawable(resId, true); } private Drawable getTintableDrawable(@DrawableRes int resId, boolean addToList) { Drawable d = Util.getTintableDrawable(this.getContext(), resId); if (addToList) { mTintableDrawables.add(d); } return d; } private void HostInWhiteListCheck(String url) { MainApplication app = (MainApplication) mContext.getApplicationContext(); WhiteListCollector whiteListCollector = app.getWhiteListCollector(); if (null == whiteListCollector) { mHostInWhiteList = false; return; } String host = ""; try { host = new URL(url).getHost(); } catch (MalformedURLException exc) { } mHostInWhiteList = whiteListCollector.isInWhiteList(host); } private void AddRemoveHostFromWhiteList(String url, boolean add) { MainApplication app = (MainApplication) mContext.getApplicationContext(); WhiteListCollector whiteListCollector = app.getWhiteListCollector(); if (null == whiteListCollector) { mHostInWhiteList = false; return; } String host = ""; try { host = new URL(url).getHost(); } catch (MalformedURLException exc) { } if (add) { whiteListCollector.addHostToWhiteList(host); } else { whiteListCollector.removeHostFromWhiteList(host); } } public int toolbarHeight() { return mToolBarHeight; } public void calculateToolbarHeight() { FrameLayout.LayoutParams currentUrlBarParams = (FrameLayout.LayoutParams) mContentEditUrl.getLayoutParams(); FrameLayout.LayoutParams currentShadowParams = (FrameLayout.LayoutParams) findViewById( R.id.actionbar_shadow).getLayoutParams(); if (null != currentUrlBarParams && null != currentShadowParams) { mToolBarHeight = currentUrlBarParams.height + currentShadowParams.height; return; } mToolBarHeight = DEFAULT_TOOLBAR_SIZE; } // The function configures the urlBar private void configureUrlBar(String urlAsString, final MainController controller) { // Set the current URL to the search URL mContentEditUrl = (FrameLayout) findViewById(R.id.content_edit_url); metUrl = (CustomAutoCompleteTextView) findViewById(R.id.autocomplete_top500websites); metUrl.setDropDownWidth(getResources().getDisplayMetrics().widthPixels); metUrl.setText(urlAsString); mFirstTimeUrlTyped = true; metUrl.addTextChangedListener(murlTextWatcher); metUrl.setOnFocusChangeListener(murlOnFocusChangeListener); metUrl.setOnItemClickListener(murlOnItemClickListener); metUrl.setOnEditorActionListener(murlActionListener); metUrl.setImeOptions(EditorInfo.IME_ACTION_GO | EditorInfo.IME_FLAG_NO_EXTRACT_UI); metUrl.setOnKeyListener(murlKeyListener); metUrl.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (CustomAutoCompleteTextView.MENU_APPEARS_TIME_ATER_DESTROY < System.currentTimeMillis() - metUrl.mCopyPasteDestroyedLastTime) { metUrl.onCopyPasteDestroyed(); } else { metUrl.onCopyPasteCreated(); } } }); metUrl.configure(controller); metUrl.setOnCreateContextMenuListener(new OnCreateContextMenuListener() { @Override public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { metUrl.onCopyPasteCreated(); } }); mAdapter = new SearchURLCustomAdapter(getContext(), android.R.layout.simple_list_item_1, getResources(), getResources().getDisplayMetrics().widthPixels); mAdapter.mRealUrlBarConstraint = urlAsString; metUrl.setAdapter(mAdapter); mAdapter.registerDataSetObserver(mDataSetObserver); mbtUrlClear = (ImageButton) findViewById(R.id.search_url_clear); mbtUrlClear.setOnClickListener(mbtClearUrlClicked); } void setTabAsActive() { try { if (mUrlTextView.getText().toString().equals(getContext().getString(R.string.empty_bubble_page))) { mTitleTextView.performClick(); } else { View view = mWebRenderer.getView(); if (null != view) { view.requestFocus(); } } } catch (NullPointerException exc) { // We have that exception sometimes inside Android SDK on requestFocus, // we would better to not get focus than crash } } @SuppressLint("SetJavaScriptEnabled") void configure(String urlAsString, TabView ownerTabView, long urlLoadStartTime, boolean hasShownAppPicker, MainController controller, EventHandler eventHandler) throws MalformedURLException { mController = controller; mHostRedirectCounter = new ConcurrentHashMap<String, Integer>(); mLifeState = LifeState.Alive; mTintableDrawables.clear(); HostInWhiteListCheck(urlAsString); View webRendererPlaceholder = findViewById(R.id.web_renderer_placeholder); mWebRenderer = WebRenderer.create(WebRenderer.Type.WebView, getContext(), mWebRendererController, webRendererPlaceholder, TAG); mWebRenderer.setUrl(mWebRendererController.getHTTPSUrl(urlAsString)); getLocationOnScreen(mOriginalLocation); getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @SuppressLint("NewApi") @Override public void onGlobalLayout() { // We do that to detect when copy/paste dialog is going to destroy on pre Android M if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1 || (null == mWebRenderer || !mWebRenderer.isCopyPasteShown()) && (null == mArticleRenderer || !mArticleRenderer.isCopyPasteShown())) { return; } int[] location = new int[2]; getLocationOnScreen(location); if (location[1] - mOriginalLocation[1] >= 0 || location[1] - mOriginalLocation[1] + (mController.getCurrentHeight() + toolbarHeight()) < -10) { return; } if (mWebRenderer.isCopyPasteShown()) { mWebRenderer.copyPasteDialogWasDestroyed(); } else { mArticleRenderer.copyPasteDialogWasDestroyed(); } } }); // Generates 1000 history links /*for (int i = 0; i < 1000; i++) { URL currentUrl = mWebRenderer.getUrl(); MainApplication.saveUrlInHistory(getContext(), null, currentUrl.toString() + String.valueOf(i), currentUrl.getHost(), "111_test"); }*/ // if (mArticleRenderer != null) { mArticleRenderer.destroy(); mArticleRenderer = null; } mOwnerTabView = ownerTabView; mHandledAppPickerForCurrentUrl = hasShownAppPicker; mUsingLinkBubbleAsDefaultForCurrentUrl = false; if (hasShownAppPicker) { mAppPickersUrls.add(urlAsString); } mToolbarLayout = (LinearLayout) findViewById(R.id.content_toolbar); mTitleTextView = (CondensedTextView) findViewById(R.id.title_text); mUrlTextView = (CondensedTextView) findViewById(R.id.url_text); // Set on click listeners to show the search URL control mTitleTextView.setOnClickListener(mOnURLEnterClicked); mUrlTextView.setOnClickListener(mOnURLEnterClicked); findViewById(R.id.content_text_container).setOnTouchListener(mOnTextContainerTouchListener); configureUrlBar(urlAsString, controller); mCaretView = findViewById(R.id.caret); mShareButton = (ContentViewButton) findViewById(R.id.share_button); mShareButton.setImageDrawable(getTintableDrawable(R.drawable.ic_share_white_24dp)); mShareButton.setOnClickListener(mOnShareButtonClickListener); mOpenInAppButton = (OpenInAppButton) findViewById(R.id.open_in_app_button); mOpenInAppButton.setOnOpenInAppClickListener(mOnOpenInAppButtonClickListener); mOpenEmbedButton = (OpenEmbedButton) findViewById(R.id.open_embed_button); mOpenEmbedButton.setOnOpenEmbedClickListener(mOnOpenEmbedButtonClickListener); mReloadButton = (ContentViewButton) findViewById(R.id.reload_button); mReloadButton.setImageDrawable(getTintableDrawable(R.drawable.ic_refresh_white_24dp)); mReloadButton.setOnClickListener(mOnReloadButtonClickListener); mArticleModeButton = (ArticleModeButton) findViewById(R.id.article_mode_button); mArticleModeButton.setState(ArticleModeButton.State.Article); mArticleModeButton.setOnClickListener(mOnArticleModeButtonClickListener); mOverflowButton = (ContentViewButton) mToolbarLayout.findViewById(R.id.overflow_button); mOverflowButton.setImageDrawable(getTintableDrawable(R.drawable.ic_more_vert_white_24dp)); mOverflowButton.setOnClickListener(mOnOverflowButtonClickListener); mRequestLocationShadow = findViewById(R.id.request_location_shadow); mRequestLocationContainer = findViewById(R.id.request_location_container); mRequestLocationTextView = (CondensedTextView) findViewById(R.id.requesting_location_text_view); mRequestLocationYesButton = (Button) findViewById(R.id.access_location_yes); findViewById(R.id.access_location_no).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { hideAllowLocationDialog(); } }); mEventHandler = eventHandler; mEventHandler.onCanGoBackChanged(false); mPageFinishedLoading = false; updateIncognitoMode(Settings.get().isIncognitoMode()); mInitialUrlLoadStartTime = urlLoadStartTime; mInitialUrlAsString = urlAsString; updateAndLoadUrl(urlAsString); updateAppsForUrl(mWebRenderer.getUrl()); Log.d(TAG, "load url: " + urlAsString); updateUrlTitleAndText(urlAsString); updateColors(null); calculateToolbarHeight(); } private void WorkWithURL(String strUrl, SearchURLSuggestions.SearchEngine selectedSearchEngine, boolean fromGoAction) { metUrl.dismissDropDown(); strUrl = strUrl.trim(); String strUrlWithPrefix = strUrl; if (!strUrl.startsWith(getContext().getString(R.string.http_prefix)) && !strUrl.startsWith(getContext().getString(R.string.https_prefix))) strUrlWithPrefix = getContext().getString(R.string.http_prefix) + strUrl; if (SearchURLSuggestions.SearchEngine.NONE == selectedSearchEngine && Patterns.WEB_URL.matcher(strUrlWithPrefix).matches()) { LoadWebPage(strUrlWithPrefix); } else if (SearchURLSuggestions.SearchEngine.NONE == selectedSearchEngine && fromGoAction) { if (null != mFirstSuggestedItem) { WorkWithURL(strUrl, mFirstSuggestedItem.EngineToUse, false); } } else if (SearchURLSuggestions.SearchEngine.DUCKDUCKGO == selectedSearchEngine) { // Make the search using duck duck go try { String strQuery = String.format(getContext().getString(R.string.duckduckgo_search_engine), URLEncoder.encode(strUrl, "UTF-8")); LoadWebPage(strQuery); } catch (IOException ioe) { Log.e(TAG, ioe.getMessage(), ioe); } } else if (SearchURLSuggestions.SearchEngine.GOOGLE == selectedSearchEngine) { // Make the search using google try { String strQuery = getContext().getString(R.string.google_search_engine) + URLEncoder.encode(strUrl, "UTF-8"); LoadWebPage(strQuery); } catch (IOException ioe) { Log.e(TAG, ioe.getMessage(), ioe); } } else if (SearchURLSuggestions.SearchEngine.YAHOO == selectedSearchEngine) { // Make the search using yahoo try { String strQuery = getContext().getString(R.string.yahoo_search_engine) + URLEncoder.encode(strUrl, "UTF-8"); LoadWebPage(strQuery); } catch (IOException ioe) { Log.e(TAG, ioe.getMessage(), ioe); } } else if (SearchURLSuggestions.SearchEngine.AMAZON == selectedSearchEngine) { // Make the search using amazon try { String strQuery = getContext().getString(R.string.amazon_search_engine) + URLEncoder.encode(strUrl, "UTF-8"); LoadWebPage(strQuery); } catch (IOException ioe) { Log.e(TAG, ioe.getMessage(), ioe); } } mToolbarLayout.bringToFront(); } private void LoadWebPage(String strUrl) { updateAndLoadUrl(strUrl); mWebRendererController.resetBubblePanelAdjustment(); } Integer themeColor; void updateColors(Integer color) { themeColor = color; int textColor; int bgColor; if (color == null || !Settings.get().getThemeToolbar()) { textColor = Settings.get().getThemedTextColor(); bgColor = Settings.get().getThemedContentViewColor(); mCaretView.setBackground(getResources() .getDrawable(Settings.get().getDarkThemeEnabled() ? R.drawable.content_view_caret_dark : R.drawable.content_view_caret_white)); } else { // Calculate text color based on contrast with background: // https://24ways.org/2010/calculating-color-contrast/ int yiq = (Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114) / 1000; textColor = yiq >= 128 ? Settings.COLOR_BLACK : Settings.COLOR_WHITE; bgColor = color; Drawable d = getTintableDrawable(R.drawable.content_view_caret_white, false); DrawableCompat.setTint(d, color); mCaretView.setBackground(d); } mToolbarLayout.setBackgroundColor(bgColor); mTitleTextView.setTextColor(textColor); mUrlTextView.setTextColor(textColor); metUrl.setBackgroundColor(bgColor); metUrl.setTextColor(textColor); mContentEditUrl.setBackgroundColor(bgColor); for (Drawable d : mTintableDrawables) { DrawableCompat.setTint(d, textColor); } mArticleModeButton.updateTheme(color); } public void collapse() { if (null != mShareAlertDialog) { mShareAlertDialog.dismiss(); mShareAlertDialog = null; } InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(metUrl.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); } void setFaviconColor(Integer color) { updateColors(color); } WebRenderer.Controller mWebRendererController = new WebRenderer.Controller() { @Override public void onWebViewContextMenuAppearedGone(boolean appeared) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1 || mUrlTextView.getText().toString() .equals(getContext().getString(R.string.empty_bubble_page))) { return; } MainController mainController = MainController.get(); if (null != mainController) { mainController.onWebViewContextMenuAppearedGone(appeared); } } @Override public void resetBubblePanelAdjustment() { if (!mWebRenderer.getView().hasFocus()) { mWebRenderer.getView().requestFocus(); } MainController mainController = MainController.get(); if (null != mainController) { mainController.adjustBubblesPanel(0, 0, false, true); } } @Override public void adjustBubblesPanel(int newY, int oldY, boolean afterTouchAdjust) { if (!mWebRenderer.getView().hasFocus()) { mWebRenderer.getView().requestFocus(); } MainController mainController = MainController.get(); if (null != mainController) { mainController.adjustBubblesPanel(newY, oldY, afterTouchAdjust, false); } } @Override public String getHTTPSUrl(String originalUrl) { if (mHostInWhiteList || !Settings.get().isHttpsEverywhereEnabled()) { return originalUrl; } MainApplication app = (MainApplication) mContext.getApplicationContext(); HttpsEverywhere httpsEverywhere = app.getHttpsEverywhere(); if (null == httpsEverywhere) { return originalUrl; } Integer redirectedCount = 0; String urlToBlackList = ""; try { urlToBlackList = new URL(originalUrl).getHost(); } catch (MalformedURLException exc) { urlToBlackList = originalUrl; } if (null != mHostRedirectCounter && null != originalUrl && !originalUrl.startsWith("https")) { if (urlToBlackList.startsWith("http://m.")) { urlToBlackList = "http://" + urlToBlackList.substring("http://m.".length()); } redirectedCount = mHostRedirectCounter.get(urlToBlackList); if (null == redirectedCount) { redirectedCount = 0; } if (redirectedCount >= BLACK_LIST_MAX_REDIRECT_COUNT) { return originalUrl; } } String realUrl = httpsEverywhere.getRealUrl(originalUrl); if (!realUrl.equals(originalUrl)) { redirectedCount++; if (null != mHostRedirectCounter) { mHostRedirectCounter.put(urlToBlackList, redirectedCount); } } return realUrl; } @Override public boolean shouldAdBlockUrl(String baseHost, String urlStr, String filterOption) { if (mHostInWhiteList) { return false; } if (null != mWebRenderer) { URL currentUrl = mWebRenderer.getUrl(); if (currentUrl.toString().equals(urlStr)) { return false; } } MainApplication app = (MainApplication) mContext.getApplicationContext(); ABPFilterParser parser = app.getABPParser(); if (null == parser) { return false; } return parser.shouldBlockJava(baseHost, urlStr, filterOption); } @Override public boolean shouldTrackingProtectionBlockUrl(String baseHost, String host) { if (mHostInWhiteList) { return false; } MainApplication app = (MainApplication) mContext.getApplicationContext(); TPFilterParser tpList = app.getTrackingProtectionList(); if (null == tpList) { return false; } if (tpList.matchesTrackerJava(baseHost, host)) { if (null == mThirdPartyHosts) { mThirdPartyHosts = tpList.findFirstPartyHostsJava(baseHost).split(","); } if (null != mThirdPartyHosts) { for (int i = 0; i < mThirdPartyHosts.length; i++) { if (host == mThirdPartyHosts[i] || host.endsWith("." + mThirdPartyHosts[i])) { return false; } } } // Temporary whitelist until we have an UI to unblock hosts List<String> whitelistHosts = Arrays.asList("connect.facebook.net"); if (whitelistHosts.contains(host)) { return false; } return true; } return false; } @Override public String adInsertionList(String baseHost) { if (mHostInWhiteList) { return ""; } MainApplication app = (MainApplication) mContext.getApplicationContext(); if (!app.mAdInserterEnabled) { return ""; } AdInserter adInsertionList = app.getAdInserter(); if (null == adInsertionList) { return ""; } return adInsertionList.getHostObjects(baseHost); } private int mConsecutiveRedirectCount = 0; @Override public void doUpdateVisitedHistory(String url, boolean isReload, boolean unknownClick) { String peekUrl = ""; if (mUrlStack.size() > 0) { peekUrl = mUrlStack.peek().toString(); } // We need isReload check when click on links from twitter. It usually has some internal links which are not the same // as real link and isReload is true in that case, so we need to skip them if it is a top website if ((isReload && 0 == mUrlStack.size()) || url.equals("file:///android_asset/blank.html") || mUrlStack.size() > 0 && peekUrl.equals(url)) { return; } try { URL historyUrl = new URL(url); if (unknownClick) { // Here we check on anchors change without clicking on them String ref = historyUrl.getRef(); if (null != ref && 0 != ref.length() && (ref.indexOf("/") == -1 || ref.equals("/") || ref.length() >= 2 && ref.indexOf("/", 1) == -1)) { String originalUrl = url.substring(0, url.length() - ref.length() - 1); if (peekUrl.equals(originalUrl)) { return; } else if (0 != peekUrl.length()) { URL peekURLForRef = new URL(peekUrl); String peekRef = peekURLForRef.getRef(); if (null != peekRef && 0 != peekRef.length() && (peekRef.indexOf("/") == -1 || peekRef.equals("/") || peekRef.length() >= 2 && peekRef.indexOf("/", 1) == -1)) { String originalPeekUrl = peekUrl.substring(0, peekUrl.length() - peekRef.length() - 1); if (originalPeekUrl.equals(originalUrl)) { return; } } } } } Log.d(TAG, "[urlstack] push:" + url + ", urlStack.size():" + mUrlStack.size()); mUrlStack.push(historyUrl); mEventHandler.onCanGoBackChanged(mUrlStack.size() > 1); } catch (MalformedURLException e) { } } @Override public boolean shouldOverrideUrlLoading(String urlAsString, boolean viaUserInput) { if (mLifeState != LifeState.Alive) { mConsecutiveRedirectCount = 0; return true; } if (urlAsString.startsWith("tel:")) { Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(urlAsString)); if (MainApplication.loadIntent(getContext(), intent, urlAsString, mInitialUrlLoadStartTime)) { MainController.get().switchToBubbleView(); } mConsecutiveRedirectCount = 0; return true; } URL updatedUrl = getUpdatedUrl(urlAsString, !viaUserInput); if (updatedUrl == null) { Log.d(TAG, "ignore unsupported URI scheme: " + urlAsString); showOpenInBrowserPrompt(R.string.unsupported_scheme_default_browser, R.string.unsupported_scheme_no_default_browser, mWebRenderer.getUrl().toString()); mConsecutiveRedirectCount = 0; return true; // true because we've handled the link ourselves } Log.d(TAG, "shouldOverrideUrlLoading() - url:" + urlAsString); if (viaUserInput) { URL currentUrl = mWebRenderer.getUrl(); mHandledAppPickerForCurrentUrl = false; mUsingLinkBubbleAsDefaultForCurrentUrl = false; } //if (mDelayedAutoContentDisplayLinkLoadedScheduled) { // mDelayedAutoContentDisplayLinkLoadedScheduled = false; // Log.e(TAG, "*** set mDelayedAutoContentDisplayLinkLoadedScheduled=" + mDelayedAutoContentDisplayLinkLoadedScheduled); //} removeCallbacks(mDelayedAutoContentDisplayLinkLoadedRunnable); String host; try { URL url = new URL(urlAsString); host = url.getHost(); if (host.equals("www.forbes.com")) { CookieManager.getInstance().setCookie("http://www.forbes.com", "forbes_ab=true"); CookieManager.getInstance().setCookie("http://www.forbes.com", "welcomeAd=true"); CookieManager.getInstance().setCookie("http://www.forbes.com", "adblock_session=Off"); CookieManager.getInstance().setCookie("http://www.forbes.com", "dailyWelcomeCookie=true"); if (url.getPath().equals("/forbes/welcome/") && mConsecutiveRedirectCount < 5) { mConsecutiveRedirectCount++; updateAndLoadUrl("http://www.forbes.com/"); return true; } } } catch (Exception e) { } mConsecutiveRedirectCount = 0; updateAndLoadUrl(urlAsString); mWebRendererController.resetBubblePanelAdjustment(); return true; } @Override public void onLoadUrl(String urlAsString) { try { URL url = new URL(urlAsString); mEventHandler.onPageLoading(url); updateUrlTitleAndText(urlAsString); } catch (MalformedURLException e) { e.printStackTrace(); } } @Override public void onReceivedError() { Log.d(TAG, "onReceivedError()"); mEventHandler.onPageLoaded(true); mReloadButton.setVisibility(VISIBLE); mShareButton.setVisibility(GONE); mArticleModeButton.setVisibility(GONE); } @Override public void onPageStarted(final String urlAsString, Bitmap favIcon) { Log.d(TAG, "onPageStarted() - " + urlAsString); //to do debug /* try { CrashTracking.log("onPageStarted(), " + urlAsString + ", index:" + MainController.get().getTabIndex(mOwnerTabView)); } catch (NullPointerException npe) { CrashTracking.log("onPageStarted(), " + urlAsString + ", index: no current MainController"); Log.e(TAG, npe.getLocalizedMessage(), npe); } */ if (mLifeState != LifeState.Alive) { return; } hideAllowLocationDialog(); mPageFinishedLoading = false; String oldUrl = mWebRenderer.getUrl().toString(); if (urlAsString.equals(Constant.ABOUT_BLANK_URI)) { Log.d(TAG, "ignore " + urlAsString); } else if (updateUrl(urlAsString) == false) { List<ResolveInfo> tempResolveInfos = new ArrayList<>(); if (!urlAsString.equals(mContext.getString(R.string.empty_bubble_page))) { tempResolveInfos = Settings.get().getAppsThatHandleUrl(urlAsString, getContext().getPackageManager()); } final List<ResolveInfo> apps = tempResolveInfos; boolean openedInApp = apps != null && apps.size() > 0 ? openInApp(apps.get(0), urlAsString) : false; if (openedInApp == false) { CrashTracking.log("ContentView.onPageStarted() - openedInApp == false"); openInBrowser(urlAsString); } return; } if (oldUrl.equals(Constant.NEW_TAB_URL)) { MainController.get().saveCurrentTabs(); } mWebRenderer.resetPageInspector(); final Context context = getContext(); PackageManager packageManager = context.getPackageManager(); URL currentUrl = mWebRenderer.getUrl(); List<ResolveInfo> tempResolveInfos = new ArrayList<>(); if (!currentUrl.toString().equals(mContext.getString(R.string.empty_bubble_page))) { tempResolveInfos = Settings.get().getAppsThatHandleUrl(currentUrl.toString(), getContext().getPackageManager()); } updateAppsForUrl(tempResolveInfos, currentUrl); if (Settings.get().redirectUrlToBrowser(currentUrl)) { CrashTracking.log("ContentView.onPageStarted() - url redirects to browser"); if (openInBrowser(urlAsString)) { String title = String.format(context.getString(R.string.link_redirected), Settings.get().getDefaultBrowserLabel()); MainApplication.saveUrlInHistory(context, null, urlAsString, title); return; } } if (mHandledAppPickerForCurrentUrl == false && mUsingLinkBubbleAsDefaultForCurrentUrl == false && mAppsForUrl != null && mAppsForUrl.size() > 0 && Settings.get().didRecentlyRedirectToApp(urlAsString) == false) { AppForUrl defaultAppForUrl = getDefaultAppForUrl(); if (defaultAppForUrl != null) { if (Util.isLinkBubbleResolveInfo(defaultAppForUrl.mResolveInfo)) { mUsingLinkBubbleAsDefaultForCurrentUrl = true; } else { if (openInApp(defaultAppForUrl.mResolveInfo, urlAsString)) { return; } } } else { boolean isLinkBubblePresent = false; //boolean isLinkBubblePresent = mAppsForUrl.size() == 1 ? Util.isLinkBubbleResolveInfo(mAppsForUrl.get(0).mResolveInfo) : false; for (AppForUrl info : mAppsForUrl) { // Handle crash: https://fabric.io/brave6/android/apps/com.linkbubble.playstore/issues/562667c7f5d3a7f76bf16a4c if (info.mResolveInfo == null || info.mResolveInfo.activityInfo == null) { CrashTracking .log("onPageStarted() Null resolveInfo when getting default for app: " + info); continue; } if (info.mResolveInfo.activityInfo.packageName.startsWith("com.linkbubble.playstore") || info.mResolveInfo.activityInfo.packageName.startsWith("com.brave.playstore")) { isLinkBubblePresent = true; break; } } if (isLinkBubblePresent == false && MainApplication.sShowingAppPickerDialog == false && mHandledAppPickerForCurrentUrl == false && mAppPickersUrls.contains(urlAsString) == false) { final ArrayList<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>(); for (AppForUrl appForUrl : mAppsForUrl) { if (appForUrl.mResolveInfo != null) { resolveInfos.add(appForUrl.mResolveInfo); } } if (0 != resolveInfos.size()) { AlertDialog dialog = ActionItem.getActionItemPickerAlert(context, resolveInfos, R.string.pick_default_app, new ActionItem.OnActionItemDefaultSelectedListener() { @Override public void onSelected(ActionItem actionItem, boolean always) { CrashTracking.log( "onPageStarted(): OnActionItemDefaultSelectedListener.onSelected()"); boolean loaded = false; String appPackageName = context.getPackageName(); for (ResolveInfo resolveInfo : resolveInfos) { if (resolveInfo.activityInfo.packageName .equals(actionItem.mPackageName) && resolveInfo.activityInfo.name .equals(actionItem.mActivityClassName)) { if (always) { Settings.get().setDefaultApp(urlAsString, resolveInfo); } // Jump out of the loop and load directly via a BubbleView below if (resolveInfo.activityInfo.packageName .equals(appPackageName)) { break; } mInitialUrlLoadStartTime = -1; loaded = MainApplication.loadIntent(context, actionItem.mPackageName, actionItem.mActivityClassName, urlAsString, -1, true); break; } } if (loaded) { if (MainController.get() != null) { MainController.get().closeTab(mOwnerTabView, MainController.get().contentViewShowing(), false); } Settings.get().addRedirectToApp(urlAsString); } // NOTE: no need to call loadUrl(urlAsString) or anything in the event the link is to be handled by // Link Bubble. The flow already assumes that will happen by continuing the load when the Dialog displays. #244 } }); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { MainApplication.sShowingAppPickerDialog = false; } }); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.show(); MainApplication.sShowingAppPickerDialog = true; mHandledAppPickerForCurrentUrl = true; mAppPickersUrls.add(urlAsString); } } } } configureOpenInAppButton(); configureOpenEmbedButton(); configureArticleModeButton(); Log.d(TAG, "redirect to url: " + urlAsString); mEventHandler.onPageLoading(mWebRenderer.getUrl()); updateUrlTitleAndText(urlAsString); if (mShareButton.getVisibility() == GONE) { mShareButton.setVisibility(VISIBLE); } if ((urlAsString.equals(Constant.WELCOME_MESSAGE_URL) || urlAsString.equals(getContext().getString(R.string.empty_bubble_page))) && mController != null && 1 == mController.mBubbleFlowDraggable.getActiveTabCount()) { mController.displayTab(mOwnerTabView); } } @Override public void onPageFinished(String urlAsString) { if (mLifeState != LifeState.Alive) { return; } if (mIgnoreNextOnPageFinished) { mIgnoreNextOnPageFinished = false; Log.d(TAG, "onPageFinished() - ignoring because of mIgnoreNextOnPageFinished..."); return; } Integer debugIndex = MainController.get() != null ? MainController.get().getTabIndex(mOwnerTabView) : null; CrashTracking.log("onPageFinished(), " + (debugIndex != null ? "index:" + debugIndex : "<MainController.get() == null>")); // This should not be necessary, but unfortunately is. // Often when pressing Back, onPageFinished() is mistakenly called when progress is 0. #245 if (mCurrentProgress != 100) { mPageFinishedIgnoredUrl = urlAsString; return; } onPageLoadComplete(urlAsString); if (null != MainController.get() && MainController.get().getCurrentTab() != mOwnerTabView) { mWebRenderer.pauseOnSetInactive(); } if (mUrlTextView.getText().toString().equals(getContext().getString(R.string.empty_bubble_page))) { mTitleTextView.performClick(); } } @Override public void onDownloadStart(String urlAsString) { ContentView.this.onDownloadStart(urlAsString); } @Override public void onReceivedTitle(String url, String title) { if (title == null || title.isEmpty()) { return; } if (MainApplication.sTitleHashMap != null && url != null) { MainApplication.sTitleHashMap.put(url, title); } updateUrlTitleAndText(url); } @Override public void onReceivedIcon(Bitmap bitmap) { // Only pass this along if the page has finished loading (https://github.com/brave/LinkBubble/issues/155). // This is to prevent passing a stale icon along when a redirect has already occurred. This shouldn't cause // too many ill-effects, because BitmapView attempts to load host/favicon.ico automatically anyway. if (mPageFinishedLoading) { if (mEventHandler.onReceivedIcon(bitmap)) { String faviconUrl = Util.getDefaultFaviconUrl(mWebRenderer.getUrl()); MainApplication.sFavicons.putFaviconInMemCache(faviconUrl, bitmap); } } } // Hacky variables to get around version 40 of Android System WebView returning "about:blank" // urls. // * mIgnoreNextOnProgressChanged is necessary to ignore the 100 progress that comes in with a // null urlAsString. // * mIgnoreNextOnPageFinished is necessary to ignore the ensuing onPageFinished() call. // // Both of these hacks combine to allow links to load correctly using WebView, and have the // progress indicator display as expected. boolean mIgnoreNextOnProgressChanged = false; boolean mIgnoreNextOnPageFinished = false; @Override public void onProgressChanged(int progress, String urlAsString) { if (urlAsString == null) { Log.d(TAG, "onProgressChanged(): ignore, no url"); mIgnoreNextOnProgressChanged = true; return; } else if (mIgnoreNextOnProgressChanged) { Log.d(TAG, "onProgressChanged(): ignoring next value..."); mIgnoreNextOnProgressChanged = false; mIgnoreNextOnPageFinished = true; return; } Log.d(TAG, "onProgressChanged() - progress:" + progress + ", " + urlAsString); mCurrentProgress = progress; // Note: annoyingly, onProgressChanged() can be called with values from a previous url. // Eg, "http://t.co/fR9bzpvyLW" redirects to "http://on.recode.net/1eOqNVq" which redirects to // "http://recode.net/2014/01/20/...", and after the "on.recode.net" redirect, progress is 100 for a moment. mEventHandler.onProgressChanged(progress); if (progress == 100 && mPageFinishedIgnoredUrl != null && mPageFinishedIgnoredUrl.equals(urlAsString)) { onPageLoadComplete(urlAsString); } } @Override public boolean onBackPressed() { return ContentView.this.onBackPressed(); } @Override public void onUrlLongClick(WebView webView, String url, int type) { ContentView.this.onUrlLongClick(webView, url, type); } @Override public void onShowBrowserPrompt() { ContentView.this.onShowBrowserPrompt(); } @Override public void onCloseWindow() { CrashTracking.log("WebRenderer.Controller.onCloseWindow()"); if (MainController.get() != null) { MainController.get().closeTab(mOwnerTabView, true, true); } } @Override public void onGeolocationPermissionsShowPrompt(String origin, WebRenderer.GetGeolocationCallback callback) { showAllowLocationDialog(origin, callback); } private Handler mHandler = new Handler(); private Runnable mUpdateOpenInAppRunnable = null; @Override public void onPageInspectorYouTubeEmbedFound() { if (mUpdateOpenInAppRunnable == null) { mUpdateOpenInAppRunnable = new Runnable() { @Override public void run() { configureOpenEmbedButton(); } }; } mOpenEmbedButton.post(mUpdateOpenInAppRunnable); } @Override public void onPageInspectorTouchIconLoaded(final Bitmap bitmap, final String pageUrl) { if (bitmap == null || pageUrl == null) { return; } mHandler.post(new Runnable() { @Override public void run() { URL url = mWebRenderer.getUrl(); if (url != null && url.toString().equals(pageUrl)) { mEventHandler.onReceivedIcon(bitmap); String faviconUrl = Util.getDefaultFaviconUrl(url); MainApplication.sFavicons.putFaviconInMemCache(faviconUrl, bitmap); } } }); } @Override public void onPageInspectorDropDownWarningClick() { mHandler.post(new Runnable() { @Override public void run() { showOpenInBrowserPrompt(R.string.unsupported_drop_down_default_browser, R.string.unsupported_drop_down_no_default_browser, mWebRenderer.getUrl().toString()); } }); } @Override public void onPagedInspectorThemeColorFound(final int color) { mHandler.post(new Runnable() { @Override public void run() { updateColors(color); mEventHandler.onThemeColor(color); } }); } @Override public void onArticleContentReady(ArticleContent articleContent) { Log.d("Article", "onArticleContentReady() - " + (articleContent == null ? "<null>" : "valid")); configureArticleModeButton(); } }; private String mPageFinishedIgnoredUrl; void onPageLoadComplete(String urlAsString) { mPageFinishedLoading = true; // NOTE: *don't* call updateUrl() here. Turns out, this function is called after a redirect has occurred. // Eg, urlAsString "t.co/xyz" even after the next redirect is starting to load // Check exact equality first for common case to avoid an allocation. URL currentUrl = mWebRenderer.getUrl(); boolean equalUrl = currentUrl.toString().equals(urlAsString); if (!equalUrl) { try { URL url = new URL(urlAsString); if (url.getProtocol().equals(currentUrl.getProtocol()) && url.getHost().equals(currentUrl.getHost()) && url.getPath().equals(currentUrl.getPath())) { equalUrl = true; } } catch (MalformedURLException e) { } } mWebRenderer.runPageInspector( mWebRendererController.adInsertionList(currentUrl.getHost().replace("www.", "").replace("m.", ""))); if (equalUrl) { updateAppsForUrl(currentUrl); configureOpenInAppButton(); configureOpenEmbedButton(); configureArticleModeButton(); mEventHandler.onPageLoaded(false); Log.e(TAG, "onPageLoadComplete() - url: " + urlAsString); String title = MainApplication.sTitleHashMap != null ? MainApplication.sTitleHashMap.get(urlAsString) : ""; if (TextUtils.isEmpty(title)) { // Note: it's possible for title == null above, but there be a valid title in the following case: // * title set for http://url.com/page?arg=1 // * urlAsString now changed to http://url.com/page, which isn't in sTitleHashMap // In this case, if there's a valid title, keep using it. if (mTitleTextView.getText() != null) { String currentTitle = mTitleTextView.getText().toString(); if (currentTitle.equals(mLoadingString) == false) { title = currentTitle; } } // If no title is set, display nothing rather than "Loading..." #265 if (title == null) { mTitleTextView.setText(null); } } if (!currentUrl.toString().equals(getContext().getString(R.string.empty_bubble_page))) { Settings settings = Settings.get(); if (null != settings && !settings.isIncognitoMode()) { // Adding the URL to the auto suggestions list mAdapter.addUrlToAutoSuggestion(currentUrl.toString()); MainApplication.saveUrlInHistory(getContext(), null, currentUrl.toString(), currentUrl.getHost(), title); } } else { mTitleTextView.performClick(); } //mDelayedAutoContentDisplayLinkLoadedScheduled = true; //Log.d(TAG, "set mDelayedAutoContentDisplayLinkLoadedScheduled=" + mDelayedAutoContentDisplayLinkLoadedScheduled); postDelayed(mDelayedAutoContentDisplayLinkLoadedRunnable, Constant.AUTO_CONTENT_DISPLAY_DELAY); mWebRenderer.onPageLoadComplete(); mWebRenderer.getView().requestFocus(); } mPageFinishedIgnoredUrl = null; } //boolean mDelayedAutoContentDisplayLinkLoadedScheduled = false; // Call autoContentDisplayLinkLoaded() via a delay so as to fix #412 Runnable mDelayedAutoContentDisplayLinkLoadedRunnable = new Runnable() { @Override public void run() { if (mLifeState == LifeState.Alive && MainController.get() != null) { //Log.e(TAG, "*** set mDelayedAutoContentDisplayLinkLoadedScheduled=" + mDelayedAutoContentDisplayLinkLoadedScheduled); MainController.get().autoContentDisplayLinkLoaded(mOwnerTabView); saveLoadTime(); } } }; OnClickListener mOnShareButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { showSelectShareMethod(mUrlTextView.getText().toString(), true); } }; OnClickListener mbtClearUrlClicked = new View.OnClickListener() { public void onClick(View v) { metUrl.setText(""); mbtUrlClear.setEnabled(false); mbtUrlClear.getBackground().setAlpha(50); } }; OnFocusChangeListener murlOnFocusChangeListener = new OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean b) { if (!b) { // Show the toolbar again if lost focus and hide the soft keyboard findViewById(R.id.content_toolbar).bringToFront(); InputMethodManager imm = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(metUrl.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); } } }; AdapterView.OnItemClickListener murlOnItemClickListener = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { // Hide the soft keyboard InputMethodManager imm = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(metUrl.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); SearchURLSuggestions urlSuggestion = (SearchURLSuggestions) adapterView.getItemAtPosition(i); WorkWithURL(urlSuggestion.Name, urlSuggestion.EngineToUse, false); } }; TextView.OnEditorActionListener murlActionListener = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_GO) { InputMethodManager imm = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(metUrl.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); String urlText = metUrl.getText().toString(); String urlToCheck = urlText; if (!urlToCheck.startsWith(getContext().getString(R.string.http_prefix)) && !urlToCheck.startsWith(getContext().getString(R.string.https_prefix))) urlToCheck = getContext().getString(R.string.http_prefix) + urlToCheck; if (Util.isValidURL(getContext(), urlToCheck)) { WorkWithURL(urlText, SearchURLSuggestions.SearchEngine.NONE, true); } else if (null != mFirstSuggestedItem) { String strUrl = mFirstSuggestedItem.Name; WorkWithURL(strUrl, SearchURLSuggestions.SearchEngine.NONE, true); } } return false; } }; EditText.OnKeyListener murlKeyListener = new EditText.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_BACK) { return ContentView.this.onBackPressed(); } } return false; } }; DataSetObserver mDataSetObserver = new DataSetObserver() { @Override public void onChanged() { super.onChanged(); if (mAdapter.getCount() > 0) { mFirstSuggestedItem = (SearchURLSuggestions) mAdapter.getItem(0); } DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); int pixels = 0; if (mAdapter.getCount() > mRowsToShowOnAutoSuggestions) { float dp = mOneRowAutoSuggestionsSize * mRowsToShowOnAutoSuggestions; float fpixels = metrics.density * dp; pixels = (int) (fpixels + 0.5f); } else { float dp = mOneRowAutoSuggestionsSize; float fpixels = metrics.density * dp; pixels = (int) (fpixels + 0.5f) * mAdapter.getCount(); } metUrl.setDropDownHeight(pixels); // Set an autosuggestion String urlText = metUrl.getText().toString(); if (mApplyAutoSuggestionToUrlString && 0 != urlText.length() && null != mFirstSuggestedItem && SearchURLSuggestions.SearchEngine.NONE == mFirstSuggestedItem.EngineToUse) { String suggestedString = mFirstSuggestedItem.Name; String stringToAppend = ""; if (suggestedString.length() > urlText.length()) { stringToAppend = suggestedString.substring(urlText.length()); String toCompare = suggestedString.substring(0, urlText.length()); if (toCompare.equals(urlText)) { mSetTheRealUrlString = false; metUrl.setText(urlText + stringToAppend); mSetTheRealUrlString = true; metUrl.setSelection(urlText.length(), urlText.length() + stringToAppend.length()); } } } } }; TextWatcher murlTextWatcher = new TextWatcher() { private String mBeforeTextString; private boolean mApplyAutoSuggestion = true; @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { mBeforeTextString = metUrl.getText().toString(); } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { String urlText = metUrl.getText().toString(); if (!mFirstTimeUrlTyped && (urlText.equals(mAdapter.mRealUrlBarConstraint) || mAdapter.mRealUrlBarConstraint.length() > urlText.length())) { mApplyAutoSuggestion = false; } else { mApplyAutoSuggestion = true; } if (mSetTheRealUrlString) { mAdapter.mRealUrlBarConstraint = urlText; } mFirstTimeUrlTyped = false; } @Override public void afterTextChanged(Editable editable) { String urlText = metUrl.getText().toString(); if (!mApplyAutoSuggestion) { mApplyAutoSuggestionToUrlString = false; } else { mApplyAutoSuggestionToUrlString = true; } if (urlText.length() != 0) { mbtUrlClear.setEnabled(true); mbtUrlClear.getBackground().setAlpha(255); } else { mbtUrlClear.setEnabled(false); mbtUrlClear.getBackground().setAlpha(50); } } }; OnClickListener mOnURLEnterClicked = new OnClickListener() { @Override public void onClick(View view) { if (null != mController && !mController.mBubbleFlowDraggable.isExpanded()) { return; } if (!mWebRenderer.getUrl().toString().equals(getContext().getString(R.string.empty_bubble_page))) { metUrl.setText(mUrlTextView.getText().toString()); } else { metUrl.setText(""); } mFirstTimeUrlTyped = true; // Bring the search URL layout on top findViewById(R.id.content_edit_url).bringToFront(); // Request the focus for the search URL control metUrl.requestFocus(); metUrl.selectAll(); // Show the soft keyboard InputMethodManager imm = (InputMethodManager) getContext() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); imm.showSoftInput(metUrl, InputMethodManager.SHOW_FORCED); } }; OpenInAppButton.OnOpenInAppClickListener mOnOpenInAppButtonClickListener = new OpenInAppButton.OnOpenInAppClickListener() { @Override public void onAppOpened() { CrashTracking.log("mOnOpenInAppButtonClickListener.onAppOpened()"); if (MainController.get() != null) { MainController.get().closeTab(mOwnerTabView, true, false); // L_WATCH: L currently lacks getRecentTasks(), so minimize here if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { MainController.get().switchToBubbleView(); } } } }; OpenEmbedButton.OnOpenEmbedClickListener mOnOpenEmbedButtonClickListener = new OpenEmbedButton.OnOpenEmbedClickListener() { @Override public void onYouTubeEmbedOpened() { } }; OnClickListener mOnReloadButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { mReloadButton.setVisibility(GONE); mWebRenderer.reload(); } }; ArticleRenderer.Controller mArticleModeController = new ArticleRenderer.Controller() { @Override public void onUrlLongClick(WebView webView, String url, int type) { ContentView.this.onUrlLongClick(webView, url, type); } @Override public void onDownloadStart(String urlAsString) { ContentView.this.onDownloadStart(urlAsString); } @Override public boolean onBackPressed() { return ContentView.this.onBackPressed(); } @Override public void onShowBrowserPrompt() { ContentView.this.onShowBrowserPrompt(); } @Override public void onFirstPageLoadStarted() { // Ugly hack to get ensure the Back button works in Article mode if (mArticleModeButton.getState() == ArticleModeButton.State.Web) { mWebRenderer.getView().setVisibility(View.INVISIBLE); } } @Override public void onWebViewContextMenuAppearedGone(boolean appeared) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1 || mUrlTextView.getText().toString() .equals(getContext().getString(R.string.empty_bubble_page))) { return; } MainController mainController = MainController.get(); if (null != mainController) { mainController.onWebViewContextMenuAppearedGone(appeared); } } @Override public void resetBubblePanelAdjustment() { if (!mArticleRenderer.getView().hasFocus()) { mArticleRenderer.getView().requestFocus(); } MainController mainController = MainController.get(); if (null != mainController) { mainController.adjustBubblesPanel(0, 0, false, true); } } @Override public void adjustBubblesPanel(int newY, int oldY, boolean afterTouchAdjust) { if (!mArticleRenderer.getView().hasFocus()) { mArticleRenderer.getView().requestFocus(); } MainController mainController = MainController.get(); if (null != mainController) { mainController.adjustBubblesPanel(newY, oldY, afterTouchAdjust, false); } } }; OnClickListener mOnArticleModeButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { mArticleModeButton.toggleState(); mArticleModeButton.updateTheme(themeColor); ArticleContent articleContent = mWebRenderer.getArticleContent(); switch (mArticleModeButton.getState()) { case Article: if (mArticleRenderer != null && mArticleRenderer.getView() != null) { mArticleRenderer.getView().setVisibility(View.INVISIBLE); } mWebRenderer.getView().setVisibility(View.VISIBLE); mWebRenderer.getView().bringToFront(); break; case Web: if (mArticleRenderer == null) { View articleRendererPlaceholder = findViewById(R.id.article_renderer_placeholder); mArticleRenderer = new ArticleRenderer(getContext(), mArticleModeController, articleContent, articleRendererPlaceholder); } else { mArticleRenderer.display(articleContent); mWebRenderer.getView().setVisibility(View.INVISIBLE); } mArticleRenderer.getView().setVisibility(VISIBLE); mArticleRenderer.getView().bringToFront(); break; } } }; OnClickListener mOnOverflowButtonClickListener = new OnClickListener() { @Override public void onClick(View v) { final Context context = getContext(); mOverflowPopupMenu = new PopupMenu(context, mOverflowButton); Resources resources = context.getResources(); final MenuItem siteProtection = mOverflowPopupMenu.getMenu() .add(Menu.NONE, R.id.item_site_protection, Menu.NONE, resources.getString(R.string.action_site_protection)) .setCheckable(true).setChecked(!mHostInWhiteList); if (mCurrentProgress != 100) { mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_stop, Menu.NONE, resources.getString(R.string.action_stop)); } mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_reload_page, Menu.NONE, resources.getString(R.string.action_reload_page)); String defaultBrowserLabel = Settings.get().getDefaultBrowserLabel(); if (defaultBrowserLabel != null) { mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_open_in_browser, Menu.NONE, String.format(resources.getString(R.string.action_open_in_browser), defaultBrowserLabel)); } final MenuItem requestDesktopSite = mOverflowPopupMenu.getMenu() .add(Menu.NONE, R.id.item_request_desktop_site, Menu.NONE, resources.getString(R.string.action_request_desktop_site)) .setCheckable(true).setChecked( mWebRenderer.getUserAgentString(mContext).equals(Constant.USER_AGENT_CHROME_DESKTOP)); mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_copy_link, Menu.NONE, resources.getString(R.string.action_copy_to_clipboard)); mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_new_bubble, Menu.NONE, resources.getString(R.string.action_new_bubble)); mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_close_tab, Menu.NONE, resources.getString(R.string.action_close_tab)); mOverflowPopupMenu.getMenu().add(Menu.NONE, R.id.item_settings, Menu.NONE, resources.getString(R.string.action_settings)); mOverflowPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.item_site_protection: { CrashTracking.log("R.id.item_site_protection"); // This looks backwards, but is correct, as isChecked() isn't true until // after onMenuItemClick() is called. AddRemoveHostFromWhiteList(mWebRenderer.getUrl().toString(), siteProtection.isChecked()); HostInWhiteListCheck(mWebRenderer.getUrl().toString()); // We need to go to reload page case. } case R.id.item_reload_page: { CrashTracking.log("R.id.item_reload_page"); mWebRenderer.resetPageInspector(); URL currentUrl = mWebRenderer.getUrl(); mEventHandler.onPageLoading(currentUrl); mWebRenderer.stopLoading(); mWebRenderer.reload(); String urlAsString = currentUrl.toString(); updateAppsForUrl(currentUrl); configureOpenInAppButton(); configureOpenEmbedButton(); configureArticleModeButton(); Log.d(TAG, "reload url: " + urlAsString); mInitialUrlLoadStartTime = System.currentTimeMillis(); updateUrlTitleAndText(urlAsString); break; } case R.id.item_open_in_browser: { CrashTracking.log("ContentView.setOnMenuItemClickListener() - open in browser clicked"); openInBrowser(mWebRenderer.getUrl().toString(), true); break; } case R.id.item_request_desktop_site: { String newUserAgentString; // This looks backwards, but is correct, as isChecked() isn't true until // after onMenuItemClick() is called. if (!requestDesktopSite.isChecked()) { newUserAgentString = Constant.USER_AGENT_CHROME_DESKTOP; } else { String defaultUserAgentString = Settings.get().getUserAgentString(); if (defaultUserAgentString != null && !defaultUserAgentString.equals(Constant.USER_AGENT_CHROME_DESKTOP)) { newUserAgentString = defaultUserAgentString; } else { newUserAgentString = Util.getDefaultUserAgentString(getContext()); } } mWebRenderer.setUserAgentString(newUserAgentString); mWebRenderer.reload(); break; } case R.id.item_copy_link: { MainApplication.copyLinkToClipboard(getContext(), mWebRenderer.getUrl().toString(), R.string.bubble_link_copied_to_clipboard); break; } case R.id.item_stop: { mWebRenderer.stopLoading(); break; } case R.id.item_new_bubble: { //to do debug //Intent intent = new Intent(getContext(), BubbleFlowActivity.class); //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); //getContext().startActivity(intent); // MainApplication.openLink(getContext(), getContext().getString(R.string.empty_bubble_page), Analytics.OPENED_URL_FROM_NEW_TAB); break; } case R.id.item_close_tab: { CrashTracking.log("R.id.item_close_tab"); if (MainController.get() != null) { MainController.get().closeTab(mOwnerTabView, MainController.get().contentViewShowing(), true); } break; } case R.id.item_settings: { Intent intent = new Intent(context, SettingsActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); context.startActivity(intent); MainController.get().switchToBubbleView(); break; } } mOverflowPopupMenu = null; return false; } }); mOverflowPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() { @Override public void onDismiss(PopupMenu menu) { if (mOverflowPopupMenu == menu) { mOverflowPopupMenu = null; } } }); mOverflowPopupMenu.show(); } }; OnSwipeTouchListener mOnTextContainerTouchListener = new OnSwipeTouchListener() { public void onSwipeRight() { MainController.get().showPreviousBubble(); } public void onSwipeLeft() { MainController.get().showNextBubble(); } }; private void onDownloadStart(String urlAsString) { CrashTracking.log("onDownloadStart()"); openInBrowser(urlAsString); if (MainController.get() != null) { MainController.get().closeTab(mOwnerTabView, true, false); // L_WATCH: L currently lacks getRecentTasks(), so minimize here if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { MainController.get().switchToBubbleView(); } } } private boolean onBackPressed() { if (mUrlStack.size() <= 1) { CrashTracking.log("onBackPressed() - closeTab()"); if (MainController.get() != null) { MainController.get().closeTab(mOwnerTabView, BubbleAction.BackButton, true, true); } return true; } else { CrashTracking.log("onBackPressed() - go back"); mWebRenderer.stopLoading(); String urlBefore = mWebRenderer.getUrl().toString(); mUrlStack.pop(); URL previousUrl = mUrlStack.peek(); String previousUrlAsString = previousUrl.toString(); mEventHandler.onCanGoBackChanged(mUrlStack.size() > 1); mHandledAppPickerForCurrentUrl = false; mUsingLinkBubbleAsDefaultForCurrentUrl = false; Log.d(TAG, "[urlstack] Go back: " + urlBefore + " -> " + mWebRenderer.getUrl() + ", urlStack.size():" + mUrlStack.size()); updateAndLoadUrl(previousUrlAsString); updateUrlTitleAndText(previousUrlAsString); mEventHandler.onPageLoading(mWebRenderer.getUrl()); updateAppsForUrl(null, previousUrl); configureOpenInAppButton(); configureArticleModeButton(); mWebRenderer.resetPageInspector(); configureOpenEmbedButton(); // The WebView doesn't reload on all pages correctly if call only loadUrl, seems like there is some kind of cache as // it loads fast on back but doesn't load pictures for thestar.com website. clearCache method doesn't work also. Only // reload works nice here. Perhaps it is some bu in API as lots of people say that problem with loadUrl method // That is the temp fix, thestar.com has a new beta website and it works great with it without reloading if (previousUrlAsString.endsWith("m.thestar.com/#/?referrer=")) { mWebRenderer.reload(); } return true; } } public void onShowBrowserPrompt() { showOpenInBrowserPrompt(R.string.long_press_unsupported_default_browser, R.string.long_press_unsupported_no_default_browser, mWebRenderer.getUrl().toString()); } private void onUrlLongClick(final WebView webView, final String urlAsString, final int type) { Resources resources = getResources(); final ArrayList<String> longClickSelections = new ArrayList<String>(); final String shareLabel = resources.getString(R.string.action_share); longClickSelections.add(shareLabel); String defaultBrowserLabel = Settings.get().getDefaultBrowserLabel(); final String leftConsumeBubbleLabel = Settings.get().getConsumeBubbleLabel(BubbleAction.ConsumeLeft); if (leftConsumeBubbleLabel != null) { if (defaultBrowserLabel == null || defaultBrowserLabel.equals(leftConsumeBubbleLabel) == false) { longClickSelections.add(leftConsumeBubbleLabel); } } final String rightConsumeBubbleLabel = Settings.get().getConsumeBubbleLabel(BubbleAction.ConsumeRight); if (rightConsumeBubbleLabel != null) { if (defaultBrowserLabel == null || defaultBrowserLabel.equals(rightConsumeBubbleLabel) == false) { longClickSelections.add(rightConsumeBubbleLabel); } } // Long pressing for a link doesn't work reliably, re #279 //final String copyLinkLabel = resources.getString(R.string.action_copy_to_clipboard); //longClickSelections.add(copyLinkLabel); Collections.sort(longClickSelections); final String openLinkInNewBubbleLabel = resources.getString(R.string.action_open_link_in_new_bubble); final String openImageInNewBubbleLabel = resources.getString(R.string.action_open_image_in_new_bubble); if (type == WebView.HitTestResult.IMAGE_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { longClickSelections.add(0, openImageInNewBubbleLabel); } if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { longClickSelections.add(0, openLinkInNewBubbleLabel); } final String openInBrowserLabel = defaultBrowserLabel != null ? String.format(resources.getString(R.string.action_open_in_browser), defaultBrowserLabel) : null; if (openInBrowserLabel != null) { longClickSelections.add(1, openInBrowserLabel); } final String saveImageLabel = resources.getString(R.string.action_save_image); if (type == WebView.HitTestResult.IMAGE_TYPE || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { longClickSelections.add(saveImageLabel); } ListView listView = new ListView(getContext()); listView.setAdapter(new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, longClickSelections.toArray(new String[0]))); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { CrashTracking.log("ContentView listView.setOnItemClickListener"); String string = longClickSelections.get(position); if (string.equals(openLinkInNewBubbleLabel) && type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { Message msg = new Message(); msg.setTarget(new Handler() { @Override public void handleMessage(Message msg) { Bundle b = msg.getData(); if (b != null && b.getString("url") != null) { MainController.get().openUrl(b.getString("url"), System.currentTimeMillis(), false, Analytics.OPENED_URL_FROM_NEW_TAB); } } }); webView.requestFocusNodeHref(msg); } if (string.equals(openLinkInNewBubbleLabel) || string.equals(openImageInNewBubbleLabel)) { MainController controller = MainController.get(); if (null != controller) { controller.openUrl(urlAsString, System.currentTimeMillis(), false, Analytics.OPENED_URL_FROM_NEW_TAB); } else { MainApplication.openLink(getContext(), urlAsString, Analytics.OPENED_URL_FROM_NEW_TAB); } } else if (openInBrowserLabel != null && string.equals(openInBrowserLabel)) { openInBrowser(urlAsString); } else if (string.equals(shareLabel)) { showSelectShareMethod(urlAsString, false); } else if (string.equals(saveImageLabel)) { saveImage(urlAsString); } else if (leftConsumeBubbleLabel != null && string.equals(leftConsumeBubbleLabel)) { MainApplication.handleBubbleAction(getContext(), BubbleAction.ConsumeLeft, urlAsString, -1); } else if (rightConsumeBubbleLabel != null && string.equals(rightConsumeBubbleLabel)) { MainApplication.handleBubbleAction(getContext(), BubbleAction.ConsumeRight, urlAsString, -1); //} else if (string.equals(copyLinkLabel)) { // MainApplication.copyLinkToClipboard(getContext(), urlAsString, R.string.link_copied_to_clipboard); } if (mLongPressAlertDialog != null) { mLongPressAlertDialog.dismiss(); } } }); listView.setBackgroundColor(Settings.get().getThemedContentViewColor()); mLongPressAlertDialog = new AlertDialog.Builder(getContext()).create(); mLongPressAlertDialog.setView(listView); mLongPressAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); mLongPressAlertDialog.show(); } private void configureOpenEmbedButton() { if (mOpenEmbedButton.configure(mWebRenderer.getPageInspectorYouTubeEmbedHelper())) { mOpenEmbedButton.invalidate(); } else { mOpenEmbedButton.setVisibility(GONE); } } private void configureOpenInAppButton() { if (mOpenInAppButton.configure(mAppsForUrl)) { mOpenInAppButton.invalidate(); } else { mOpenInAppButton.setVisibility(GONE); } } private void configureArticleModeButton() { ArticleContent articleContent = mWebRenderer.getArticleContent(); if (articleContent != null) { if (mArticleNotificationId == -1 && TextUtils.isEmpty(articleContent.mText) == false && Settings.get().getArticleModeOnWearEnabled()) { mArticleNotificationId = sNextArticleNotificationId; sNextArticleNotificationId++; String title = MainApplication.sTitleHashMap != null ? MainApplication.sTitleHashMap.get(articleContent.mUrl.toString()) : "Open Bubble"; Context context = getContext(); Intent closeTabIntent = new Intent(context, NotificationCloseTabActivity.class); closeTabIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); closeTabIntent.putExtra(NotificationCloseTabActivity.EXTRA_DISMISS_NOTIFICATION, mArticleNotificationId); PendingIntent closeTabPendingIntent = PendingIntent.getActivity(context, mArticleNotificationId, closeTabIntent, PendingIntent.FLAG_UPDATE_CURRENT); Intent openTabIntent = new Intent(context, NotificationOpenTabActivity.class); openTabIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_NEW_TASK); openTabIntent.putExtra(NotificationOpenTabActivity.EXTRA_DISMISS_NOTIFICATION, mArticleNotificationId); PendingIntent openTabPendingIntent = PendingIntent.getActivity(context, (int) System.currentTimeMillis(), openTabIntent, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new NotificationCompat.Builder(context) .addAction(R.drawable.ic_action_cancel_white, context.getString(R.string.action_close_tab), closeTabPendingIntent) .setContentTitle(title).setContentText(articleContent.mText) .setSmallIcon(R.drawable.ic_launcher).setGroup(Constant.NOTIFICATION_GROUP_KEY_ARTICLES) .setContentIntent(openTabPendingIntent).build(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(mArticleNotificationId, notification); } if (Settings.get().getArticleModeEnabled()) { mArticleModeButton.setVisibility(VISIBLE); } else { mArticleModeButton.setVisibility(GONE); } } else { mArticleModeButton.setVisibility(GONE); if (mArticleRenderer != null) { mArticleRenderer.stopLoading(); } cancelWearNotification(); } } private void cancelWearNotification() { if (mArticleNotificationId > -1) { NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getContext()); notificationManager.cancel(mArticleNotificationId); mArticleNotificationId = -1; } } private void updateAppsForUrl(URL url) { String urlString = url.toString(); List<ResolveInfo> tempResolveInfos = new ArrayList<>(); if (!urlString.equals(mContext.getString(R.string.empty_bubble_page))) { tempResolveInfos = Settings.get().getAppsThatHandleUrl(urlString, getContext().getPackageManager()); } final List<ResolveInfo> resolveInfos = tempResolveInfos; updateAppsForUrl(resolveInfos, url); } private void updateAppsForUrl(List<ResolveInfo> resolveInfos, URL url) { if (resolveInfos != null && resolveInfos.size() > 0) { mTempAppsForUrl.clear(); for (ResolveInfo resolveInfoToAdd : resolveInfos) { if (resolveInfoToAdd.activityInfo != null) { boolean alreadyAdded = false; for (int i = 0; i < mAppsForUrl.size(); i++) { AppForUrl existing = mAppsForUrl.get(i); // In certain situations mResolveInfo is null, likely because we can't find the app. // One possibility is that this happens when the app is currently being updated through the play store. // Prevents crash: https://fabric.io/brave6/android/apps/com.linkbubble.playstore/issues/55dcee53e0d514e5d6413e8d if (existing.mResolveInfo == null) { continue; } if (existing.mResolveInfo.activityInfo.packageName .equals(resolveInfoToAdd.activityInfo.packageName) && existing.mResolveInfo.activityInfo.name .equals(resolveInfoToAdd.activityInfo.name)) { alreadyAdded = true; if (existing.mUrl.equals(url) == false) { if (url.getHost().contains(existing.mUrl.getHost()) && url.getHost().length() > existing.mUrl.getHost().length()) { // don't update the url in this case. This means prevents, as an example, saving a host like // "mobile.twitter.com" instead of using "twitter.com". This occurs when loading // "https://twitter.com/lokibartleby/status/412160702707539968" with Tweet Lanes // and the official Twitter client installed. } else { try { existing.mUrl = new URL(url.toString()); // Update the Url } catch (MalformedURLException e) { throw new RuntimeException("Malformed URL: " + url); } } } break; } } if (alreadyAdded == false) { //if (resolveInfoToAdd.activityInfo.packageName.equals(Settings.get().mLinkBubbleEntryActivityResolveInfo.activityInfo.packageName)) { // continue; //} mTempAppsForUrl.add(resolveInfoToAdd); } } } if (mTempAppsForUrl.size() > 0) { URL currentUrl; try { currentUrl = new URL(url.toString()); } catch (MalformedURLException e) { e.printStackTrace(); return; } // We need to handle the following case: // * Load reddit.com/r/Android. The app to handle that URL might be "Reddit is Fun" or something similar. // * Click on a link to play.google.com/store/, which is handled by the "Google Play" app. // * The code below adds "Google Play" to the app list that contains "Reddit is Fun", // even though "Reddit is Fun" is not applicable for this link. // Unfortunately there is no way reliable way to find out when a user has clicked on a link using the WebView. // http://stackoverflow.com/a/17937536/328679 is close, but doesn't work because it relies on onPageFinished() // being called, which will not be called if the current page is still loading when the link was clicked. // // So, in the event contains results, and these results reference a different URL that which matched the // resolveInfos passed in, clear mAppsForUrl. if (mAppsForUrl.size() > 0) { URL firstUrl = mAppsForUrl.get(0).mUrl; if ((currentUrl.getHost().contains(firstUrl.getHost()) && currentUrl.getHost().length() > firstUrl.getHost().length()) == false) { mAppsForUrl.clear(); // start again } } for (ResolveInfo resolveInfoToAdd : mTempAppsForUrl) { mAppsForUrl.add(new AppForUrl(resolveInfoToAdd, currentUrl)); } } } else { mAppsForUrl.clear(); } boolean containsLinkBubble = false; for (AppForUrl appForUrl : mAppsForUrl) { if (appForUrl.mResolveInfo != null && appForUrl.mResolveInfo.activityInfo != null && appForUrl.mResolveInfo.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID)) { containsLinkBubble = true; break; } } if (containsLinkBubble == false) { mAppsForUrl.add(new AppForUrl(Settings.get().mLinkBubbleEntryActivityResolveInfo, url)); } } AppForUrl getDefaultAppForUrl() { if (mAppsForUrl != null && mAppsForUrl.size() > 0) { mTempAppsForUrl.clear(); for (AppForUrl appForUrl : mAppsForUrl) { mTempAppsForUrl.add(appForUrl.mResolveInfo); } if (mTempAppsForUrl.size() > 0) { ResolveInfo defaultApp = Settings.get().getDefaultAppForUrl(mWebRenderer.getUrl(), mTempAppsForUrl); if (defaultApp != null) { for (AppForUrl appForUrl : mAppsForUrl) { if (appForUrl.mResolveInfo == defaultApp) { return appForUrl; } } } } } return null; } public void onAnimateOnScreen() { hidePopups(); resetButtonPressedStates(); } public void onAnimateOffscreen() { hidePopups(); resetButtonPressedStates(); } public void onBeginBubbleDrag() { hidePopups(); resetButtonPressedStates(); } void onCurrentContentViewChanged(boolean isCurrent) { hidePopups(); resetButtonPressedStates(); if (isCurrent && MainController.get().contentViewShowing()) { saveLoadTime(); } } public void saveLoadTime() { if (mInitialUrlLoadStartTime > -1) { Settings.get().trackLinkLoadTime(System.currentTimeMillis() - mInitialUrlLoadStartTime, Settings.LinkLoadType.PageLoad, mWebRenderer.getUrl().toString()); mInitialUrlLoadStartTime = -1; } } void onOrientationChanged() { metUrl.setDropDownWidth(getResources().getDisplayMetrics().widthPixels); mAdapter.setDropDownWidth(getResources().getDisplayMetrics().widthPixels); } private boolean updateUrl(String urlAsString) { if (urlAsString.equals("about:blank")) { Log.d(TAG, "updateUrl(): ignore url:" + urlAsString); return true; } if (urlAsString.equals(mWebRenderer.getUrl().toString()) == false) { try { Log.d(TAG, "change url from " + mWebRenderer.getUrl() + " to " + urlAsString); HostInWhiteListCheck(urlAsString); mWebRenderer.setUrl(mWebRendererController.getHTTPSUrl(urlAsString)); } catch (MalformedURLException e) { return false; } } return true; } private void updateAndLoadUrl(String urlAsString) { mThirdPartyHosts = null; updateUrl(urlAsString); URL updatedUrl = getUrl(); WebRenderer.Mode mode = WebRenderer.Mode.Web; if (Settings.get().getAutoArticleMode()) { String path = updatedUrl.getPath(); if (path != null && !path.equals("") && !path.equals("/")) { mode = WebRenderer.Mode.Article; } } //if (mWebRenderer.getUrl().toString().equals(getContext().getString(R.string.empty_bubble_page))) { //mWebRenderer.getView().bringToFront(); //} mWebRenderer.loadUrl(updatedUrl, mode); } private void cleanVisitedHistory(String urlToLook) { String peekUrl = ""; if (mUrlStack.size() > 0) { peekUrl = mUrlStack.peek().toString(); } if (peekUrl.equals(urlToLook)) { mUrlStack.pop(); mEventHandler.onCanGoBackChanged(mUrlStack.size() > 1); } } private URL getUpdatedUrl(String urlAsString, boolean cleanVisitedHistory) { URL currentUrl = mWebRenderer.getUrl(); String currentUrlString = currentUrl.toString(); if (urlAsString.equals(currentUrl.toString()) == false) { if (cleanVisitedHistory) { cleanVisitedHistory(currentUrlString); } try { Log.d(TAG, "getUpdatedUrl(): change url from " + currentUrlString + " to " + urlAsString); return new URL(urlAsString); } catch (MalformedURLException e) { return null; } } return currentUrl; } URL getUrl() { return mWebRenderer.getUrl(); } private void hidePopups() { if (mOverflowPopupMenu != null) { mOverflowPopupMenu.dismiss(); mOverflowPopupMenu = null; } if (mLongPressAlertDialog != null) { mLongPressAlertDialog.dismiss(); mLongPressAlertDialog = null; } mWebRenderer.hidePopups(); } private void resetButtonPressedStates() { if (mShareButton != null) { mShareButton.setIsTouched(false); } if (mOpenEmbedButton != null) { mOpenEmbedButton.setIsTouched(false); } if (mOpenInAppButton != null) { mOpenInAppButton.setIsTouched(false); } if (mArticleModeButton != null) { mArticleModeButton.setIsTouched(false); } if (mOverflowButton != null) { mOverflowButton.setIsTouched(false); } } private boolean openInBrowser(String urlAsString) { return openInBrowser(urlAsString, false); } private boolean openInBrowser(String urlAsString, boolean canShowUndoPrompt) { Log.d(TAG, "ContentView.openInBrowser() - url:" + urlAsString); CrashTracking.log("ContentView.openInBrowser()"); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(urlAsString)); intent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); if (MainApplication.openInBrowser(getContext(), intent, true) && MainController.get() != null && mOwnerTabView != null) { // L_WATCH: L currently lacks getRecentTasks(), so minimize here MainController.get().switchToBubbleView(); MainController.get().closeTab(mOwnerTabView, MainController.get().contentViewShowing(), canShowUndoPrompt); return true; } return false; } private boolean openInApp(ResolveInfo resolveInfo, String urlAsString) { Context context = getContext(); if (MainApplication.loadResolveInfoIntent(getContext(), resolveInfo, urlAsString, mInitialUrlLoadStartTime)) { CrashTracking.log("openInApp(): resolveInfo:" + resolveInfo.activityInfo.packageName); String title = String.format(context.getString(R.string.link_loaded_with_app), resolveInfo.loadLabel(context.getPackageManager())); MainApplication.saveUrlInHistory(context, resolveInfo, urlAsString, title); MainController mainController = MainController.get(); if (mainController != null) { mainController.closeTab(mOwnerTabView, mainController.contentViewShowing(), false); } Settings.get().addRedirectToApp(urlAsString); // L_WATCH: L currently lacks getRecentTasks(), so minimize here if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT && null != mainController) { mainController.switchToBubbleView(); } return true; } return false; } private void showOpenInBrowserPrompt(int hasBrowserStringId, int noBrowserStringId, final String urlAsString) { String defaultBrowserLabel = Settings.get().getDefaultBrowserLabel(); String message; if (defaultBrowserLabel != null) { message = String.format(getResources().getString(hasBrowserStringId), defaultBrowserLabel); } else { message = getResources().getString(noBrowserStringId); } Prompt.show(message, getResources().getString(android.R.string.ok), Prompt.LENGTH_LONG, new Prompt.OnPromptEventListener() { @Override public void onActionClick() { if (urlAsString != null) { CrashTracking.log("ContentView.showOpenInBrowserPrompt() - onActionClick()"); openInBrowser(urlAsString); } } @Override public void onClose() { } }); } private void changeTitleTextColor() { Settings settings = Settings.get(); mTitleTextView.setTextColor(0xFFFFFFFF); if (null != settings && settings.getDarkThemeEnabled()) { mTitleTextView.setTextColor(0x0); } } private void changeUrlTextColor() { Settings settings = Settings.get(); mUrlTextView.setTextColor(0xFFFFFFFF); if (null != settings && settings.getDarkThemeEnabled()) { mUrlTextView.setTextColor(0x0); } } void updateUrlTitleAndText(String urlAsString) { String title = MainApplication.sTitleHashMap != null ? MainApplication.sTitleHashMap.get(urlAsString) : null; boolean showTitleUrl = !urlAsString.equals(getContext().getString(R.string.empty_bubble_page)); if (title == null) { title = mLoadingString; } else if (!showTitleUrl) { changeTitleTextColor(); } mTitleTextView.setText(title); if (urlAsString.equals(Constant.NEW_TAB_URL)) { mUrlTextView.setText(null); } else if (urlAsString.equals(Constant.WELCOME_MESSAGE_URL)) { mUrlTextView.setText(Constant.WELCOME_MESSAGE_DISPLAY_URL); } else { mUrlTextView.setText(urlAsString.replace("http://", "")); if (!showTitleUrl) { changeUrlTextColor(); } } } void showAllowLocationDialog(final String origin, final WebRenderer.GetGeolocationCallback callback) { LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); if (locationManager == null || locationManager.getAllProviders() == null || locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER) == false) { return; } String originCopy = origin.replace("http://", "").replace("https://", ""); String messageText = String.format(getResources().getString(R.string.requesting_location_message), originCopy); mRequestLocationTextView.setText(messageText); mRequestLocationContainer.setVisibility(View.VISIBLE); mRequestLocationShadow.setVisibility(View.VISIBLE); mRequestLocationYesButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { callback.onAllow(); hideAllowLocationDialog(); } }); } void hideAllowLocationDialog() { mRequestLocationContainer.setVisibility(View.GONE); mRequestLocationShadow.setVisibility(View.GONE); } int getArticleNotificationId() { return mArticleNotificationId; } }