blob: 049c5fc5786d82c2032d3da300fa49e8b38c5d60 [file] [log] [blame]
/*
* 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.statusbar.phone;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.KeyButtonView;
/**
* A swipeable button for affordances on the lockscreen. This is used for the camera and phone
* affordance.
*/
public class SwipeAffordanceView extends KeyButtonView {
private static final int SWIPE_DIRECTION_START = 0;
private static final int SWIPE_DIRECTION_END = 1;
private static final int SWIPE_DIRECTION_LEFT = 0;
private static final int SWIPE_DIRECTION_RIGHT = 1;
private AffordanceListener mListener;
private int mScaledTouchSlop;
private float mDragDistance;
private int mResolvedSwipeDirection;
private int mSwipeDirection;
public SwipeAffordanceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SwipeAffordanceView,
0, 0);
try {
mSwipeDirection = a.getInt(R.styleable.SwipeAffordanceView_swipeDirection, 0);
} finally {
a.recycle();
}
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
if (!isLayoutRtl()) {
mResolvedSwipeDirection = mSwipeDirection;
} else {
mResolvedSwipeDirection = mSwipeDirection == SWIPE_DIRECTION_START
? SWIPE_DIRECTION_RIGHT
: SWIPE_DIRECTION_LEFT;
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragDistance = getResources().getDimension(R.dimen.affordance_drag_distance);
mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
public void enableAccessibility(boolean touchExplorationEnabled) {
// Add a touch handler or accessibility click listener for camera button.
if (touchExplorationEnabled) {
setOnTouchListener(null);
setOnClickListener(mClickListener);
} else {
setOnTouchListener(mTouchListener);
setOnClickListener(null);
}
}
public void setAffordanceListener(AffordanceListener listener) {
mListener = listener;
}
private void onActionPerformed() {
if (mListener != null) {
mListener.onActionPerformed(this);
}
}
private void onUserActivity(long when) {
if (mListener != null) {
mListener.onUserActivity(when);
}
}
private final OnClickListener mClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
onActionPerformed();
}
};
private final OnTouchListener mTouchListener = new OnTouchListener() {
private float mStartX;
private boolean mTouchSlopReached;
private boolean mSkipCancelAnimation;
@Override
public boolean onTouch(final View view, MotionEvent event) {
float realX = event.getRawX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = realX;
mTouchSlopReached = false;
mSkipCancelAnimation = false;
break;
case MotionEvent.ACTION_MOVE:
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX > mStartX
: realX < mStartX) {
realX = mStartX;
}
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mDragDistance
: realX > mStartX + mDragDistance) {
view.setPressed(true);
onUserActivity(event.getEventTime());
} else {
view.setPressed(false);
}
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mScaledTouchSlop
: realX > mStartX + mScaledTouchSlop) {
mTouchSlopReached = true;
}
view.setTranslationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? Math.max(realX - mStartX, -mDragDistance)
: Math.min(realX - mStartX, mDragDistance));
break;
case MotionEvent.ACTION_UP:
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mDragDistance
: realX > mStartX + mDragDistance) {
onActionPerformed();
view.animate().x(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? -view.getWidth()
: ((View) view.getParent()).getWidth() + view.getWidth())
.setInterpolator(new AccelerateInterpolator(2f)).withEndAction(
new Runnable() {
@Override
public void run() {
view.setTranslationX(0);
}
});
mSkipCancelAnimation = true;
}
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mScaledTouchSlop
: realX > mStartX + mScaledTouchSlop) {
mTouchSlopReached = true;
}
if (!mTouchSlopReached) {
mSkipCancelAnimation = true;
view.animate().translationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? -mDragDistance / 2
: mDragDistance / 2).
setInterpolator(new DecelerateInterpolator()).withEndAction(
new Runnable() {
@Override
public void run() {
view.animate().translationX(0).
setInterpolator(new AccelerateInterpolator());
}
});
}
case MotionEvent.ACTION_CANCEL:
view.setPressed(false);
if (!mSkipCancelAnimation) {
view.animate().translationX(0)
.setInterpolator(new AccelerateInterpolator(2f));
}
break;
}
return true;
}
};
public interface AffordanceListener {
/**
* Called when the view would like to report user activity.
*
* @param when The timestamp of the user activity in {@link SystemClock#uptimeMillis} time
* base.
*/
void onUserActivity(long when);
/**
* Called when the action of the affordance has been performed.
*/
void onActionPerformed(SwipeAffordanceView view);
}
}