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