org.mozilla.focus.fragment.UrlInputFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.mozilla.focus.fragment.UrlInputFragment.java

Source

/* -*- 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.focus.fragment;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;

import org.mozilla.focus.R;
import org.mozilla.focus.autocomplete.UrlAutoCompleteFilter;
import org.mozilla.focus.telemetry.TelemetryWrapper;
import org.mozilla.focus.utils.UrlUtils;
import org.mozilla.focus.utils.ViewUtils;
import org.mozilla.focus.widget.HintFrameLayout;
import org.mozilla.focus.widget.InlineAutocompleteEditText;

/**
 * Fragment for displaying he URL input controls.
 */
public class UrlInputFragment extends Fragment implements View.OnClickListener,
        InlineAutocompleteEditText.OnCommitListener, InlineAutocompleteEditText.OnFilterListener {
    public static final String FRAGMENT_TAG = "url_input";

    private static final String ARGUMENT_URL = "url";
    private static final String ARGUMENT_ANIMATION = "animation";
    private static final String ARGUMENT_X = "x";
    private static final String ARGUMENT_Y = "y";
    private static final String ARGUMENT_WIDTH = "width";
    private static final String ARGUMENT_HEIGHT = "height";

    private static final String ANIMATION_HOME_SCREEN = "home_screen";
    private static final String ANIMATION_BROWSER_SCREEN = "browser_screen";

    private static final int ANIMATION_DURATION = 200;

    /**
     * Create a new UrlInputFragment and animate the url input view from the position/size of the
     * fake url bar view.
     */
    public static UrlInputFragment createWithHomeScreenAnimation(View fakeUrlBarView) {
        int[] screenLocation = new int[2];
        fakeUrlBarView.getLocationOnScreen(screenLocation);

        Bundle arguments = new Bundle();
        arguments.putString(ARGUMENT_ANIMATION, ANIMATION_HOME_SCREEN);
        arguments.putInt(ARGUMENT_X, screenLocation[0]);
        arguments.putInt(ARGUMENT_Y, screenLocation[1]);
        arguments.putInt(ARGUMENT_WIDTH, fakeUrlBarView.getWidth());
        arguments.putInt(ARGUMENT_HEIGHT, fakeUrlBarView.getHeight());

        UrlInputFragment fragment = new UrlInputFragment();
        fragment.setArguments(arguments);

        return fragment;
    }

    /**
     * Create a new UrlInputFragment and animate the url input view from the position/size of the
     * browser toolbar's URL view.
     */
    public static UrlInputFragment createWithBrowserScreenAnimation(String url, View urlView) {
        final Bundle arguments = new Bundle();
        arguments.putString(ARGUMENT_ANIMATION, ANIMATION_BROWSER_SCREEN);
        arguments.putString(ARGUMENT_URL, url);

        int[] screenLocation = new int[2];
        urlView.getLocationOnScreen(screenLocation);

        arguments.putInt(ARGUMENT_X, screenLocation[0]);
        arguments.putInt(ARGUMENT_Y, screenLocation[1]);
        arguments.putInt(ARGUMENT_WIDTH, urlView.getWidth());
        arguments.putInt(ARGUMENT_HEIGHT, urlView.getHeight());

        final UrlInputFragment fragment = new UrlInputFragment();
        fragment.setArguments(arguments);

        return fragment;
    }

    private InlineAutocompleteEditText urlView;
    private View clearView;
    private View searchViewContainer;
    private TextView searchView;

    private UrlAutoCompleteFilter urlAutoCompleteFilter;
    private View dismissView;
    private HintFrameLayout urlInputContainerView;
    private View urlInputBackgroundView;
    private View toolbarBackgroundView;

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.fragment_urlinput, container, false);

        dismissView = view.findViewById(R.id.dismiss);
        dismissView.setOnClickListener(this);

        clearView = view.findViewById(R.id.clear);
        clearView.setOnClickListener(this);

        searchViewContainer = view.findViewById(R.id.search_hint_container);

        searchView = (TextView) view.findViewById(R.id.search_hint);
        searchView.setOnClickListener(this);

        urlAutoCompleteFilter = new UrlAutoCompleteFilter();
        urlAutoCompleteFilter.loadDomainsInBackground(getContext().getApplicationContext());

        urlView = (InlineAutocompleteEditText) view.findViewById(R.id.url_edit);
        urlView.setOnFilterListener(this);
        urlView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    ViewUtils.showKeyboard(urlView);
                }
            }
        });

        toolbarBackgroundView = view.findViewById(R.id.toolbar_background);
        urlInputBackgroundView = view.findViewById(R.id.url_input_background);

        urlInputContainerView = (HintFrameLayout) view.findViewById(R.id.url_input_container);
        urlInputContainerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                urlInputContainerView.getViewTreeObserver().removeOnPreDrawListener(this);

                animateFirstDraw();

                return true;
            }
        });

        urlView.setOnCommitListener(this);

        if (getArguments().containsKey(ARGUMENT_URL)) {
            urlView.setText(getArguments().getString(ARGUMENT_URL));
            clearView.setVisibility(View.VISIBLE);
        }

        return view;
    }

    public boolean onBackPressed() {
        animateAndDismiss();
        return true;
    }

    @Override
    public void onStart() {
        super.onStart();

        urlView.requestFocus();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
        case R.id.clear:
            urlView.setText("");
            urlView.requestFocus();
            break;

        case R.id.search_hint:
            onSearch();
            break;

        case R.id.dismiss:
            animateAndDismiss();
            break;

        default:
            throw new IllegalStateException("Unhandled view in onClick()");
        }
    }

    private void animateFirstDraw() {
        final String animation = getArguments().getString(ARGUMENT_ANIMATION);

        if (ANIMATION_HOME_SCREEN.equals(animation)) {
            playHomeScreenAnimation(false);
        } else if (ANIMATION_BROWSER_SCREEN.equals(animation)) {
            playBrowserScreenAnimation(false);
        }
    }

    private void animateAndDismiss() {
        // Don't allow any more clicks: dismissView is still visible until the animation ends,
        // but we don't want to restart animations and/or trigger hiding again (which could potentially
        // cause crashes since we don't know what state we're in). Ignoring further clicks is the simplest
        // solution, since dismissView is about to disappear anyway.
        dismissView.setClickable(false);

        final String animation = getArguments().getString(ARGUMENT_ANIMATION);

        if (ANIMATION_HOME_SCREEN.equals(animation)) {
            playHomeScreenAnimation(true);
        } else if (ANIMATION_BROWSER_SCREEN.equals(animation)) {
            playBrowserScreenAnimation(true);
        } else {
            dismiss();
        }
    }

    /**
     * Play animation between home screen and the URL input.
     */
    private void playHomeScreenAnimation(final boolean reverse) {
        int[] screenLocation = new int[2];
        urlInputContainerView.getLocationOnScreen(screenLocation);

        int leftDelta = getArguments().getInt(ARGUMENT_X) - screenLocation[0];
        int topDelta = getArguments().getInt(ARGUMENT_Y) - screenLocation[1];

        float widthScale = (float) getArguments().getInt(ARGUMENT_WIDTH) / urlInputContainerView.getWidth();
        float heightScale = (float) getArguments().getInt(ARGUMENT_HEIGHT) / urlInputContainerView.getHeight();

        if (!reverse) {
            // Move all views to the position of the fake URL bar on the home screen. Hide them until
            // the animation starts because we need to switch between fake URL bar and the actual URL
            // bar once the animation starts.
            urlInputContainerView.setAlpha(0);
            urlInputContainerView.setPivotX(0);
            urlInputContainerView.setPivotY(0);
            urlInputContainerView.setScaleX(widthScale);
            urlInputContainerView.setScaleY(heightScale);
            urlInputContainerView.setTranslationX(leftDelta);
            urlInputContainerView.setTranslationY(topDelta);
            urlInputContainerView.setAnimationOffset(1.0f);

            toolbarBackgroundView.setAlpha(0);

            dismissView.setAlpha(0);
        }

        // Move the URL bar from its position on the home screen to the actual position (and scale it).
        urlInputContainerView.animate().setDuration(ANIMATION_DURATION).scaleX(reverse ? widthScale : 1)
                .scaleY(reverse ? heightScale : 1).translationX(reverse ? leftDelta : 0)
                .translationY(reverse ? topDelta : 0).setInterpolator(new DecelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        ViewUtils.updateAlphaIfViewExists(getActivity(), R.id.fake_urlbar, 0f);

                        urlInputContainerView.setAlpha(1);

                        if (reverse) {
                            urlView.setText("");
                            urlView.setCursorVisible(false);
                            urlView.clearFocus();
                        }
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (reverse) {
                            urlInputContainerView.setAlpha(0f);

                            ViewUtils.updateAlphaIfViewExists(getActivity(), R.id.fake_urlbar, 1f);

                            dismiss();
                        } else {
                            urlView.setCursorVisible(true);
                        }
                    }
                });

        final ObjectAnimator hintAnimator = ObjectAnimator.ofFloat(urlInputContainerView, "animationOffset",
                reverse ? 0f : 1f, reverse ? 1f : 0f);

        hintAnimator.setDuration(ANIMATION_DURATION);
        hintAnimator.start();

        // Let the toolbar background come int from the top
        toolbarBackgroundView.animate().alpha(reverse ? 0 : 1).setDuration(ANIMATION_DURATION)
                .setInterpolator(new DecelerateInterpolator());

        // Use an alpha animation on the transparent black background
        dismissView.animate().alpha(reverse ? 0 : 1).setDuration(ANIMATION_DURATION);
    }

    private void playBrowserScreenAnimation(final boolean reverse) {
        {
            float containerMargin = ((FrameLayout.LayoutParams) urlInputContainerView
                    .getLayoutParams()).bottomMargin;

            float width = urlInputBackgroundView.getWidth();
            float height = urlInputBackgroundView.getHeight();

            float widthScale = (width + (2 * containerMargin)) / width;
            float heightScale = (height + (2 * containerMargin)) / height;

            if (!reverse) {
                urlInputBackgroundView.setPivotX(0);
                urlInputBackgroundView.setPivotY(0);
                urlInputBackgroundView.setScaleX(widthScale);
                urlInputBackgroundView.setScaleY(heightScale);
                urlInputBackgroundView.setTranslationX(-containerMargin);
                urlInputBackgroundView.setTranslationY(-containerMargin);
                urlInputContainerView.setAnimationOffset(0f);

                clearView.setAlpha(0);
            }

            // Let the URL input use the full width/height and then shrink to the actual size
            urlInputBackgroundView.animate().setDuration(ANIMATION_DURATION).scaleX(reverse ? widthScale : 1)
                    .scaleY(reverse ? heightScale : 1).alpha(reverse ? 0 : 1)
                    .translationX(reverse ? -containerMargin : 0).translationY(reverse ? -containerMargin : 0)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationStart(Animator animation) {
                            if (reverse) {
                                clearView.setAlpha(0);
                            }
                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            if (reverse) {
                                dismiss();
                            } else {
                                clearView.setAlpha(1);
                            }
                        }
                    });
        }
        {
            int[] screenLocation = new int[2];
            urlView.getLocationOnScreen(screenLocation);

            int leftDelta = getArguments().getInt(ARGUMENT_X) - screenLocation[0] - urlView.getPaddingLeft();

            if (!reverse) {
                urlView.setPivotX(0);
                urlView.setPivotY(0);
                urlView.setTranslationX(leftDelta);
            }

            // The URL moves from the right (at least if the lock is visible) to it's actual position
            urlView.animate().setDuration(ANIMATION_DURATION).translationX(reverse ? leftDelta : 0);
        }

        if (!reverse) {
            toolbarBackgroundView.setAlpha(0);
            clearView.setAlpha(0);
        }

        // The darker background appears with an alpha animation
        toolbarBackgroundView.animate().setDuration(ANIMATION_DURATION).alpha(reverse ? 0 : 1);
    }

    private void dismiss() {
        getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
    }

    @Override
    public void onCommit() {
        ViewUtils.hideKeyboard(urlView);

        final String rawUrl = urlView.getText().toString();

        final boolean isUrl = UrlUtils.isUrl(rawUrl);

        final String url = isUrl ? UrlUtils.normalize(rawUrl) : UrlUtils.createSearchUrl(getContext(), rawUrl);

        openUrl(url);

        TelemetryWrapper.urlBarEvent(isUrl);
    }

    private void onSearch() {
        final String searchUrl = UrlUtils.createSearchUrl(getContext(), urlView.getOriginalText());

        openUrl(searchUrl);

        TelemetryWrapper.searchSelectEvent();
    }

    private void openUrl(String url) {
        final FragmentManager fragmentManager = getActivity().getSupportFragmentManager();

        // Replace all fragments with a fresh browser fragment. This means we either remove the
        // HomeFragment with an UrlInputFragment on top or an old BrowserFragment with an
        // UrlInputFragment.
        final BrowserFragment browserFragment = (BrowserFragment) fragmentManager
                .findFragmentByTag(BrowserFragment.FRAGMENT_TAG);

        if (browserFragment != null && browserFragment.isVisible()) {
            // Reuse existing visible fragment - in this case we know the user is already browsing.
            // The fragment might exist if we "erased" a browsing session, hence we need to check
            // for visibility in addition to existence.
            browserFragment.loadUrl(url);

            // And this fragment can be removed again.
            fragmentManager.beginTransaction().remove(this).commit();
        } else {
            fragmentManager.beginTransaction()
                    .replace(R.id.container, BrowserFragment.create(url), BrowserFragment.FRAGMENT_TAG).commit();
        }
    }

    @Override
    public void onFilter(String searchText, InlineAutocompleteEditText view) {
        // If the UrlInputFragment has already been hidden, don't bother with filtering. Because of the text
        // input architecture on Android it's possible for onFilter() to be called after we've already
        // hidden the Fragment, see the relevant bug for more background:
        // https://github.com/mozilla-mobile/focus-android/issues/441#issuecomment-293691141
        if (!isVisible()) {
            return;
        }

        urlAutoCompleteFilter.onFilter(searchText, view);

        if (searchText.length() == 0) {
            clearView.setVisibility(View.GONE);
            searchViewContainer.setVisibility(View.GONE);
        } else {
            clearView.setVisibility(View.VISIBLE);

            final String hint = getString(R.string.search_hint, searchText);

            final SpannableString content = new SpannableString(hint);
            content.setSpan(new StyleSpan(Typeface.BOLD), hint.length() - searchText.length(), hint.length(), 0);

            searchView.setText(content);
            searchViewContainer.setVisibility(View.VISIBLE);
        }
    }
}