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