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