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