FileDocCategorySizeDatePackage
SearchPanelView.javaAPI DocAndroid 5.1 API12684Thu Mar 12 22:22:42 GMT 2015com.android.systemui

SearchPanelView.java

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui;

import android.app.ActivityOptions;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarPanel;
import com.android.systemui.statusbar.phone.PhoneStatusBar;

public class SearchPanelView extends FrameLayout implements StatusBarPanel {

    private static final String TAG = "SearchPanelView";
    private static final String ASSIST_ICON_METADATA_NAME =
            "com.android.systemui.action_assist_icon";

    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
            .build();

    private final Context mContext;
    private BaseStatusBar mBar;

    private SearchPanelCircleView mCircle;
    private ImageView mLogo;
    private View mScrim;

    private int mThreshold;
    private boolean mHorizontal;

    private boolean mLaunching;
    private boolean mDragging;
    private boolean mDraggedFarEnough;
    private float mStartTouch;
    private float mStartDrag;
    private boolean mLaunchPending;

    public SearchPanelView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
    }

    private void startAssistActivity() {
        if (!mBar.isDeviceProvisioned()) return;

        // Close Recent Apps if needed
        mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);

        final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
                .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
        if (intent == null) return;

        try {
            final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
                    R.anim.search_launch_enter, R.anim.search_launch_exit);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            AsyncTask.execute(new Runnable() {
                @Override
                public void run() {
                    mContext.startActivityAsUser(intent, opts.toBundle(),
                            new UserHandle(UserHandle.USER_CURRENT));
                }
            });
        } catch (ActivityNotFoundException e) {
            Log.w(TAG, "Activity not found for " + intent.getAction());
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
        mLogo = (ImageView) findViewById(R.id.search_logo);
        mScrim = findViewById(R.id.search_panel_scrim);
    }

    private void maybeSwapSearchIcon() {
        Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
                .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
        if (intent != null) {
            ComponentName component = intent.getComponent();
            replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME);
        } else {
            mLogo.setImageDrawable(null);
        }
    }

    public void replaceDrawable(ImageView v, ComponentName component, String name) {
        if (component != null) {
            try {
                PackageManager packageManager = mContext.getPackageManager();
                // Look for the search icon specified in the activity meta-data
                Bundle metaData = packageManager.getActivityInfo(
                        component, PackageManager.GET_META_DATA).metaData;
                if (metaData != null) {
                    int iconResId = metaData.getInt(name);
                    if (iconResId != 0) {
                        Resources res = packageManager.getResourcesForActivity(component);
                        v.setImageDrawable(res.getDrawable(iconResId));
                        return;
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                Log.w(TAG, "Failed to swap drawable; "
                        + component.flattenToShortString() + " not found", e);
            } catch (Resources.NotFoundException nfe) {
                Log.w(TAG, "Failed to swap drawable from "
                        + component.flattenToShortString(), nfe);
            }
        }
        v.setImageDrawable(null);
    }

    @Override
    public boolean isInContentArea(int x, int y) {
        return true;
    }

    private void vibrate() {
        Context context = getContext();
        if (Settings.System.getIntForUser(context.getContentResolver(),
                Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) {
            Resources res = context.getResources();
            Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
            vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration),
                    VIBRATION_ATTRIBUTES);
        }
    }

    public void show(final boolean show, boolean animate) {
        if (show) {
            maybeSwapSearchIcon();
            if (getVisibility() != View.VISIBLE) {
                setVisibility(View.VISIBLE);
                vibrate();
                if (animate) {
                    startEnterAnimation();
                } else {
                    mScrim.setAlpha(1f);
                }
            }
            setFocusable(true);
            setFocusableInTouchMode(true);
            requestFocus();
        } else {
            if (animate) {
                startAbortAnimation();
            } else {
                setVisibility(View.INVISIBLE);
            }
        }
    }

    private void startEnterAnimation() {
        mCircle.startEnterAnimation();
        mScrim.setAlpha(0f);
        mScrim.animate()
                .alpha(1f)
                .setDuration(300)
                .setStartDelay(50)
                .setInterpolator(PhoneStatusBar.ALPHA_IN)
                .start();

    }

    private void startAbortAnimation() {
        mCircle.startAbortAnimation(new Runnable() {
                    @Override
                    public void run() {
                        mCircle.setAnimatingOut(false);
                        setVisibility(View.INVISIBLE);
                    }
                });
        mCircle.setAnimatingOut(true);
        mScrim.animate()
                .alpha(0f)
                .setDuration(300)
                .setStartDelay(0)
                .setInterpolator(PhoneStatusBar.ALPHA_OUT);
    }

    public void hide(boolean animate) {
        if (mBar != null) {
            // This will indirectly cause show(false, ...) to get called
            mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
        } else {
            if (animate) {
                startAbortAnimation();
            } else {
                setVisibility(View.INVISIBLE);
            }
        }
    }

    @Override
    public boolean dispatchHoverEvent(MotionEvent event) {
        // Ignore hover events outside of this panel bounds since such events
        // generate spurious accessibility events with the panel content when
        // tapping outside of it, thus confusing the user.
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
            return super.dispatchHoverEvent(event);
        }
        return true;
    }

    /**
     * Whether the panel is showing, or, if it's animating, whether it will be
     * when the animation is done.
     */
    public boolean isShowing() {
        return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut();
    }

    public void setBar(BaseStatusBar bar) {
        mBar = bar;
    }

    public boolean isAssistantAvailable() {
        return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
                .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mLaunching || mLaunchPending) {
            return false;
        }
        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mStartTouch = mHorizontal ? event.getX() : event.getY();
                mDragging = false;
                mDraggedFarEnough = false;
                mCircle.reset();
                break;
            case MotionEvent.ACTION_MOVE:
                float currentTouch = mHorizontal ? event.getX() : event.getY();
                if (getVisibility() == View.VISIBLE && !mDragging &&
                        (!mCircle.isAnimationRunning(true /* enterAnimation */)
                                || Math.abs(mStartTouch - currentTouch) > mThreshold)) {
                    mStartDrag = currentTouch;
                    mDragging = true;
                }
                if (mDragging) {
                    float offset = Math.max(mStartDrag - currentTouch, 0.0f);
                    mCircle.setDragDistance(offset);
                    mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold;
                    mCircle.setDraggedFarEnough(mDraggedFarEnough);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mDraggedFarEnough) {
                    if (mCircle.isAnimationRunning(true  /* enterAnimation */)) {
                        mLaunchPending = true;
                        mCircle.setAnimatingOut(true);
                        mCircle.performOnAnimationFinished(new Runnable() {
                            @Override
                            public void run() {
                                startExitAnimation();
                            }
                        });
                    } else {
                        startExitAnimation();
                    }
                } else {
                    startAbortAnimation();
                }
                break;
        }
        return true;
    }

    private void startExitAnimation() {
        mLaunchPending = false;
        if (mLaunching || getVisibility() != View.VISIBLE) {
            return;
        }
        mLaunching = true;
        startAssistActivity();
        vibrate();
        mCircle.setAnimatingOut(true);
        mCircle.startExitAnimation(new Runnable() {
                    @Override
                    public void run() {
                        mLaunching = false;
                        mCircle.setAnimatingOut(false);
                        setVisibility(View.INVISIBLE);
                    }
                });
        mScrim.animate()
                .alpha(0f)
                .setDuration(300)
                .setStartDelay(0)
                .setInterpolator(PhoneStatusBar.ALPHA_OUT);
    }

    public void setHorizontal(boolean horizontal) {
        mHorizontal = horizontal;
        mCircle.setHorizontal(horizontal);
    }
}