Implement new assist gesture and motion

Change-Id: Ic8ba18c200058062f4d38ac4226d3516af3d3df0
This commit is contained in:
Jorim Jaggi
2015-04-01 15:13:03 -07:00
parent 0b68ff4512
commit 2fdeeabe78
23 changed files with 879 additions and 1266 deletions

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2014 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
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:angle="90"
android:startColor="#33000000"
android:endColor="#00000000" />
</shape>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* apps/common/assets/default/default/skins/StatusBar.xml
**
** Copyright 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.
*/
-->
<!-- Extends FrameLayout -->
<com.android.systemui.assist.AssistOrbContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.systemui.statusbar.AlphaOptimizedView
android:layout_width="match_parent"
android:layout_height="@dimen/assist_orb_scrim_height"
android:layout_gravity="bottom"
android:id="@+id/assist_orb_scrim"
android:background="@drawable/assist_orb_scrim"/>
<com.android.systemui.assist.AssistOrbView
android:id="@+id/assist_orb"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_logo"/>
</com.android.systemui.assist.AssistOrbView>
<com.android.systemui.statusbar.AlphaOptimizedView
android:id="@+id/assist_orb_navbar_scrim"
android:layout_height="@dimen/assist_orb_navbar_scrim_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
android:elevation="50dp"
android:outlineProvider="none"
android:background="@drawable/assist_orb_navbar_scrim"/>
</com.android.systemui.assist.AssistOrbContainer>

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* apps/common/assets/default/default/skins/StatusBar.xml
**
** Copyright 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.
*/
-->
<!-- Extends FrameLayout -->
<com.android.systemui.SearchPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/search_panel_container"
android:layout_height="match_parent"
android:layout_width="match_parent">
<com.android.systemui.statusbar.AlphaOptimizedView
style="@style/SearchPanelScrim"
android:id="@+id/search_panel_scrim"
android:background="@drawable/search_panel_scrim" />
<com.android.systemui.SearchPanelCircleView
style="@style/SearchPanelCircle"
android:id="@+id/search_panel_circle">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_logo" />
</com.android.systemui.SearchPanelCircleView>
</com.android.systemui.SearchPanelView>

View File

@@ -18,10 +18,4 @@
<style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer">
<item name="android:layout_width">360dp</item>
</style>
<style name="SearchPanelScrim">
<item name="android:layout_width">@dimen/search_panel_scrim_height</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">right</item>
</style>
</resources>

View File

@@ -19,12 +19,6 @@
<item name="android:layout_width">480dp</item>
</style>
<style name="SearchPanelScrim">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/search_panel_scrim_height</item>
<item name="android:layout_gravity">bottom</item>
</style>
<style name="UserDetailView">
<item name="numColumns">4</item>
</style>

View File

@@ -108,8 +108,7 @@
<color name="notification_guts_text_color">#b2FFFFFF</color>
<color name="notification_guts_btn_color">#FFFFFFFF</color>
<color name="search_panel_circle_color">#ffffff</color>
<color name="search_panel_ripple_color">#ffbbbbbb</color>
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
<color name="doze_small_icon_background_color">#ff434343</color>

View File

@@ -471,22 +471,23 @@
<dimen name="go_to_full_shade_appearing_translation">200dp</dimen>
<!-- The diameter of the search panel circle. -->
<dimen name="search_panel_circle_size">88dp</dimen>
<dimen name="assist_orb_size">144dp</dimen>
<!-- The margin to the edge of the screen from where the circle starts to appear -->
<dimen name="search_panel_circle_base_margin">80dp</dimen>
<!-- The margin to the edge of the screen from where the orb starts to appear -->
<dimen name="assist_orb_base_margin">22dp</dimen>
<!-- The amount the circle translates when appearing -->
<dimen name="search_panel_circle_travel_distance">80dp</dimen>
<!-- The amount the orb translates when appearing -->
<dimen name="assist_orb_travel_distance">26dp</dimen>
<!-- The elevation of the search panel circle -->
<dimen name="search_panel_circle_elevation">12dp</dimen>
<!-- The elevation of the orb -->
<dimen name="assist_orb_elevation">12dp</dimen>
<!-- The height of the scrim behind the search panel circle. -->
<dimen name="search_panel_scrim_height">250dp</dimen>
<!-- The height of the scrim behind the orb. -->
<dimen name="assist_orb_scrim_height">250dp</dimen>
<!-- How far the user needs to drag up to invoke search. -->
<dimen name="search_panel_threshold">100dp</dimen>
<!-- The height of the scrim behind the search panel circle. Should be navigation_bar_height
+ 8dp. -->
<dimen name="assist_orb_navbar_scrim_height">56dp</dimen>
<!-- The width/height of the phone/camera/unlock icon view on keyguard. -->
<dimen name="keyguard_affordance_height">56dp</dimen>

View File

@@ -246,12 +246,6 @@
<item name="android:layout_height">match_parent</item>
</style>
<style name="SearchPanelScrim">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/search_panel_scrim_height</item>
<item name="android:layout_gravity">bottom</item>
</style>
<style name="UserDetailView">
<item name="numColumns">3</item>
</style>

View File

