| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui; |
| |
| import android.animation.LayoutTransition; |
| import android.app.ActivityManagerNative; |
| import android.app.ActivityOptions; |
| import android.app.SearchManager; |
| import android.content.ActivityNotFoundException; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserHandle; |
| import android.os.Vibrator; |
| import android.provider.Settings; |
| import android.util.AttributeSet; |
| import android.util.EventLog; |
| import android.util.Log; |
| import android.view.IWindowManager; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewTreeObserver; |
| import android.view.ViewTreeObserver.OnPreDrawListener; |
| import android.widget.FrameLayout; |
| |
| import com.android.internal.widget.multiwaveview.GlowPadView; |
| import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener; |
| |
| import com.android.systemui.EventLogTags; |
| import com.android.systemui.R; |
| import com.android.systemui.recent.StatusBarTouchProxy; |
| import com.android.systemui.statusbar.BaseStatusBar; |
| import com.android.systemui.statusbar.CommandQueue; |
| import com.android.systemui.statusbar.phone.PhoneStatusBar; |
| import com.android.systemui.statusbar.tablet.StatusBarPanel; |
| import com.android.systemui.statusbar.tablet.TabletStatusBar; |
| |
| public class SearchPanelView extends FrameLayout implements |
| StatusBarPanel, ActivityOptions.OnAnimationStartedListener { |
| private static final int SEARCH_PANEL_HOLD_DURATION = 0; |
| static final String TAG = "SearchPanelView"; |
| static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; |
| public static final boolean DEBUG_GESTURES = true; |
| private static final String ASSIST_ICON_METADATA_NAME = |
| "com.android.systemui.action_assist_icon"; |
| private final Context mContext; |
| private BaseStatusBar mBar; |
| private StatusBarTouchProxy mStatusBarTouchProxy; |
| |
| private boolean mShowing; |
| private View mSearchTargetsContainer; |
| private GlowPadView mGlowPadView; |
| private IWindowManager mWm; |
| |
| public SearchPanelView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public SearchPanelView(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| mContext = context; |
| mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); |
| } |
| |
| private void startAssistActivity() { |
| if (!mBar.isDeviceProvisioned()) return; |
| |
| // Close Recent Apps if needed |
| mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL); |
| boolean isKeyguardShowing = false; |
| try { |
| isKeyguardShowing = mWm.isKeyguardLocked(); |
| } catch (RemoteException e) { |
| |
| } |
| |
| if (isKeyguardShowing) { |
| // Have keyguard show the bouncer and launch the activity if the user succeeds. |
| try { |
| mWm.showAssistant(); |
| } catch (RemoteException e) { |
| // too bad, so sad... |
| } |
| onAnimationStarted(); |
| } else { |
| // Otherwise, keyguard isn't showing so launch it from here. |
| Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) |
| .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); |
| if (intent == null) return; |
| |
| try { |
| ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); |
| } catch (RemoteException e) { |
| // too bad, so sad... |
| } |
| |
| try { |
| ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, |
| R.anim.search_launch_enter, R.anim.search_launch_exit, |
| getHandler(), this); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| mContext.startActivityAsUser(intent, opts.toBundle(), |
| new UserHandle(UserHandle.USER_CURRENT)); |
| } catch (ActivityNotFoundException e) { |
| Log.w(TAG, "Activity not found for " + intent.getAction()); |
| onAnimationStarted(); |
| } |
| } |
| } |
| |
| class GlowPadTriggerListener implements GlowPadView.OnTriggerListener { |
| boolean mWaitingForLaunch; |
| |
| public void onGrabbed(View v, int handle) { |
| } |
| |
| public void onReleased(View v, int handle) { |
| } |
| |
| public void onGrabbedStateChange(View v, int handle) { |
| if (!mWaitingForLaunch && OnTriggerListener.NO_HANDLE == handle) { |
| mBar.hideSearchPanel(); |
| } |
| } |
| |
| public void onTrigger(View v, final int target) { |
| final int resId = mGlowPadView.getResourceIdForTarget(target); |
| switch (resId) { |
| case com.android.internal.R.drawable.ic_action_assist_generic: |
| mWaitingForLaunch = true; |
| startAssistActivity(); |
| vibrate(); |
| break; |
| } |
| } |
| |
| public void onFinishFinalAnimation() { |
| } |
| } |
| final GlowPadTriggerListener mGlowPadViewListener = new GlowPadTriggerListener(); |
| |
| @Override |
| public void onAnimationStarted() { |
| postDelayed(new Runnable() { |
| public void run() { |
| mGlowPadViewListener.mWaitingForLaunch = false; |
| mBar.hideSearchPanel(); |
| } |
| }, SEARCH_PANEL_HOLD_DURATION); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| mSearchTargetsContainer = findViewById(R.id.search_panel_container); |
| mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy); |
| // TODO: fetch views |
| mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view); |
| mGlowPadView.setOnTriggerListener(mGlowPadViewListener); |
| } |
| |
| private void maybeSwapSearchIcon() { |
| Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) |
| .getAssistIntent(mContext, false, UserHandle.USER_CURRENT); |
| if (intent != null) { |
| ComponentName component = intent.getComponent(); |
| if (component == null || !mGlowPadView.replaceTargetDrawablesIfPresent(component, |
| ASSIST_ICON_METADATA_NAME, |
| com.android.internal.R.drawable.ic_action_assist_generic)) { |
| if (DEBUG) Log.v(TAG, "Couldn't grab icon for component " + component); |
| } |
| } |
| } |
| |
| private boolean pointInside(int x, int y, View v) { |
| final int l = v.getLeft(); |
| final int r = v.getRight(); |
| final int t = v.getTop(); |
| final int b = v.getBottom(); |
| return x >= l && x < r && y >= t && y < b; |
| } |
| |
| public boolean isInContentArea(int x, int y) { |
| if (pointInside(x, y, mSearchTargetsContainer)) { |
| return true; |
| } else if (mStatusBarTouchProxy != null && |
| pointInside(x, y, mStatusBarTouchProxy)) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| private final OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { |
| public boolean onPreDraw() { |
| getViewTreeObserver().removeOnPreDrawListener(this); |
| mGlowPadView.resumeAnimations(); |
| return false; |
| } |
| }; |
| |
| private void vibrate() { |
| Context context = getContext(); |
| if (Settings.System.getIntForUser(context.getContentResolver(), |
| Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) { |
| Resources res = context.getResources(); |
| Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); |
| vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration)); |
| } |
| } |
| |
| public void show(final boolean show, boolean animate) { |
| if (!show) { |
| final LayoutTransition transitioner = animate ? createLayoutTransitioner() : null; |
| ((ViewGroup) mSearchTargetsContainer).setLayoutTransition(transitioner); |
| } |
| mShowing = show; |
| if (show) { |
| maybeSwapSearchIcon(); |
| if (getVisibility() != View.VISIBLE) { |
| setVisibility(View.VISIBLE); |
| // Don't start the animation until we've created the layer, which is done |
| // right before we are drawn |
| mGlowPadView.suspendAnimations(); |
| mGlowPadView.ping(); |
| getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); |
| vibrate(); |
| } |
| setFocusable(true); |
| setFocusableInTouchMode(true); |
| requestFocus(); |
| } else { |
| setVisibility(View.INVISIBLE); |
| } |
| } |
| |
| public void hide(boolean animate) { |
| if (mBar != null) { |
| // This will indirectly cause show(false, ...) to get called |
| mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); |
| } else { |
| setVisibility(View.INVISIBLE); |
| } |
| } |
| |
| /** |
| * We need to be aligned at the bottom. LinearLayout can't do this, so instead, |
| * let LinearLayout do all the hard work, and then shift everything down to the bottom. |
| */ |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| super.onLayout(changed, l, t, r, b); |
| // setPanelHeight(mSearchTargetsContainer.getHeight()); |
| } |
| |
| @Override |
| public boolean dispatchHoverEvent(MotionEvent event) { |
| // Ignore hover events outside of this panel bounds since such events |
| // generate spurious accessibility events with the panel content when |
| // tapping outside of it, thus confusing the user. |
| final int x = (int) event.getX(); |
| final int y = (int) event.getY(); |
| if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { |
| return super.dispatchHoverEvent(event); |
| } |
| return true; |
| } |
| |
| /** |
| * Whether the panel is showing, or, if it's animating, whether it will be |
| * when the animation is done. |
| */ |
| public boolean isShowing() { |
| return mShowing; |
| } |
| |
| public void setBar(BaseStatusBar bar) { |
| mBar = bar; |
| } |
| |
| public void setStatusBarView(final View statusBarView) { |
| if (mStatusBarTouchProxy != null) { |
| mStatusBarTouchProxy.setStatusBar(statusBarView); |
| // mGlowPadView.setOnTouchListener(new OnTouchListener() { |
| // public boolean onTouch(View v, MotionEvent event) { |
| // return statusBarView.onTouchEvent(event); |
| // } |
| // }); |
| } |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| if (DEBUG_GESTURES) { |
| if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { |
| EventLog.writeEvent(EventLogTags.SYSUI_SEARCHPANEL_TOUCH, |
| event.getActionMasked(), (int) event.getX(), (int) event.getY()); |
| } |
| } |
| return super.onTouchEvent(event); |
| } |
| |
| private LayoutTransition createLayoutTransitioner() { |
| LayoutTransition transitioner = new LayoutTransition(); |
| transitioner.setDuration(200); |
| transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); |
| transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); |
| return transitioner; |
| } |
| |
| public boolean isAssistantAvailable() { |
| return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) |
| .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; |
| } |
| } |