• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.systemui;
18 
19 import android.animation.LayoutTransition;
20 import android.app.ActivityManagerNative;
21 import android.app.ActivityOptions;
22 import android.app.SearchManager;
23 import android.content.ActivityNotFoundException;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Resources;
28 import android.os.RemoteException;
29 import android.os.ServiceManager;
30 import android.os.UserHandle;
31 import android.os.Vibrator;
32 import android.provider.Settings;
33 import android.util.AttributeSet;
34 import android.util.EventLog;
35 import android.util.Log;
36 import android.view.IWindowManager;
37 import android.view.MotionEvent;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.ViewTreeObserver;
41 import android.view.ViewTreeObserver.OnPreDrawListener;
42 import android.widget.FrameLayout;
43 
44 import com.android.internal.widget.multiwaveview.GlowPadView;
45 import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
46 import com.android.systemui.statusbar.BaseStatusBar;
47 import com.android.systemui.statusbar.CommandQueue;
48 import com.android.systemui.statusbar.StatusBarPanel;
49 import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
50 import com.android.systemui.statusbar.phone.PhoneStatusBar;
51 
52 public 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 
SearchPanelView(Context context, AttributeSet attrs)68     public SearchPanelView(Context context, AttributeSet attrs) {
69         this(context, attrs, 0);
70     }
71 
SearchPanelView(Context context, AttributeSet attrs, int defStyle)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 
startAssistActivity()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 
onGrabbed(View v, int handle)123         public void onGrabbed(View v, int handle) {
124         }
125 
onReleased(View v, int handle)126         public void onReleased(View v, int handle) {
127         }
128 
onGrabbedStateChange(View v, int handle)129         public void onGrabbedStateChange(View v, int handle) {
130             if (!mWaitingForLaunch && OnTriggerListener.NO_HANDLE == handle) {
131                 mBar.hideSearchPanel();
132             }
133         }
134 
onTrigger(View v, final int target)135         public void onTrigger(View v, final int target) {
136             final int resId = mGlowPadView.getResourceIdForTarget(target);
137             switch (resId) {
138                 case com.android.internal.R.drawable.ic_action_assist_generic:
139                     mWaitingForLaunch = true;
140                     startAssistActivity();
141                     vibrate();
142                     break;
143             }
144         }
145 
onFinishFinalAnimation()146         public void onFinishFinalAnimation() {
147         }
148     }
149     final GlowPadTriggerListener mGlowPadViewListener = new GlowPadTriggerListener();
150 
151     @Override
onAnimationStarted()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
onFinishInflate()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 
maybeSwapSearchIcon()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                     com.android.internal.R.drawable.ic_action_assist_generic)) {
179                 if (DEBUG) Log.v(TAG, "Couldn't grab icon for component " + component);
180             }
181         }
182     }
183 
pointInside(int x, int y, View v)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 
isInContentArea(int x, int y)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 
vibrate()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 
show(final boolean show, boolean animate)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 
hide(boolean animate)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
onLayout(boolean changed, int l, int t, int r, int b)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
dispatchHoverEvent(MotionEvent event)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      */
isShowing()275     public boolean isShowing() {
276         return mShowing;
277     }
278 
setBar(BaseStatusBar bar)279     public void setBar(BaseStatusBar bar) {
280         mBar = bar;
281     }
282 
283     @Override
onTouchEvent(MotionEvent event)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 
createLayoutTransitioner()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 
isAssistantAvailable()302     public boolean isAssistantAvailable() {
303         return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
304                 .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
305     }
306 }
307