@@ -1,592 +0,0 @@
/*
* Copyright (C) 2014 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.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
public class SearchPanelCircleView extends FrameLayout {
private final int mCircleMinSize;
private final int mBaseMargin;
private final int mStaticOffset;
private final Paint mBackgroundPaint = new Paint();
private final Paint mRipplePaint = new Paint();
private final Rect mCircleRect = new Rect();
private final Rect mStaticRect = new Rect();
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mAppearInterpolator;
private final Interpolator mDisappearInterpolator;
private boolean mClipToOutline;
private final int mMaxElevation;
private boolean mAnimatingOut;
private float mOutlineAlpha;
private float mOffset;
private float mCircleSize;
private boolean mHorizontal;
private boolean mCircleHidden;
private ImageView mLogo;
private boolean mDraggedFarEnough;
private boolean mOffsetAnimatingIn;
private float mCircleAnimationEndValue;
private ArrayList<Ripple> mRipples = new ArrayList<Ripple>();
private ValueAnimator mOffsetAnimator;
private ValueAnimator mCircleAnimator;
private ValueAnimator mFadeOutAnimator;
private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
applyCircleSize((float) animation.getAnimatedValue());
updateElevation();
}
};
private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCircleAnimator = null;
}
};
private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setOffset((float) animation.getAnimatedValue());
}
};
public SearchPanelCircleView(Context context) {
this(context, null);
}
public SearchPanelCircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
if (mCircleSize > 0.0f) {
outline.setOval(mCircleRect);
} else {
outline.setEmpty();
}
outline.setAlpha(mOutlineAlpha);
}
});
setWillNotDraw(false);
mCircleMinSize = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_size);
mBaseMargin = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_base_margin);
mStaticOffset = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_travel_distance);
mMaxElevation = context.getResources().getDimensionPixelSize(
R.dimen.search_panel_circle_elevation);
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_linear_in);
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setColor(context.getColor(R.color.search_panel_circle_color));
mRipplePaint.setColor(context.getColor(R.color.search_panel_ripple_color));
mRipplePaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
drawRipples(canvas);
}
private void drawRipples(Canvas canvas) {
for (int i = 0; i < mRipples.size(); i++) {
Ripple ripple = mRipples.get(i);
ripple.draw(canvas);
}
}
private void drawBackground(Canvas canvas) {
canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
mBackgroundPaint);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLogo = (ImageView) findViewById(R.id.search_logo);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
if (changed) {
updateCircleRect(mStaticRect, mStaticOffset, true);
}
}
public void setCircleSize(float circleSize) {
setCircleSize(circleSize, false, null, 0, null);
}
public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable,
int startDelay, Interpolator interpolator) {
boolean isAnimating = mCircleAnimator != null;
boolean animationPending = isAnimating && !mCircleAnimator.isRunning();
boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0;
if (animated || animationPending || animatingOut) {
if (isAnimating) {
if (circleSize == mCircleAnimationEndValue) {
return;
}
mCircleAnimator.cancel();
}
mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
mCircleAnimator.addUpdateListener(mCircleUpdateListener);
mCircleAnimator.addListener(mClearAnimatorListener);
mCircleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (endRunnable != null) {
endRunnable.run();
}
}
});
Interpolator desiredInterpolator = interpolator != null ? interpolator
: circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator;
mCircleAnimator.setInterpolator(desiredInterpolator);
mCircleAnimator.setDuration(300);
mCircleAnimator.setStartDelay(startDelay);
mCircleAnimator.start();
mCircleAnimationEndValue = circleSize;
} else {
if (isAnimating) {
float diff = circleSize - mCircleAnimationEndValue;
PropertyValuesHolder[] values = mCircleAnimator.getValues();
values[0].setFloatValues(diff, circleSize);
mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime());
mCircleAnimationEndValue = circleSize;
} else {
applyCircleSize(circleSize);
updateElevation();
}
}
}
private void applyCircleSize(float circleSize) {
mCircleSize = circleSize;
updateLayout();
}
private void updateElevation() {
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
t = 1.0f - Math.max(t, 0.0f);
float offset = t * mMaxElevation;
setElevation(offset);
}
/**
* Sets the offset to the edge of the screen. By default this not not animated.
*
* @param offset The offset to apply.
*/
public void setOffset(float offset) {
setOffset(offset, false, 0, null, null);
}
/**
* Sets the offset to the edge of the screen.
*
* @param offset The offset to apply.
* @param animate Whether an animation should be performed.
* @param startDelay The desired start delay if animated.
* @param interpolator The desired interpolator if animated. If null,
* a default interpolator will be taken designed for appearing or
* disappearing.
* @param endRunnable The end runnable which should be executed when the animation is finished.
*/
private void setOffset(float offset, boolean animate, int startDelay,
Interpolator interpolator, final Runnable endRunnable) {
if (!animate) {
mOffset = offset;
updateLayout();
if (endRunnable != null) {
endRunnable.run();
}
} else {
if (mOffsetAnimator != null) {
mOffsetAnimator.removeAllListeners();
mOffsetAnimator.cancel();
}
mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOffsetAnimator = null;
if (endRunnable != null) {
endRunnable.run();
}
}
});
Interpolator desiredInterpolator = interpolator != null ?
interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator;
mOffsetAnimator.setInterpolator(desiredInterpolator);
mOffsetAnimator.setStartDelay(startDelay);
mOffsetAnimator.setDuration(300);
mOffsetAnimator.start();
mOffsetAnimatingIn = offset != 0;
}
}
private void updateLayout() {
updateCircleRect();
updateLogo();
invalidateOutline();
invalidate();
updateClipping();
}
private void updateClipping() {
boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty();
if (clip != mClipToOutline) {
setClipToOutline(clip);
mClipToOutline = clip;
}
}
private void updateLogo() {
boolean exitAnimationRunning = mFadeOutAnimator != null;
Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect;
float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f;
float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f;
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
if (!exitAnimationRunning) {
if (mHorizontal) {
translationX += t * mStaticOffset * 0.3f;
} else {
translationY += t * mStaticOffset * 0.3f;
}
float alpha = 1.0f-t;
alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
mLogo.setAlpha(alpha);
} else {
translationY += (mOffset - mStaticOffset) / 2;
}
mLogo.setTranslationX(translationX);
mLogo.setTranslationY(translationY);
}
private void updateCircleRect() {
updateCircleRect(mCircleRect, mOffset, false);
}
private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
int left, top;
float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
if (mHorizontal) {
left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset);
top = (int) ((getHeight() - circleSize) / 2);
} else {
left = (int) (getWidth() - circleSize) / 2;
top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
}
rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
}
public void setHorizontal(boolean horizontal) {
mHorizontal = horizontal;
updateCircleRect(mStaticRect, mStaticOffset, true);
updateLayout();
}
public void setDragDistance(float distance) {
if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) {
float circleSize = mCircleMinSize + rubberband(distance);
setCircleSize(circleSize);
}
}
private float rubberband(float diff) {
return (float) Math.pow(Math.abs(diff), 0.6f);
}
public void startAbortAnimation(Runnable endRunnable) {
if (mAnimatingOut) {
if (endRunnable != null) {
endRunnable.run();
}
return;
}
setCircleSize(0, true, null, 0, null);
setOffset(0, true, 0, null, endRunnable);
mCircleHidden = true;
}
public void startEnterAnimation() {
if (mAnimatingOut) {
return;
}
applyCircleSize(0);
setOffset(0);
setCircleSize(mCircleMinSize, true, null, 50, null);
setOffset(mStaticOffset, true, 50, null, null);
mCircleHidden = false;
}
public void startExitAnimation(final Runnable endRunnable) {
if (!mHorizontal) {
float offset = getHeight() / 2.0f;
setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null);
float xMax = getWidth() / 2;
float yMax = getHeight() / 2;
float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2);
setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator);
performExitFadeOutAnimation(50, 300, endRunnable);
} else {
// when in landscape, we don't wan't the animation as it interferes with the general
// rotation animation to the homescreen.
endRunnable.run();
}
}
private void performExitFadeOutAnimation(int startDelay, int duration,
final Runnable endRunnable) {
mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f);
// Linear since we are animating multiple values
mFadeOutAnimator.setInterpolator(new LinearInterpolator());
mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedFraction = animation.getAnimatedFraction();
float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f;
logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue);
float backgroundValue = animatedFraction < 0.2f ? 0.0f :
PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f);
backgroundValue = 1.0f - backgroundValue;
mBackgroundPaint.setAlpha((int) (backgroundValue * 255));
mOutlineAlpha = backgroundValue;
mLogo.setAlpha(logoValue);
invalidateOutline();
invalidate();
}
});
mFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (endRunnable != null) {
endRunnable.run();
}
mLogo.setAlpha(1.0f);
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
mFadeOutAnimator = null;
}
});
mFadeOutAnimator.setStartDelay(startDelay);
mFadeOutAnimator.setDuration(duration);
mFadeOutAnimator.start();
}
public void setDraggedFarEnough(boolean farEnough) {
if (farEnough != mDraggedFarEnough) {
if (farEnough) {
if (mCircleHidden) {
startEnterAnimation();
}
if (mOffsetAnimator == null) {
addRipple();
} else {
postDelayed(new Runnable() {
@Override
public void run() {
addRipple();
}
}, 100);
}
} else {
startAbortAnimation(null);
}
mDraggedFarEnough = farEnough;
}
}
private void addRipple() {
if (mRipples.size() > 1) {
// we only want 2 ripples at the time
return;
}
float xInterpolation, yInterpolation;
if (mHorizontal) {
xInterpolation = 0.75f;
yInterpolation = 0.5f;
} else {
xInterpolation = 0.5f;
yInterpolation = 0.75f;
}
float circleCenterX = mStaticRect.left * (1.0f - xInterpolation)
+ mStaticRect.right * xInterpolation;
float circleCenterY = mStaticRect.top * (1.0f - yInterpolation)
+ mStaticRect.bottom * yInterpolation;
float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f;
Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius);
ripple.start();
}
public void reset() {
mDraggedFarEnough = false;
mAnimatingOut = false;
mCircleHidden = true;
mClipToOutline = false;
if (mFadeOutAnimator != null) {
mFadeOutAnimator.cancel();
}
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
}
/**
* Check if an animation is currently running
*
* @param enterAnimation Is the animating queried the enter animation.
*/
public boolean isAnimationRunning(boolean enterAnimation) {
return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn);
}
public void performOnAnimationFinished(final Runnable runnable) {
if (mOffsetAnimator != null) {
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (runnable != null) {
runnable.run();
}
}
});
} else {
if (runnable != null) {
runnable.run();
}
}
}
public void setAnimatingOut(boolean animatingOut) {
mAnimatingOut = animatingOut;
}
/**
* @return Whether the circle is currently launching to the search activity or aborting the
* interaction
*/
public boolean isAnimatingOut() {
return mAnimatingOut;
}
@Override
public boolean hasOverlappingRendering() {
// not really true but it's ok during an animation, as it's never permanent
return false;
}
private class Ripple {
float x;
float y;
float radius;
float endRadius;
float alpha;
Ripple(float x, float y, float endRadius) {
this.x = x;
this.y = y;
this.endRadius = endRadius;
}
void start() {
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
// Linear since we are animating multiple values
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
alpha = 1.0f - animation.getAnimatedFraction();
alpha = mDisappearInterpolator.getInterpolation(alpha);
radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction());
radius *= endRadius;
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRipples.remove(Ripple.this);
updateClipping();
}
public void onAnimationStart(Animator animation) {
mRipples.add(Ripple.this);
updateClipping();
}
});
animator.setDuration(400);
animator.start();
}
public void draw(Canvas canvas) {
mRipplePaint.setAlpha((int) (alpha * 255));
canvas.drawCircle(x, y, radius, mRipplePaint);
}
}
}

