• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.ActivityOptions;
20 import android.app.SearchManager;
21 import android.content.ActivityNotFoundException;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.content.res.Resources;
27 import android.media.AudioAttributes;
28 import android.os.AsyncTask;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.os.Vibrator;
32 import android.provider.Settings;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import android.widget.FrameLayout;
38 import android.widget.ImageView;
39 
40 import com.android.systemui.statusbar.BaseStatusBar;
41 import com.android.systemui.statusbar.CommandQueue;
42 import com.android.systemui.statusbar.StatusBarPanel;
43 import com.android.systemui.statusbar.phone.PhoneStatusBar;
44 
45 public class SearchPanelView extends FrameLayout implements StatusBarPanel {
46 
47     private static final String TAG = "SearchPanelView";
48     private static final String ASSIST_ICON_METADATA_NAME =
49             "com.android.systemui.action_assist_icon";
50 
51     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
52             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
53             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
54             .build();
55 
56     private final Context mContext;
57     private BaseStatusBar mBar;
58 
59     private SearchPanelCircleView mCircle;
60     private ImageView mLogo;
61     private View mScrim;
62 
63     private int mThreshold;
64     private boolean mHorizontal;
65 
66     private boolean mLaunching;
67     private boolean mDragging;
68     private boolean mDraggedFarEnough;
69     private float mStartTouch;
70     private float mStartDrag;
71     private boolean mLaunchPending;
72 
SearchPanelView(Context context, AttributeSet attrs)73     public SearchPanelView(Context context, AttributeSet attrs) {
74         this(context, attrs, 0);
75     }
76 
SearchPanelView(Context context, AttributeSet attrs, int defStyle)77     public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
78         super(context, attrs, defStyle);
79         mContext = context;
80         mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
81     }
82 
startAssistActivity()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 
89         final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
90                 .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
91         if (intent == null) return;
92 
93         try {
94             final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
95                     R.anim.search_launch_enter, R.anim.search_launch_exit);
96             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
97             AsyncTask.execute(new Runnable() {
98                 @Override
99                 public void run() {
100                     mContext.startActivityAsUser(intent, opts.toBundle(),
101                             new UserHandle(UserHandle.USER_CURRENT));
102                 }
103             });
104         } catch (ActivityNotFoundException e) {
105             Log.w(TAG, "Activity not found for " + intent.getAction());
106         }
107     }
108 
109     @Override
onFinishInflate()110     protected void onFinishInflate() {
111         super.onFinishInflate();
112         mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
113         mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
114         mLogo = (ImageView) findViewById(R.id.search_logo);
115         mScrim = findViewById(R.id.search_panel_scrim);
116     }
117 
maybeSwapSearchIcon()118     private void maybeSwapSearchIcon() {
119         Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
120                 .getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
121         if (intent != null) {
122             ComponentName component = intent.getComponent();
123             replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME);
124         } else {
125             mLogo.setImageDrawable(null);
126         }
127     }
128 
replaceDrawable(ImageView v, ComponentName component, String name)129     public void replaceDrawable(ImageView v, ComponentName component, String name) {
130         if (component != null) {
131             try {
132                 PackageManager packageManager = mContext.getPackageManager();
133                 // Look for the search icon specified in the activity meta-data
134                 Bundle metaData = packageManager.getActivityInfo(
135                         component, PackageManager.GET_META_DATA).metaData;
136                 if (metaData != null) {
137                     int iconResId = metaData.getInt(name);
138                     if (iconResId != 0) {
139                         Resources res = packageManager.getResourcesForActivity(component);
140                         v.setImageDrawable(res.getDrawable(iconResId));
141                         return;
142                     }
143                 }
144             } catch (PackageManager.NameNotFoundException e) {
145                 Log.w(TAG, "Failed to swap drawable; "
146                         + component.flattenToShortString() + " not found", e);
147             } catch (Resources.NotFoundException nfe) {
148                 Log.w(TAG, "Failed to swap drawable from "
149                         + component.flattenToShortString(), nfe);
150             }
151         }
152         v.setImageDrawable(null);
153     }
154 
155     @Override
isInContentArea(int x, int y)156     public boolean isInContentArea(int x, int y) {
157         return true;
158     }
159 
vibrate()160     private void vibrate() {
161         Context context = getContext();
162         if (Settings.System.getIntForUser(context.getContentResolver(),
163                 Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) {
164             Resources res = context.getResources();
165             Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
166             vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration),
167                     VIBRATION_ATTRIBUTES);
168         }
169     }
170 
show(final boolean show, boolean animate)171     public void show(final boolean show, boolean animate) {
172         if (show) {
173             maybeSwapSearchIcon();
174             if (getVisibility() != View.VISIBLE) {
175                 setVisibility(View.VISIBLE);
176                 vibrate();
177                 if (animate) {
178                     startEnterAnimation();
179                 } else {
180                     mScrim.setAlpha(1f);
181                 }
182             }
183             setFocusable(true);
184             setFocusableInTouchMode(true);
185             requestFocus();
186         } else {
187             if (animate) {
188                 startAbortAnimation();
189             } else {
190                 setVisibility(View.INVISIBLE);
191             }
192         }
193     }
194 
startEnterAnimation()195     private void startEnterAnimation() {
196         mCircle.startEnterAnimation();
197         mScrim.setAlpha(0f);
198         mScrim.animate()
199                 .alpha(1f)
200                 .setDuration(300)
201                 .setStartDelay(50)
202                 .setInterpolator(PhoneStatusBar.ALPHA_IN)
203                 .start();
204 
205     }
206 
startAbortAnimation()207     private void startAbortAnimation() {
208         mCircle.startAbortAnimation(new Runnable() {
209                     @Override
210                     public void run() {
211                         mCircle.setAnimatingOut(false);
212                         setVisibility(View.INVISIBLE);
213                     }
214                 });
215         mCircle.setAnimatingOut(true);
216         mScrim.animate()
217                 .alpha(0f)
218                 .setDuration(300)
219                 .setStartDelay(0)
220                 .setInterpolator(PhoneStatusBar.ALPHA_OUT);
221     }
222 
hide(boolean animate)223     public void hide(boolean animate) {
224         if (mBar != null) {
225             // This will indirectly cause show(false, ...) to get called
226             mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
227         } else {
228             if (animate) {
229                 startAbortAnimation();
230             } else {
231                 setVisibility(View.INVISIBLE);
232             }
233         }
234     }
235 
236     @Override
dispatchHoverEvent(MotionEvent event)237     public boolean dispatchHoverEvent(MotionEvent event) {
238         // Ignore hover events outside of this panel bounds since such events
239         // generate spurious accessibility events with the panel content when
240         // tapping outside of it, thus confusing the user.
241         final int x = (int) event.getX();
242         final int y = (int) event.getY();
243         if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
244             return super.dispatchHoverEvent(event);
245         }
246         return true;
247     }
248 
249     /**
250      * Whether the panel is showing, or, if it's animating, whether it will be
251      * when the animation is done.
252      */
isShowing()253     public boolean isShowing() {
254         return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut();
255     }
256 
setBar(BaseStatusBar bar)257     public void setBar(BaseStatusBar bar) {
258         mBar = bar;
259     }
260 
isAssistantAvailable()261     public boolean isAssistantAvailable() {
262         return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
263                 .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
264     }
265 
266     @Override
onTouchEvent(MotionEvent event)267     public boolean onTouchEvent(MotionEvent event) {
268         if (mLaunching || mLaunchPending) {
269             return false;
270         }
271         int action = event.getActionMasked();
272         switch (action) {
273             case MotionEvent.ACTION_DOWN:
274                 mStartTouch = mHorizontal ? event.getX() : event.getY();
275                 mDragging = false;
276                 mDraggedFarEnough = false;
277                 mCircle.reset();
278                 break;
279             case MotionEvent.ACTION_MOVE:
280                 float currentTouch = mHorizontal ? event.getX() : event.getY();
281                 if (getVisibility() == View.VISIBLE && !mDragging &&
282                         (!mCircle.isAnimationRunning(true /* enterAnimation */)
283                                 || Math.abs(mStartTouch - currentTouch) > mThreshold)) {
284                     mStartDrag = currentTouch;
285                     mDragging = true;
286                 }
287                 if (mDragging) {
288                     float offset = Math.max(mStartDrag - currentTouch, 0.0f);
289                     mCircle.setDragDistance(offset);
290                     mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold;
291                     mCircle.setDraggedFarEnough(mDraggedFarEnough);
292                 }
293                 break;
294             case MotionEvent.ACTION_UP:
295             case MotionEvent.ACTION_CANCEL:
296                 if (mDraggedFarEnough) {
297                     if (mCircle.isAnimationRunning(true  /* enterAnimation */)) {
298                         mLaunchPending = true;
299                         mCircle.setAnimatingOut(true);
300                         mCircle.performOnAnimationFinished(new Runnable() {
301                             @Override
302                             public void run() {
303                                 startExitAnimation();
304                             }
305                         });
306                     } else {
307                         startExitAnimation();
308                     }
309                 } else {
310                     startAbortAnimation();
311                 }
312                 break;
313         }
314         return true;
315     }
316 
startExitAnimation()317     private void startExitAnimation() {
318         mLaunchPending = false;
319         if (mLaunching || getVisibility() != View.VISIBLE) {
320             return;
321         }
322         mLaunching = true;
323         startAssistActivity();
324         vibrate();
325         mCircle.setAnimatingOut(true);
326         mCircle.startExitAnimation(new Runnable() {
327                     @Override
328                     public void run() {
329                         mLaunching = false;
330                         mCircle.setAnimatingOut(false);
331                         setVisibility(View.INVISIBLE);
332                     }
333                 });
334         mScrim.animate()
335                 .alpha(0f)
336                 .setDuration(300)
337                 .setStartDelay(0)
338                 .setInterpolator(PhoneStatusBar.ALPHA_OUT);
339     }
340 
setHorizontal(boolean horizontal)341     public void setHorizontal(boolean horizontal) {
342         mHorizontal = horizontal;
343         mCircle.setHorizontal(horizontal);
344     }
345 }
346