Home | History | Annotate | Download | only in systemui
      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