View File

@@ -1,402 +0,0 @@
/*
* 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.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.pm.PackageManager;
import android.content.res.Resources;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Bundle;
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.Log;
import android.util.Slog;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarPanel;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
public class SearchPanelView extends FrameLayout implements StatusBarPanel {
private static final String TAG = "SearchPanelView";
private static final String ASSIST_ICON_METADATA_NAME =
"com.android.systemui.action_assist_icon";
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
private final Context mContext;
private BaseStatusBar mBar;
private SearchPanelCircleView mCircle;
private ImageView mLogo;
private View mScrim;
private int mThreshold;
private boolean mHorizontal;
private boolean mLaunching;
private boolean mDragging;
private boolean mDraggedFarEnough;
private float mStartTouch;
private float mStartDrag;
private boolean mLaunchPending;
private IVoiceInteractionManagerService mVoiceInteractionManagerService;
public SearchPanelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchPanelView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold);
}
private void startAssist() {
if (isVoiceInteractorActive()) {
startVoiceInteractor();
} else {
startAssistActivity();
}
}
private void startAssistActivity() {
if (!mBar.isDeviceProvisioned()) return;
// Close Recent Apps if needed
mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL);
final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
if (intent == null) return;
try {
final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
R.anim.search_launch_enter, R.anim.search_launch_exit);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
mContext.startActivityAsUser(intent, opts.toBundle(),
new UserHandle(UserHandle.USER_CURRENT));
}
});
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Activity not found for " + intent.getAction());
}
}
private void startVoiceInteractor() {
try {
// TODO: Pass show callback
mVoiceInteractionManagerService.showSessionForActiveService(null /* showCallback */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to call showSessionForActiveService", e);
}
}
private boolean isVoiceInteractorActive() {
try {
return mVoiceInteractionManagerService.activeServiceSupportsAssistGesture();
} catch (RemoteException e) {
Log.w(TAG, "Failed to call isServiceActive", e);
return false;
}
}
private ComponentName getVoiceInteractorComponentName() {
try {
return mVoiceInteractionManagerService.getActiveServiceComponentName();
} catch (RemoteException e) {
Log.w(TAG, "Failed to call getActiveServiceComponentName", e);
return null;
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle);
mLogo = (ImageView) findViewById(R.id.search_logo);
mScrim = findViewById(R.id.search_panel_scrim);
mVoiceInteractionManagerService = (IVoiceInteractionManagerService)
IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
}
private void maybeSwapSearchIcon() {
Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
ComponentName component = null;
boolean isService = false;
if (isVoiceInteractorActive()) {
component = getVoiceInteractorComponentName();
isService = true;
} else if (intent != null) {
component = intent.getComponent();
}
if (component != null) {
replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME,
isService);
} else {
mLogo.setImageDrawable(null);
}
}
public void replaceDrawable(ImageView v, ComponentName component, String name,
boolean isService) {
if (component != null) {
try {
PackageManager packageManager = mContext.getPackageManager();
// Look for the search icon specified in the activity meta-data
Bundle metaData = isService
? packageManager.getServiceInfo(
component, PackageManager.GET_META_DATA).metaData
: packageManager.getActivityInfo(
component, PackageManager.GET_META_DATA).metaData;
if (metaData != null) {
int iconResId = metaData.getInt(name);
if (iconResId != 0) {
Resources res = packageManager.getResourcesForApplication(
component.getPackageName());
v.setImageDrawable(res.getDrawable(iconResId));
return;
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Failed to swap drawable; "
+ component.flattenToShortString() + " not found", e);
} catch (Resources.NotFoundException nfe) {
Log.w(TAG, "Failed to swap drawable from "
+ component.flattenToShortString(), nfe);
}
}
v.setImageDrawable(null);
}
@Override
public boolean isInContentArea(int x, int y) {
return true;
}
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),
VIBRATION_ATTRIBUTES);
}
}
public void show(final boolean show, boolean animate) {
if (show) {
maybeSwapSearchIcon();
if (getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
vibrate();
if (animate) {
startEnterAnimation();
} else {
mScrim.setAlpha(1f);
}
}
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
} else {
if (animate) {
startAbortAnimation();
} else {
setVisibility(View.INVISIBLE);
}
}
}
private void startEnterAnimation() {
mCircle.startEnterAnimation();
mScrim.setAlpha(0f);
mScrim.animate()
.alpha(1f)
.setDuration(300)
.setStartDelay(50)
.setInterpolator(PhoneStatusBar.ALPHA_IN)
.start();
}
private void startAbortAnimation() {
mCircle.startAbortAnimation(new Runnable() {
@Override
public void run() {
mCircle.setAnimatingOut(false);
setVisibility(View.INVISIBLE);
}
});
mCircle.setAnimatingOut(true);
mScrim.animate()
.alpha(0f)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(PhoneStatusBar.ALPHA_OUT);
}
public void hide(boolean animate) {
if (mBar != null) {
// This will indirectly cause show(false, ...) to get called
mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
} else {
if (animate) {
startAbortAnimation();
} else {
setVisibility(View.INVISIBLE);
}
}
}
@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 getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut();
}
public void setBar(BaseStatusBar bar) {
mBar = bar;
}
public boolean isAssistantAvailable() {
return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mLaunching || mLaunchPending) {
return false;
}
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mStartTouch = mHorizontal ? event.getX() : event.getY();
mDragging = false;
mDraggedFarEnough = false;
mCircle.reset();
break;
case MotionEvent.ACTION_MOVE:
float currentTouch = mHorizontal ? event.getX() : event.getY();
if (getVisibility() == View.VISIBLE && !mDragging &&
(!mCircle.isAnimationRunning(true /* enterAnimation */)
|| Math.abs(mStartTouch - currentTouch) > mThreshold)) {
mStartDrag = currentTouch;
mDragging = true;
}
if (mDragging) {
float offset = Math.max(mStartDrag - currentTouch, 0.0f);
mCircle.setDragDistance(offset);
mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold;
mCircle.setDraggedFarEnough(mDraggedFarEnough);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mDraggedFarEnough) {
if (mCircle.isAnimationRunning(true /* enterAnimation */)) {
mLaunchPending = true;
mCircle.setAnimatingOut(true);
mCircle.performOnAnimationFinished(new Runnable() {
@Override
public void run() {
startExitAnimation();
}
});
} else {
startExitAnimation();
}
} else {
startAbortAnimation();
}
break;
}
return true;
}
private void startExitAnimation() {
mLaunchPending = false;
if (mLaunching || getVisibility() != View.VISIBLE) {
return;
}
mLaunching = true;
startAssist();
vibrate();
mCircle.setAnimatingOut(true);
mCircle.startExitAnimation(new Runnable() {
@Override
public void run() {
mLaunching = false;
mCircle.setAnimatingOut(false);
setVisibility(View.INVISIBLE);
}
});
mScrim.animate()
.alpha(0f)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(PhoneStatusBar.ALPHA_OUT);
}
public void setHorizontal(boolean horizontal) {
mHorizontal = horizontal;
mCircle.setHorizontal(horizontal);
}
}

