@@ -340,6 +340,10 @@ public class NavigationURLBar extends FrameLayout {
             if (!hasProtocol && !urlText.contains(" ") && UrlUtils.isDomain(urlText)) {
                 urlText = "https://" + urlText;
                 hasProtocol = true;
+            } else if (!hasProtocol && !urlText.contains(" ") && UrlUtils.isIPUri(urlText)) {
+                String protocol = UrlUtils.isLocalIP(urlText) ? "http://" : "https://";
+                urlText = protocol + urlText;
+                hasProtocol = true;
             }
             if (hasProtocol) {
                 URL url = new URL(urlText);
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
 * 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 org.mozilla.vrbrowser.ui.views;

import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ObservableBoolean;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import org.mozilla.geckoview.GeckoSession;
import org.mozilla.vrbrowser.R;
import org.mozilla.vrbrowser.VRBrowserActivity;
import org.mozilla.vrbrowser.VRBrowserApplication;
import org.mozilla.vrbrowser.audio.AudioEngine;
import org.mozilla.vrbrowser.browser.BookmarksStore;
import org.mozilla.vrbrowser.browser.engine.Session;
import org.mozilla.vrbrowser.browser.engine.SessionStore;
import org.mozilla.vrbrowser.databinding.NavigationUrlBinding;
import org.mozilla.vrbrowser.search.SearchEngineWrapper;
import org.mozilla.vrbrowser.telemetry.GleanMetricsService;
import org.mozilla.vrbrowser.telemetry.TelemetryWrapper;
import org.mozilla.vrbrowser.ui.viewmodel.WindowViewModel;
import org.mozilla.vrbrowser.ui.widgets.UIWidget;
import org.mozilla.vrbrowser.ui.widgets.WindowWidget;
import org.mozilla.vrbrowser.ui.widgets.dialogs.SelectionActionWidget;
import org.mozilla.vrbrowser.utils.StringUtils;
import org.mozilla.vrbrowser.utils.SystemUtils;
import org.mozilla.vrbrowser.utils.UrlUtils;
import org.mozilla.vrbrowser.utils.ViewUtils;

import java.net.URI;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.Executor;

import kotlin.Unit;
import mozilla.components.browser.domains.autocomplete.DomainAutocompleteResult;
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider;
import mozilla.components.ui.autocomplete.InlineAutocompleteEditText;

public class NavigationURLBar extends FrameLayout {

    private static final String LOGTAG = SystemUtils.createLogtag(NavigationURLBar.class);

    private WindowViewModel mViewModel;
    private NavigationUrlBinding mBinding;
    private Animation mLoadingAnimation;
    private NavigationURLBarDelegate mDelegate;
    private ShippedDomainsProvider mAutocompleteProvider;
    private AudioEngine mAudio;
    private Executor mUIThreadExecutor;
    private Session mSession;
    private SelectionActionWidget mSelectionMenu;
    private boolean mWasFocusedWhenTouchBegan = false;
    private boolean mLongPressed = false;
    private int lastTouchDownOffset = 0;

    private Unit domainAutocompleteFilter(String text) {
        if (mBinding.urlEditText != null) {
            DomainAutocompleteResult result = mAutocompleteProvider.getAutocompleteSuggestion(text);
            if (result != null) {
                mBinding.urlEditText.applyAutocompleteResult(new InlineAutocompleteEditText.AutocompleteResult(
                        result.getText(),
                        result.getSource(),
                        result.getTotalItems(),
                        null));
            } else {
                mBinding.urlEditText.noAutocompleteResult();
            }
        }
        return Unit.INSTANCE;
    }

    public interface NavigationURLBarDelegate {
        void onVoiceSearchClicked();
        void onShowAwesomeBar();
        void onHideAwesomeBar();
        void onURLSelectionAction(EditText aURLEdit, float centerX, SelectionActionWidget actionMenu);
        void onPopUpButtonClicked();
    }

    public NavigationURLBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize(context);
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initialize(Context aContext) {
        mAudio = AudioEngine.fromContext(aContext);

        mUIThreadExecutor = ((VRBrowserApplication)getContext().getApplicationContext()).getExecutors().mainThread();

        mSession = SessionStore.get().getActiveSession();

        mLoadingAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.loading);

        // Layout setup
        mBinding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.navigation_url, this, true);
        mBinding.setLifecycleOwner((VRBrowserActivity)getContext());

        // Use Domain autocomplete provider from components
        mAutocompleteProvider = new ShippedDomainsProvider();
        mAutocompleteProvider.initialize(getContext());

        mBinding.urlEditText.clearFocus();
        mBinding.urlEditText.setShowSoftInputOnFocus(false);
        mBinding.urlEditText.setOnEditorActionListener((aTextView, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_SEARCH
                    || actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_SEND) {
                handleURLEdit(aTextView.getText().toString());
                return true;
            }
            return false;
        });

        mBinding.urlEditText.setOnFocusChangeListener((view, focused) -> {
            mViewModel.setIsFocused(focused);
            mViewModel.setIsUrlEmpty(mBinding.urlEditText.getText().toString().isEmpty());
            if (!focused) {
                hideSelectionMenu();
            } else {
                mBinding.urlEditText.selectAll();
            }
        });

        final GestureDetector gd = new GestureDetector(getContext(), new UrlGestureListener());
        gd.setOnDoubleTapListener(mUrlDoubleTapListener);
        mBinding.urlEditText.setOnTouchListener((view, motionEvent) -> {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                mWasFocusedWhenTouchBegan = view.isFocused();
                lastTouchDownOffset = ViewUtils.getCursorOffset(mBinding.urlEditText, motionEvent.getX());

            } else if (mLongPressed && motionEvent.getAction() == MotionEvent.ACTION_MOVE) {
                // Selection gesture while long pressing
                ViewUtils.placeSelection(mBinding.urlEditText, lastTouchDownOffset, ViewUtils.getCursorOffset(mBinding.urlEditText, motionEvent.getX()));

            } else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
                mLongPressed = false;
            }

            if (gd.onTouchEvent(motionEvent)) {
                return true;
            }

            if (mLongPressed) {
                // Do not scroll editable when selecting text after a long press.
                return true;
            }
            return view.onTouchEvent(motionEvent);
        });

        mBinding.urlEditText.setOnClickListener(v -> {
            if (mWasFocusedWhenTouchBegan) {
                hideSelectionMenu();
            }
        });

        mBinding.urlEditText.setOnLongClickListener(v -> {
            if (!v.isFocused()) {
                mBinding.urlEditText.requestFocus();
                mBinding.urlEditText.selectAll();

            } else if (!mBinding.urlEditText.hasSelection()) {
                // Place the cursor in the long pressed position.
                if (lastTouchDownOffset >= 0) {
                    mBinding.urlEditText.setSelection(lastTouchDownOffset);
                }
                mLongPressed = true;
            }

            // Add some delay so selection ranges are ready
            postDelayed(this::showSelectionMenu, 10);
            return true;
        });

        mBinding.urlEditText.addTextChangedListener(mURLTextWatcher);

        mBinding.urlEditText.setOnSelectionChangedCallback((start, end) -> {
            if (mSelectionMenu != null) {
                boolean hasCopy = mSelectionMenu.hasAction(GeckoSession.SelectionActionDelegate.ACTION_COPY);
                boolean showCopy = end > start;
                if (hasCopy != showCopy) {
                    showSelectionMenu();

                } else {
                    mDelegate.onURLSelectionAction(mBinding.urlEditText, getSelectionCenterX(), mSelectionMenu);
                    mSelectionMenu.updateWidget();
                }
            }
        });

        mBinding.urlEditText.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
            if (mLongPressed) {
                hideSelectionMenu();
            }
        });

        // Set a filter to provide domain autocomplete results
        mBinding.urlEditText.setOnFilterListener(this::domainAutocompleteFilter);

        mBinding.microphoneButton.setTag(R.string.view_id_tag, R.id.microphoneButton);
        mBinding.microphoneButton.setOnClickListener(mMicrophoneListener);

        mBinding.clearButton.setTag(R.string.view_id_tag, R.id.clearButton);
        mBinding.clearButton.setOnClickListener(mClearListener);

        mBinding.popup.setOnClickListener(mPopUpListener);

        // Bookmarks
        mBinding.bookmarkButton.setOnClickListener(v -> handleBookmarkClick());

        clearFocus();
    }

    public void detachFromWindow() {
        if (mViewModel != null) {
            mViewModel.getIsLoading().removeObserver(mIsLoadingObserver);
            mViewModel.getIsBookmarked().removeObserver(mIsBookmarkedObserver);
            mViewModel = null;
        }
    }

    public void attachToWindow(@NonNull WindowWidget aWindow) {
        mViewModel = new ViewModelProvider(
                (VRBrowserActivity)getContext(),
                ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication()))
                .get(String.valueOf(aWindow.hashCode()), WindowViewModel.class);

        mBinding.setViewmodel(mViewModel);

        mViewModel.getIsLoading().observe((VRBrowserActivity)getContext(), mIsLoadingObserver);
        mViewModel.getIsBookmarked().observe((VRBrowserActivity)getContext(), mIsBookmarkedObserver);
    }

    public void setSession(Session session) {
        mSession = session;
    }

    public void onPause() {
        if (mViewModel.getIsLoading().getValue().get()) {
            mBinding.loadingView.clearAnimation();
        }
    }

    public void onResume() {
        if (mViewModel.getIsLoading().getValue().get()) {
            mBinding.loadingView.startAnimation(mLoadingAnimation);
        }
    }

    public void setDelegate(NavigationURLBarDelegate delegate) {
        mDelegate = delegate;
    }

    private void handleBookmarkClick() {
        if (mAudio != null) {
            mAudio.playSound(AudioEngine.Sound.CLICK);
        }

        String url = mSession.getCurrentUri();
        if (StringUtils.isEmpty(url)) {
            return;
        }
        BookmarksStore bookmarkStore = SessionStore.get().getBookmarkStore();
        bookmarkStore.isBookmarked(url).thenAcceptAsync(bookmarked -> {
            if (!bookmarked) {
                bookmarkStore.addBookmark(url, mSession.getCurrentTitle());
                mViewModel.setIsBookmarked(true);

            } else {
                // Delete
                bookmarkStore.deleteBookmarkByURL(url);
                mViewModel.setIsBookmarked(false);
            }
        }, mUIThreadExecutor).exceptionally(throwable -> {
            Log.d(LOGTAG, "Error checking bookmark: " + throwable.getLocalizedMessage());
            throwable.printStackTrace();
            return null;
        });

    }

    private Observer<ObservableBoolean> mIsLoadingObserver = aBoolean -> {
        if (aBoolean.get()) {
            mBinding.loadingView.startAnimation(mLoadingAnimation);
        } else {
            mBinding.loadingView.clearAnimation();
        }
    };

    private Observer<ObservableBoolean> mIsBookmarkedObserver = aBoolean -> mBinding.bookmarkButton.clearFocus();

    public String getText() {
        return mBinding.urlEditText.getText().toString();
    }

    public String getOriginalText() {
        try {
            return mBinding.urlEditText.getOriginalText();

        } catch (IndexOutOfBoundsException e) {
            return mBinding.urlEditText.getNonAutocompleteText();
        }
    }

    public UIButton getPopUpButton() {
        return mBinding.popup;
    }

    public void handleURLEdit(String text) {
        text = text.trim();
        URI uri = null;
        try {
            boolean hasProtocol = text.contains("://");
            String urlText = text;
            // Detect when the protocol is missing from the URL.
            // Look for a separated '.' in the text with no white spaces.
            if (!hasProtocol && !urlText.contains(" ") && UrlUtils.isDomain(urlText)) {
                urlText = "https://" + urlText;
                hasProtocol = true;
            }
            if (hasProtocol) {
                URL url = new URL(urlText);
                uri = url.toURI();
            }
        }
        catch (Exception ex) {
        }

        String url;
        if (uri != null) {
            url = uri.toString();
            TelemetryWrapper.urlBarEvent(true);
            GleanMetricsService.urlBarEvent(true);
        } else if (text.startsWith("about:") || text.startsWith("resource://")) {
            url = text;
        } else {
            url = SearchEngineWrapper.get(getContext()).getSearchURL(text);

            // Doing search in the URL bar, so sending "aIsURL: false" to telemetry.
            TelemetryWrapper.urlBarEvent(false);
            GleanMetricsService.urlBarEvent(false);
        }

        mSession.loadUri(url);

        if (mDelegate != null) {
            mDelegate.onHideAwesomeBar();
        }

        clearFocus();
    }

    @Override
    public void setClickable(boolean clickable) {
        super.setClickable(clickable);
        mBinding.urlEditText.setEnabled(clickable);
    }

    private OnClickListener mMicrophoneListener = view -> {
        if (mAudio != null) {
            mAudio.playSound(AudioEngine.Sound.CLICK);
        }

        if (mDelegate != null) {
            mDelegate.onVoiceSearchClicked();
        }

        TelemetryWrapper.voiceInputEvent();
        GleanMetricsService.voiceInputEvent();
    };

    private OnClickListener mClearListener = view -> {
        if (mAudio != null) {
            mAudio.playSound(AudioEngine.Sound.CLICK);
        }

        mBinding.urlEditText.getText().clear();
    };

    private OnClickListener mPopUpListener = view -> {
        if (mAudio != null) {
            mAudio.playSound(AudioEngine.Sound.CLICK);
        }

        if (mDelegate != null) {
            mDelegate.onPopUpButtonClicked();
        }
    };

    private TextWatcher mURLTextWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            mViewModel.setIsUrlEmpty(mBinding.urlEditText.getText().toString().isEmpty());
        }

        @Override
        public void afterTextChanged(Editable editable) {
            if (mDelegate != null && mBinding.urlEditText.isFocused()) {
                mDelegate.onShowAwesomeBar();
            }
            hideSelectionMenu();
        }
    };

    private class UrlGestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDoubleTap(MotionEvent event) {
            return true;
        }
    }

    GestureDetector.OnDoubleTapListener mUrlDoubleTapListener = new GestureDetector.OnDoubleTapListener() {
        @Override
        public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent motionEvent) {
            mBinding.urlEditText.selectAll();
            showSelectionMenu();
            return true;
        }
    };

    private void showSelectionMenu() {
        Collection<String> actions = new HashSet<>();
        if (mBinding.urlEditText.getSelectionEnd() > mBinding.urlEditText.getSelectionStart()) {
            actions.add(GeckoSession.SelectionActionDelegate.ACTION_CUT);
            actions.add(GeckoSession.SelectionActionDelegate.ACTION_COPY);
        }
        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
        if (clipboard.hasPrimaryClip()) {
            actions.add(GeckoSession.SelectionActionDelegate.ACTION_PASTE);
        }
        if (!StringUtils.isEmpty(mBinding.urlEditText.getText().toString()) &&
                (mBinding.urlEditText.getSelectionStart() != 0 || mBinding.urlEditText.getSelectionEnd() != mBinding.urlEditText.getText().toString().length())) {
            actions.add(GeckoSession.SelectionActionDelegate.ACTION_SELECT_ALL);
        }

        if (actions.size() == 0) {
            hideSelectionMenu();
            return;
        }

        if (mSelectionMenu != null && !mSelectionMenu.hasSameActions(actions)) {
            // Release current selection menu to recreate it with different actions.
            hideSelectionMenu();
        }

        if (mSelectionMenu == null) {
            mSelectionMenu = new SelectionActionWidget(getContext());
            mSelectionMenu.setActions(actions);
            mSelectionMenu.setDelegate(new SelectionActionWidget.Delegate() {
                @Override
                public void onAction(String action) {
                    int startSelection = mBinding.urlEditText.getSelectionStart();
                    int endSelection = mBinding.urlEditText.getSelectionEnd();
                    boolean selectionValid = endSelection > startSelection;

                    if (action.equals(GeckoSession.SelectionActionDelegate.ACTION_CUT) && selectionValid) {
                        String selectedText = mBinding.urlEditText.getText().toString().substring(startSelection, endSelection);
                        clipboard.setPrimaryClip(ClipData.newPlainText("text", selectedText));
                        mBinding.urlEditText.setText(StringUtils.removeRange(mBinding.urlEditText.getText().toString(), startSelection, endSelection));
                        mBinding.urlEditText.setSelection(startSelection);

                    } else if (action.equals(GeckoSession.SelectionActionDelegate.ACTION_COPY) && selectionValid) {
                        String selectedText = mBinding.urlEditText.getText().toString().substring(startSelection, endSelection);
                        clipboard.setPrimaryClip(ClipData.newPlainText("text", selectedText));
                        mBinding.urlEditText.setSelection(endSelection);
                    } else if (action.equals(GeckoSession.SelectionActionDelegate.ACTION_PASTE) && clipboard.hasPrimaryClip()) {
                        ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
                        if (selectionValid) {
                            mBinding.urlEditText.setText(StringUtils.removeRange(mBinding.urlEditText.getText().toString(), startSelection, endSelection));
                            mBinding.urlEditText.setSelection(startSelection);
                        }
                        if (item != null && item.getText() != null) {
                            mBinding.urlEditText.getText().insert(mBinding.urlEditText.getSelectionStart(), item.getText());
                        } else if (item != null && item.getUri() != null) {
                            mBinding.urlEditText.getText().insert(mBinding.urlEditText.getSelectionStart(), item.getUri().toString());
                        }
                    } else if (action.equals(GeckoSession.SelectionActionDelegate.ACTION_SELECT_ALL)) {
                        mBinding.urlEditText.selectAll();
                        showSelectionMenu();
                        return;

                    }
                    hideSelectionMenu();
                }

                @Override
                public void onDismiss() {
                    hideSelectionMenu();
                }
            });
        }

        if (mDelegate != null) {
            mDelegate.onURLSelectionAction(mBinding.urlEditText, getSelectionCenterX(), mSelectionMenu);
        }

        mSelectionMenu.show(UIWidget.KEEP_FOCUS);
    }


    private float getSelectionCenterX() {
        float start = 0;
        if (mBinding.urlEditText.getSelectionStart() >= 0) {
            start = ViewUtils.GetLetterPositionX(mBinding.urlEditText, mBinding.urlEditText.getSelectionStart(), true);
        }
        float end = start;
        if (mBinding.urlEditText.getSelectionEnd() > mBinding.urlEditText.getSelectionStart()) {
            end = ViewUtils.GetLetterPositionX(mBinding.urlEditText, mBinding.urlEditText.getSelectionEnd(), true);
        }
        if (end < start) {
            end = start;
        }
        return start + (end - start) * 0.5f;
    }

    private void hideSelectionMenu() {
        if (mSelectionMenu != null) {
            mSelectionMenu.setDelegate((SelectionActionWidget.Delegate) null);
            mSelectionMenu.hide(UIWidget.REMOVE_WIDGET);
            mSelectionMenu.releaseWidget();
            mSelectionMenu = null;
        }
    }

}

I was thinking about ways we could degrade from https:// to http:// in FxR so we always try secure first.