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