View File

@@ -0,0 +1,292 @@
package com.android.systemui.assist;
import android.app.ActivityManager;
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.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.media.AudioAttributes;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.systemui.R;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
/**
* Class to manage everything around the assist gesture.
*/
public class AssistGestureManager {
private static final String TAG = "AssistGestureManager";
private static final String ASSIST_ICON_METADATA_NAME =
"com.android.systemui.action_assist_icon";
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
private static final long TIMEOUT_SERVICE = 2500;
private static final long TIMEOUT_ACTIVITY = 1000;
private final Context mContext;
private final WindowManager mWindowManager;
private AssistOrbContainer mView;
private final PhoneStatusBar mBar;
private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@Override
public void onFailed() throws RemoteException {
mView.post(mHideRunnable);
}
@Override
public void onShown() throws RemoteException {
mView.post(mHideRunnable);
}
};
private Runnable mHideRunnable = new Runnable() {
@Override
public void run() {
mView.removeCallbacks(this);
mView.show(false /* show */, true /* animate */);
}
};
public AssistGestureManager(PhoneStatusBar bar, Context context) {
mContext = context;
mBar = bar;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
}
public void onConfigurationChanged() {
boolean visible = false;
if (mView != null) {
visible = mView.isShowing();
mWindowManager.removeView(mView);
}
mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
R.layout.assist_orb, null);
mView.setVisibility(View.GONE);
mView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
WindowManager.LayoutParams lp = getLayoutParams();
mWindowManager.addView(mView, lp);
mBar.getNavigationBarView().setDelegateView(mView);
if (visible) {
mView.show(true /* show */, false /* animate */);
}
}
public void onGestureInvoked(boolean vibrate) {
boolean isVoiceInteractorActive = getVoiceInteractorSupportsAssistGesture();
if (!isVoiceInteractorActive && !isAssistantIntentAvailable()) {
return;
}
if (vibrate) {
vibrate();
}
if (!isVoiceInteractorActive || !isVoiceSessionRunning()) {
showOrb();
mView.postDelayed(mHideRunnable, isVoiceInteractorActive
? TIMEOUT_SERVICE
: TIMEOUT_ACTIVITY);
}
startAssist();
}
private WindowManager.LayoutParams getLayoutParams() {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
lp.gravity = Gravity.BOTTOM | Gravity.START;
lp.setTitle("AssistPreviewPanel");
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
return lp;
}
private void showOrb() {
maybeSwapSearchIcon();
mView.show(true /* show */, true /* animate */);
}
private void startAssist() {
if (getVoiceInteractorSupportsAssistGesture()) {
startVoiceInteractor();
} else {
startAssistActivity();
}
}
private void startAssistActivity() {
if (!mBar.isDeviceProvisioned()) {
return;
}
// Close Recent Apps if needed
mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
if (intent == null) {
return;
}
try {
final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
R.anim.search_launch_enter, R.anim.search_launch_exit);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
mContext.startActivityAsUser(intent, opts.toBundle(),
new UserHandle(UserHandle.USER_CURRENT));
}
});
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Activity not found for " + intent.getAction());
}
}
private void startVoiceInteractor() {
try {
mVoiceInteractionManagerService.showSessionForActiveService(mShowCallback);
} catch (RemoteException e) {
Log.w(TAG, "Failed to call showSessionForActiveService", e);
}
}
private boolean getVoiceInteractorSupportsAssistGesture() {
try {
return mVoiceInteractionManagerService.activeServiceSupportsAssistGesture();
} catch (RemoteException e) {
Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e);
return false;
}
}
private ComponentName getVoiceInteractorComponentName() {
try {
return mVoiceInteractionManagerService.getActiveServiceComponentName();
} catch (RemoteException e) {
Log.w(TAG, "Failed to call getActiveServiceComponentName", e);
return null;
}
}
private boolean isVoiceSessionRunning() {
try {
return mVoiceInteractionManagerService.isSessionRunning();
} catch (RemoteException e) {
Log.w(TAG, "Failed to call isSessionRunning", e);
return false;
}
}
public void destroy() {
mWindowManager.removeViewImmediate(mView);
}
private void maybeSwapSearchIcon() {
Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, false, UserHandle.USER_CURRENT);
ComponentName component = null;
boolean isService = false;
if (getVoiceInteractorSupportsAssistGesture()) {
component = getVoiceInteractorComponentName();
isService = true;
} else if (intent != null) {
component = intent.getComponent();
}
if (component != null) {
replaceDrawable(mView.getOrb().getLogo(), component, ASSIST_ICON_METADATA_NAME,
isService);
} else {
mView.getOrb().getLogo().setImageDrawable(null);
}
}
public void replaceDrawable(ImageView v, ComponentName component, String name,
boolean isService) {
if (component != null) {
try {
PackageManager packageManager = mContext.getPackageManager();
// Look for the search icon specified in the activity meta-data
Bundle metaData = isService
? packageManager.getServiceInfo(
component, PackageManager.GET_META_DATA).metaData
: packageManager.getActivityInfo(
component, PackageManager.GET_META_DATA).metaData;
if (metaData != null) {
int iconResId = metaData.getInt(name);
if (iconResId != 0) {
Resources res = packageManager.getResourcesForApplication(
component.getPackageName());
v.setImageDrawable(res.getDrawable(iconResId));
return;
}
}
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Failed to swap drawable; "
+ component.flattenToShortString() + " not found", e);
} catch (Resources.NotFoundException nfe) {
Log.w(TAG, "Failed to swap drawable from "
+ component.flattenToShortString(), nfe);
}
}
v.setImageDrawable(null);
}
private void vibrate() {
if (Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) {
Resources res = mContext.getResources();
Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration),
VIBRATION_ATTRIBUTES);
}
}
public boolean isAssistantIntentAvailable() {
return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
.getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null;
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2015 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.assist;
import android.annotation.Nullable;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
public class AssistOrbContainer extends FrameLayout {
private static final long EXIT_START_DELAY = 150;
private final Interpolator mLinearOutSlowInInterpolator;
private final Interpolator mFastOutLinearInInterpolator;
private View mScrim;
private View mNavbarScrim;
private AssistOrbView mOrb;
private boolean mAnimatingOut;
public AssistOrbContainer(Context context) {
this(context, null);
}
public AssistOrbContainer(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public AssistOrbContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_slow_in);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mScrim = findViewById(R.id.assist_orb_scrim);
mNavbarScrim = findViewById(R.id.assist_orb_navbar_scrim);
mOrb = (AssistOrbView) findViewById(R.id.assist_orb);
}
public void show(final boolean show, boolean animate) {
if (show) {
if (getVisibility() != View.VISIBLE) {
setVisibility(View.VISIBLE);
if (animate) {
startEnterAnimation();
} else {
reset();
}
}
} else {
if (animate) {
startExitAnimation(new Runnable() {
@Override
public void run() {
mAnimatingOut = false;
setVisibility(View.GONE);
}
});
} else {
setVisibility(View.GONE);
}
}
}
private void reset() {
mAnimatingOut = false;
mOrb.reset();
mScrim.setAlpha(1f);
mNavbarScrim.setAlpha(1f);
}
private void startEnterAnimation() {
if (mAnimatingOut) {
return;
}
mOrb.startEnterAnimation();
mScrim.setAlpha(0f);
mNavbarScrim.setAlpha(0f);
post(new Runnable() {
@Override
public void run() {
mScrim.animate()
.alpha(1f)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(mLinearOutSlowInInterpolator);
mNavbarScrim.animate()
.alpha(1f)
.setDuration(300)
.setStartDelay(0)
.setInterpolator(mLinearOutSlowInInterpolator);
}
});
}
private void startExitAnimation(final Runnable endRunnable) {
if (mAnimatingOut) {
if (endRunnable != null) {
endRunnable.run();
}
return;
}
mAnimatingOut = true;
mOrb.startExitAnimation(EXIT_START_DELAY);
mScrim.animate()
.alpha(0f)
.setDuration(250)
.setStartDelay(EXIT_START_DELAY)
.setInterpolator(mFastOutLinearInInterpolator);
mNavbarScrim.animate()
.alpha(0f)
.setDuration(250)
.setStartDelay(EXIT_START_DELAY)
.setInterpolator(mFastOutLinearInInterpolator)
.withEndAction(endRunnable);
}
/**
* Whether the panel is showing, or, if it's animating, whether it will be
* when the animation is done.
*/
public boolean isShowing() {
return getVisibility() == View.VISIBLE && !mAnimatingOut;
}
public AssistOrbView getOrb() {
return mOrb;
}
}

