Implement new assist gesture and motion
Change-Id: Ic8ba18c200058062f4d38ac4226d3516af3d3df0
This commit is contained in:
25
packages/SystemUI/res/drawable/assist_orb_navbar_scrim.xml
Normal file
25
packages/SystemUI/res/drawable/assist_orb_navbar_scrim.xml
Normal 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>
|
||||
54
packages/SystemUI/res/layout/assist_orb.xml
Normal file
54
packages/SystemUI/res/layout/assist_orb.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user