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