View File

@@ -0,0 +1,285 @@
/*
* Copyright (C) 2014 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.assist;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
public class AssistOrbView extends FrameLayout {
private final int mCircleMinSize;
private final int mBaseMargin;
private final int mStaticOffset;
private final Paint mBackgroundPaint = new Paint();
private final Rect mCircleRect = new Rect();
private final Rect mStaticRect = new Rect();
private final Interpolator mAppearInterpolator;
private final Interpolator mDisappearInterpolator;
private final Interpolator mOvershootInterpolator = new OvershootInterpolator();
private boolean mClipToOutline;
private final int mMaxElevation;
private float mOutlineAlpha;
private float mOffset;
private float mCircleSize;
private ImageView mLogo;
private float mCircleAnimationEndValue;
private ValueAnimator mOffsetAnimator;
private ValueAnimator mCircleAnimator;
private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
applyCircleSize((float) animation.getAnimatedValue());
updateElevation();
}
};
private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCircleAnimator = null;
}
};
private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOffset = (float) animation.getAnimatedValue();
updateLayout();
}
};
public AssistOrbView(Context context) {
this(context, null);
}
public AssistOrbView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
if (mCircleSize > 0.0f) {
outline.setOval(mCircleRect);
} else {
outline.setEmpty();
}
outline.setAlpha(mOutlineAlpha);
}
});
setWillNotDraw(false);
mCircleMinSize = context.getResources().getDimensionPixelSize(
R.dimen.assist_orb_size);
mBaseMargin = context.getResources().getDimensionPixelSize(
R.dimen.assist_orb_base_margin);
mStaticOffset = context.getResources().getDimensionPixelSize(
R.dimen.assist_orb_travel_distance);
mMaxElevation = context.getResources().getDimensionPixelSize(
R.dimen.assist_orb_elevation);
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_linear_in);
mBackgroundPaint.setAntiAlias(true);
mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color));
}
public ImageView getLogo() {
return mLogo;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBackground(canvas);
}
private void drawBackground(Canvas canvas) {
canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2,
mBackgroundPaint);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLogo = (ImageView) findViewById(R.id.search_logo);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight());
if (changed) {
updateCircleRect(mStaticRect, mStaticOffset, true);
}
}
public void animateCircleSize(float circleSize, long duration,
long startDelay, Interpolator interpolator) {
if (circleSize == mCircleAnimationEndValue) {
return;
}
if (mCircleAnimator != null) {
mCircleAnimator.cancel();
}
mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize);
mCircleAnimator.addUpdateListener(mCircleUpdateListener);
mCircleAnimator.addListener(mClearAnimatorListener);
mCircleAnimator.setInterpolator(interpolator);
mCircleAnimator.setDuration(duration);
mCircleAnimator.setStartDelay(startDelay);
mCircleAnimator.start();
mCircleAnimationEndValue = circleSize;
}
private void applyCircleSize(float circleSize) {
mCircleSize = circleSize;
updateLayout();
}
private void updateElevation() {
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
t = 1.0f - Math.max(t, 0.0f);
float offset = t * mMaxElevation;
setElevation(offset);
}
/**
* Animates the offset to the edge of the screen.
*
* @param offset The offset to apply.
* @param startDelay The desired start delay if animated.
*
* @param interpolator The desired interpolator if animated. If null,
* a default interpolator will be taken designed for appearing or
* disappearing.
*/
private void animateOffset(float offset, long duration, long startDelay,
Interpolator interpolator) {
if (mOffsetAnimator != null) {
mOffsetAnimator.removeAllListeners();
mOffsetAnimator.cancel();
}
mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset);
mOffsetAnimator.addUpdateListener(mOffsetUpdateListener);
mOffsetAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mOffsetAnimator = null;
}
});
mOffsetAnimator.setInterpolator(interpolator);
mOffsetAnimator.setStartDelay(startDelay);
mOffsetAnimator.setDuration(duration);
mOffsetAnimator.start();
}
private void updateLayout() {
updateCircleRect();
updateLogo();
invalidateOutline();
invalidate();
updateClipping();
}
private void updateClipping() {
boolean clip = mCircleSize < mCircleMinSize;
if (clip != mClipToOutline) {
setClipToOutline(clip);
mClipToOutline = clip;
}
}
private void updateLogo() {
float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f;
float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f
- mLogo.getHeight() / 2.0f - mCircleMinSize / 7f;
float t = (mStaticOffset - mOffset) / (float) mStaticOffset;
translationY += t * mStaticOffset * 0.1f;
float alpha = 1.0f-t;
alpha = Math.max((alpha - 0.5f) * 2.0f, 0);
mLogo.setImageAlpha((int) (alpha * 255));
mLogo.setTranslationX(translationX);
mLogo.setTranslationY(translationY);
}
private void updateCircleRect() {
updateCircleRect(mCircleRect, mOffset, false);
}
private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) {
int left, top;
float circleSize = useStaticSize ? mCircleMinSize : mCircleSize;
left = (int) (getWidth() - circleSize) / 2;
top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset);
rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize));
}
public void startExitAnimation(long delay) {
animateCircleSize(0, 200, delay, mDisappearInterpolator);
animateOffset(0, 200, delay, mDisappearInterpolator);
}
public void startEnterAnimation() {
applyCircleSize(0);
post(new Runnable() {
@Override
public void run() {
animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator);
animateOffset(mStaticOffset, 400, 0 /* delay */, mAppearInterpolator);
}
});
}
public void reset() {
mClipToOutline = false;
mBackgroundPaint.setAlpha(255);
mOutlineAlpha = 1.0f;
}
@Override
public boolean hasOverlappingRendering() {
// not really true but it's ok during an animation, as it's never permanent
return false;
}
}

