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