SearchPanelView.java revision e20a177d3f147f3011647c3bdab401f90b2c5d1d
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui;
18
19import android.animation.LayoutTransition;
20import android.app.ActivityOptions;
21import android.app.SearchManager;
22import android.content.ActivityNotFoundException;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.res.Resources;
27import android.os.UserHandle;
28import android.os.Vibrator;
29import android.provider.Settings;
30import android.util.AttributeSet;
31import android.util.Slog;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.ViewTreeObserver;
36import android.view.ViewTreeObserver.OnPreDrawListener;
37import android.widget.FrameLayout;
38
39import com.android.internal.widget.multiwaveview.GlowPadView;
40import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
41import com.android.systemui.R;
42import com.android.systemui.recent.StatusBarTouchProxy;
43import com.android.systemui.statusbar.BaseStatusBar;
44import com.android.systemui.statusbar.CommandQueue;
45import com.android.systemui.statusbar.phone.PhoneStatusBar;
46import com.android.systemui.statusbar.tablet.StatusBarPanel;
47import com.android.systemui.statusbar.tablet.TabletStatusBar;
48
49public class SearchPanelView extends FrameLayout implements
50        StatusBarPanel, ActivityOptions.OnAnimationStartedListener {
51    private static final int SEARCH_PANEL_HOLD_DURATION = 0;
52    static final String TAG = "SearchPanelView";
53    static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
54    private static final String ASSIST_ICON_METADATA_NAME =
55            "com.android.systemui.action_assist_icon";
56    private final Context mContext;
57    private BaseStatusBar mBar;
58    private StatusBarTouchProxy mStatusBarTouchProxy;
59
60    private boolean mShowing;
61    private View mSearchTargetsContainer;
62    private GlowPadView mGlowPadView;
63
64    public SearchPanelView(Context context, AttributeSet attrs) {
65        this(context, attrs, 0);
66    }
67
68    public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
69        super(context, attrs, defStyle);
70        mContext = context;
71    }
72
73    private void startAssistActivity() {
74        // Close Recent Apps if needed
75        mBar.animateCollapseNotifications(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);
76        // Launch Assist
77        Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
78                .getAssistIntent(mContext, UserHandle.USER_CURRENT);
79        if (intent == null) return;
80        try {
81            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
82                    R.anim.search_launch_enter, R.anim.search_launch_exit,
83                    getHandler(), this);
84            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
85            mContext.startActivityAsUser(intent, opts.toBundle(),
86                    new UserHandle(UserHandle.USER_CURRENT));
87        } catch (ActivityNotFoundException e) {
88            Slog.w(TAG, "Activity not found for " + intent.getAction());
89            onAnimationStarted();
90        }
91    }
92
93    class GlowPadTriggerListener implements GlowPadView.OnTriggerListener {
94        boolean mWaitingForLaunch;
95
96        public void onGrabbed(View v, int handle) {
97        }
98
99        public void onReleased(View v, int handle) {
100        }
101
102        public void onGrabbedStateChange(View v, int handle) {
103            if (!mWaitingForLaunch && OnTriggerListener.NO_HANDLE == handle) {
104                mBar.hideSearchPanel();
105            }
106        }
107
108        public void onTrigger(View v, final int target) {
109            final int resId = mGlowPadView.getResourceIdForTarget(target);
110            switch (resId) {
111                case com.android.internal.R.drawable.ic_action_assist_generic:
112                    mWaitingForLaunch = true;
113                    startAssistActivity();
114                    vibrate();
115                    break;
116            }
117        }
118
119        public void onFinishFinalAnimation() {
120        }
121    }
122    final GlowPadTriggerListener mGlowPadViewListener = new GlowPadTriggerListener();
123
124    @Override
125    public void onAnimationStarted() {
126        postDelayed(new Runnable() {
127            public void run() {
128                mGlowPadViewListener.mWaitingForLaunch = false;
129                mBar.hideSearchPanel();
130            }
131        }, SEARCH_PANEL_HOLD_DURATION);
132    }
133
134    @Override
135    protected void onFinishInflate() {
136        super.onFinishInflate();
137        mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
138        mSearchTargetsContainer = findViewById(R.id.search_panel_container);
139        mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy);
140        // TODO: fetch views
141        mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
142        mGlowPadView.setOnTriggerListener(mGlowPadViewListener);
143    }
144
145    private void maybeSwapSearchIcon() {
146        Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
147                .getAssistIntent(mContext, UserHandle.USER_CURRENT);
148        if (intent != null) {
149            ComponentName component = intent.getComponent();
150            if (component == null || !mGlowPadView.replaceTargetDrawablesIfPresent(component,
151                    ASSIST_ICON_METADATA_NAME,
152                    com.android.internal.R.drawable.ic_action_assist_generic)) {
153                if (DEBUG) Slog.v(TAG, "Couldn't grab icon for component " + component);
154            }
155        }
156    }
157
158    private boolean pointInside(int x, int y, View v) {
159        final int l = v.getLeft();
160        final int r = v.getRight();
161        final int t = v.getTop();
162        final int b = v.getBottom();
163        return x >= l && x < r && y >= t && y < b;
164    }
165
166    public boolean isInContentArea(int x, int y) {
167        if (pointInside(x, y, mSearchTargetsContainer)) {
168            return true;
169        } else if (mStatusBarTouchProxy != null &&
170                pointInside(x, y, mStatusBarTouchProxy)) {
171            return true;
172        } else {
173            return false;
174        }
175    }
176
177    private final OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
178        public boolean onPreDraw() {
179            getViewTreeObserver().removeOnPreDrawListener(this);
180            mGlowPadView.resumeAnimations();
181            return false;
182        }
183    };
184
185    private void vibrate() {
186        Context context = getContext();
187        if (Settings.System.getInt(context.getContentResolver(),
188                Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) != 0) {
189            Resources res = context.getResources();
190            Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
191            vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration));
192        }
193    }
194
195    public void show(final boolean show, boolean animate) {
196        if (!show) {
197            final LayoutTransition transitioner = animate ? createLayoutTransitioner() : null;
198            ((ViewGroup) mSearchTargetsContainer).setLayoutTransition(transitioner);
199        }
200        mShowing = show;
201        if (show) {
202            maybeSwapSearchIcon();
203            if (getVisibility() != View.VISIBLE) {
204                setVisibility(View.VISIBLE);
205                // Don't start the animation until we've created the layer, which is done
206                // right before we are drawn
207                mGlowPadView.suspendAnimations();
208                mGlowPadView.ping();
209                getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
210                vibrate();
211            }
212            setFocusable(true);
213            setFocusableInTouchMode(true);
214            requestFocus();
215        } else {
216            setVisibility(View.INVISIBLE);
217        }
218    }
219
220    public void hide(boolean animate) {
221        if (mBar != null) {
222            // This will indirectly cause show(false, ...) to get called
223            mBar.animateCollapseNotifications(CommandQueue.FLAG_EXCLUDE_NONE);
224        } else {
225            setVisibility(View.INVISIBLE);
226        }
227    }
228
229    /**
230     * We need to be aligned at the bottom.  LinearLayout can't do this, so instead,
231     * let LinearLayout do all the hard work, and then shift everything down to the bottom.
232     */
233    @Override
234    protected void onLayout(boolean changed, int l, int t, int r, int b) {
235        super.onLayout(changed, l, t, r, b);
236        // setPanelHeight(mSearchTargetsContainer.getHeight());
237    }
238
239    @Override
240    public boolean dispatchHoverEvent(MotionEvent event) {
241        // Ignore hover events outside of this panel bounds since such events
242        // generate spurious accessibility events with the panel content when
243        // tapping outside of it, thus confusing the user.
244        final int x = (int) event.getX();
245        final int y = (int) event.getY();
246        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
247            return super.dispatchHoverEvent(event);
248        }
249        return true;
250    }
251
252    /**
253     * Whether the panel is showing, or, if it's animating, whether it will be
254     * when the animation is done.
255     */
256    public boolean isShowing() {
257        return mShowing;
258    }
259
260    public void setBar(BaseStatusBar bar) {
261        mBar = bar;
262    }
263
264    public void setStatusBarView(final View statusBarView) {
265        if (mStatusBarTouchProxy != null) {
266            mStatusBarTouchProxy.setStatusBar(statusBarView);
267//            mGlowPadView.setOnTouchListener(new OnTouchListener() {
268//                public boolean onTouch(View v, MotionEvent event) {
269//                    return statusBarView.onTouchEvent(event);
270//                }
271//            });
272        }
273    }
274
275    private LayoutTransition createLayoutTransitioner() {
276        LayoutTransition transitioner = new LayoutTransition();
277        transitioner.setDuration(200);
278        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
279        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
280        return transitioner;
281    }
282
283    public boolean isAssistantAvailable() {
284        return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
285                .getAssistIntent(mContext, UserHandle.USER_CURRENT) != null;
286    }
287}
288