View File

@@ -71,7 +71,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -79,7 +78,6 @@ import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.widget.DateTimeView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -90,7 +88,6 @@ import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SearchPanelView;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
@@ -132,7 +129,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024;
protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025;
protected static final int MSG_CLOSE_SEARCH_PANEL = 1027;
protected static final int MSG_SHOW_HEADS_UP = 1028;
protected static final int MSG_HIDE_HEADS_UP = 1029;
protected static final int MSG_ESCALATE_HEADS_UP = 1030;
@@ -164,9 +160,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected HeadsUpNotificationView mHeadsUpNotificationView;
protected int mHeadsUpNotificationDecay;
// Search panel
protected SearchPanelView mSearchPanelView;
protected int mCurrentUserId = 0;
final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
@@ -1043,50 +1036,6 @@ public abstract class BaseStatusBar extends SystemUI implements
mHandler.sendEmptyMessage(msg);
}
@Override
public void showSearchPanel() {
if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) {
mSearchPanelView.show(true, true);
}
}
@Override
public void hideSearchPanel() {
int msg = MSG_CLOSE_SEARCH_PANEL;
mHandler.removeMessages(msg);
mHandler.sendEmptyMessage(msg);
}
protected abstract WindowManager.LayoutParams getSearchLayoutParams(
LayoutParams layoutParams);
protected void updateSearchPanel() {
// Search Panel
boolean visible = false;
if (mSearchPanelView != null) {
visible = mSearchPanelView.isShowing();
mWindowManager.removeView(mSearchPanelView);
}
// Provide SearchPanel with a temporary parent to allow layout params to work.
LinearLayout tmpRoot = new LinearLayout(mContext);
mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_search_panel, tmpRoot, false);
mSearchPanelView.setOnTouchListener(
new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView));
mSearchPanelView.setVisibility(View.GONE);
boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical();
mSearchPanelView.setHorizontal(vertical);
WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams());
mWindowManager.addView(mSearchPanelView, lp);
mSearchPanelView.setBar(this);
if (visible) {
mSearchPanelView.show(true, false);
}
}
protected H createHandler() {
return new H();
}
@@ -1263,38 +1212,10 @@ public abstract class BaseStatusBar extends SystemUI implements
case MSG_SHOW_PREV_AFFILIATED_TASK:
showRecentsPreviousAffiliatedTask();
break;
case MSG_CLOSE_SEARCH_PANEL:
if (DEBUG) Log.d(TAG, "closing search panel");
if (mSearchPanelView != null && mSearchPanelView.isShowing()) {
mSearchPanelView.show(false, true);
}
break;
}
}
}
public class TouchOutsideListener implements View.OnTouchListener {
private int mMsg;
private StatusBarPanel mPanel;
public TouchOutsideListener(int msg, StatusBarPanel panel) {
mMsg = msg;
mPanel = panel;
}
public boolean onTouch(View v, MotionEvent ev) {
final int action = ev.getAction();
if (action == MotionEvent.ACTION_OUTSIDE
|| (action == MotionEvent.ACTION_DOWN
&& !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
mHandler.removeMessages(mMsg);
mHandler.sendEmptyMessage(mMsg);
return true;
}
return false;
}
}
protected void workAroundBadLayerDrawableOpacity(View v) {
}
@@ -1935,7 +1856,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected abstract void setAreThereNotifications();
protected abstract void updateNotifications();
protected abstract boolean shouldDisableNavbarGestures();
public abstract boolean shouldDisableNavbarGestures();
public abstract void addNotification(StatusBarNotification notification,
RankingMap ranking, Entry oldEntry);
@@ -2241,9 +2162,6 @@ public abstract class BaseStatusBar extends SystemUI implements
}
public void destroy() {
if (mSearchPanelView != null) {
mWindowManager.removeViewImmediate(mSearchPanelView);
}
mContext.unregisterReceiver(mBroadcastReceiver);
try {
mNotificationListener.unregisterAsSystemService();

View File

@@ -96,8 +96,6 @@ public class CommandQueue extends IStatusBar.Stub {
public void toggleRecentApps();
public void preloadRecentApps();
public void cancelPreloadRecentApps();
public void showSearchPanel();
public void hideSearchPanel();
public void setWindowState(int window, int state);
public void buzzBeepBlinked();
public void notificationLightOff();

View File

@@ -22,11 +22,12 @@ import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
public class DelegateViewHelper {
private View mDelegateView;
private View mSourceView;
private BaseStatusBar mBar;
private PhoneStatusBar mBar;
private int[] mTempPoint = new int[2];
private float[] mDownPoint = new float[2];
private float mTriggerThreshhold;
@@ -45,7 +46,7 @@ public class DelegateViewHelper {
mDelegateView = view;
}
public void setBar(BaseStatusBar phoneStatusBar) {
public void setBar(PhoneStatusBar phoneStatusBar) {
mBar = phoneStatusBar;
}
@@ -79,7 +80,7 @@ public class DelegateViewHelper {
float y = k < historySize ? event.getHistoricalY(k) : event.getY();
final float distance = mSwapXY ? (mDownPoint[0] - x) : (mDownPoint[1] - y);
if (distance > mTriggerThreshhold) {
mBar.showSearchPanel();
mBar.invokeAssistGesture(false /* vibrate */);
mPanelShowing = true;
break;
}

View File

@@ -1,21 +0,0 @@
/*
* Copyright (C) 2010 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.statusbar;
public interface StatusBarPanel {
public boolean isInContentArea(int x, int y);
}

View File

@@ -50,6 +50,8 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.DelegateViewHelper;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -195,7 +197,7 @@ public class NavigationBarView extends LinearLayout {
mDelegateHelper.setDelegateView(view);
}
public void setBar(BaseStatusBar phoneStatusBar) {
public void setBar(PhoneStatusBar phoneStatusBar) {
mTaskSwitchHelper.setBar(phoneStatusBar);
mDelegateHelper.setBar(phoneStatusBar);
}
@@ -261,8 +263,8 @@ public class NavigationBarView extends LinearLayout {
return mCurrentView.findViewById(R.id.back);
}
public View getHomeButton() {
return mCurrentView.findViewById(R.id.home);
public KeyButtonView getHomeButton() {
return (KeyButtonView) mCurrentView.findViewById(R.id.home);
}
public View getImeSwitchButton() {

View File

@@ -121,6 +121,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.assist.AssistGestureManager;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.CommandQueue;
@@ -323,6 +324,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private int mNavigationIconHints = 0;
private HandlerThread mHandlerThread;
private AssistGestureManager mAssistGestureManager;
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) {
@@ -642,8 +645,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
new NavigationBarView.OnVerticalChangedListener() {
@Override
public void onVerticalChanged(boolean isVertical) {
if (mSearchPanelView != null) {
mSearchPanelView.setHorizontal(isVertical);
if (mAssistGestureManager != null) {
mAssistGestureManager.onConfigurationChanged();
}
mNotificationPanel.setQsScrimEnabled(!isVertical);
}
@@ -834,6 +837,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mBroadcastReceiver.onReceive(mContext,
new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF));
mAssistGestureManager = new AssistGestureManager(this, context);
// receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -953,60 +958,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mStatusBarWindow;
}
@Override
protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) {
boolean opaque = false;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
(opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT));
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
lp.gravity = Gravity.BOTTOM | Gravity.START;
lp.setTitle("SearchPanel");
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
return lp;
}
@Override
protected void updateSearchPanel() {
super.updateSearchPanel();
if (mNavigationBarView != null) {
mNavigationBarView.setDelegateView(mSearchPanelView);
}
}
@Override
public void showSearchPanel() {
super.showSearchPanel();
mHandler.removeCallbacks(mShowSearchPanel);
// we want to freeze the sysui state wherever it is
mSearchPanelView.setSystemUiVisibility(mSystemUiVisibility);
if (mNavigationBarView != null) {
WindowManager.LayoutParams lp =
(android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWindowManager.updateViewLayout(mNavigationBarView, lp);
}
}
@Override
public void hideSearchPanel() {
super.hideSearchPanel();
if (mNavigationBarView != null) {
WindowManager.LayoutParams lp =
(android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams();
lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWindowManager.updateViewLayout(mNavigationBarView, lp);
}
public void invokeAssistGesture(boolean vibrate) {
mHandler.removeCallbacks(mInvokeAssist);
mAssistGestureManager.onGestureInvoked(vibrate);
}
public int getStatusBarHeight() {
@@ -1036,30 +990,33 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
private int mShowSearchHoldoff = 0;
private Runnable mShowSearchPanel = new Runnable() {
private Runnable mInvokeAssist = new Runnable() {
public void run() {
showSearchPanel();
invokeAssistGesture(true /* vibrate */);
awakenDreams();
if (mNavigationBarView != null) {
mNavigationBarView.getHomeButton().abortCurrentGesture();
}
}
};
View.OnTouchListener mHomeActionListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!shouldDisableNavbarGestures()) {
mHandler.removeCallbacks(mShowSearchPanel);
mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff);
}
break;
if (!shouldDisableNavbarGestures()) {
mHandler.removeCallbacks(mInvokeAssist);
mHandler.postDelayed(mInvokeAssist, mShowSearchHoldoff);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mHandler.removeCallbacks(mShowSearchPanel);
awakenDreams();
break;
}
return false;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mHandler.removeCallbacks(mInvokeAssist);
awakenDreams();
break;
}
return false;
}
};
@@ -1083,7 +1040,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNavigationBarView.getBackButton().setLongClickable(true);
mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
updateSearchPanel();
mAssistGestureManager.onConfigurationChanged();
}
// For small-screen devices (read: phones) that lack hardware navigation buttons
@@ -2060,11 +2017,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) {
mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL);
mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL);
}
if (mStatusBarWindow != null) {
// release focus immediately to kick off focus change transition
mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -2989,7 +2941,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
};
@Override
protected boolean shouldDisableNavbarGestures() {
public boolean shouldDisableNavbarGestures() {
return !isDeviceProvisioned()
|| mExpandedVisible
|| (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0;
@@ -3058,6 +3010,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHandlerThread = null;
}
mContext.unregisterReceiver(mBroadcastReceiver);
mAssistGestureManager.destroy();
}
private boolean mDemoModeAllowed;

View File

@@ -48,6 +48,7 @@ public class KeyButtonView extends ImageView {
private int mTouchSlop;
private boolean mSupportsLongpress = true;
private AudioManager mAudioManager;
private boolean mGestureAborted;
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
@@ -126,10 +127,15 @@ public class KeyButtonView extends ImageView {
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
mGestureAborted = false;
}
if (mGestureAborted) {
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
//Log.d("KeyButtonView", "press");
mDownTime = SystemClock.uptimeMillis();
setPressed(true);
if (mCode != 0) {
@@ -203,6 +209,11 @@ public class KeyButtonView extends ImageView {
InputManager.getInstance().injectInputEvent(ev,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
public void abortCurrentGesture() {
setPressed(false);
mGestureAborted = true;
}
}

View File

@@ -105,12 +105,6 @@ public class TvStatusBar extends BaseStatusBar {
public void notificationLightPulse(int argb, int onMillis, int offMillis) {
}
@Override
protected WindowManager.LayoutParams getSearchLayoutParams(
LayoutParams layoutParams) {
return null;
}
@Override
protected void setAreThereNotifications() {
}
@@ -120,7 +114,7 @@ public class TvStatusBar extends BaseStatusBar {
}
@Override
protected boolean shouldDisableNavbarGestures() {
public boolean shouldDisableNavbarGestures() {
return true;
}

View File

@@ -3910,6 +3910,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (attrs.type == TYPE_STATUS_BAR
|| attrs.type == TYPE_TOAST
|| attrs.type == TYPE_VOICE_INTERACTION_STARTING
|| (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
// Asking for layout as if the nav bar is hidden, lets the