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