blob: 09aa60ff3ebb944d788e185f65415b65d2ccaaec [file] [log] [blame]
/*
* 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.media.AudioManager;
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.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarPanel;
import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
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 = 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 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.
KeyguardTouchDelegate.getInstance(getContext()).showAssistant();
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 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);
// 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,
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) {
return pointInside(x, y, mSearchTargetsContainer);
}
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),
AudioManager.STREAM_SYSTEM);
}
}
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;
}
@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;
}
}