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