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