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