blob: 4a1c451c1d28d9a077c848f344fffabd0b1cdf1e [file] [log] [blame]
/*
* Copyright (C) 2016 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;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
private static final int GEAR_ALPHA_ANIM_DURATION = 200;
public interface SettingsIconRowListener {
/**
* Called when the gear behind a notification is touched.
*/
public void onGearTouched(ExpandableNotificationRow row, int x, int y);
/**
* Called when a notification is slid back over the gear.
*/
public void onSettingsIconRowReset(ExpandableNotificationRow row);
}
private ExpandableNotificationRow mParent;
private AlphaOptimizedImageView mGearIcon;
private float mHorizSpaceForGear;
private SettingsIconRowListener mListener;
private ValueAnimator mFadeAnimator;
private boolean mSettingsFadedIn = false;
private boolean mAnimating = false;
private boolean mOnLeft = true;
private boolean mDismissing = false;
private boolean mSnapping = false;
private boolean mIconPlaced = false;
private int[] mGearLocation = new int[2];
private int[] mParentLocation = new int[2];
private int mVertSpaceForGear;
public NotificationSettingsIconRow(Context context) {
this(context, null);
}
public NotificationSettingsIconRow(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon);
mGearIcon.setOnClickListener(this);
setOnClickListener(this);
mHorizSpaceForGear =
getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
resetState();
}
public void resetState() {
setGearAlpha(0f);
mIconPlaced = false;
mSettingsFadedIn = false;
mAnimating = false;
mSnapping = false;
mDismissing = false;
setIconLocation(true /* on left */);
if (mListener != null) {
mListener.onSettingsIconRowReset(mParent);
}
}
public void setGearListener(SettingsIconRowListener listener) {
mListener = listener;
}
public void setNotificationRowParent(ExpandableNotificationRow parent) {
mParent = parent;
setIconLocation(mOnLeft);
}
public void setAppName(String appName) {
Resources res = getResources();
String description = String.format(res.getString(R.string.notification_gear_accessibility),
appName);
mGearIcon.setContentDescription(description);
}
public ExpandableNotificationRow getNotificationParent() {
return mParent;
}
public void setGearAlpha(float alpha) {
if (alpha == 0) {
mSettingsFadedIn = false; // Can fade in again once it's gone.
setVisibility(View.INVISIBLE);
} else {
setVisibility(View.VISIBLE);
}
mGearIcon.setAlpha(alpha);
}
/**
* Returns whether the icon is on the left side of the view or not.
*/
public boolean isIconOnLeft() {
return mOnLeft;
}
/**
* Returns the horizontal space in pixels required to display the gear behind a notification.
*/
public float getSpaceForGear() {
return mHorizSpaceForGear;
}
/**
* Indicates whether the gear is visible at 1 alpha. Does not indicate
* if entire view is visible.
*/
public boolean isVisible() {
return mGearIcon.getAlpha() > 0;
}
public void cancelFadeAnimator() {
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
}
public void updateSettingsIcons(final float transX, final float size) {
if (mAnimating || !mSettingsFadedIn) {
// Don't adjust when animating, or if the gear hasn't been shown yet.
return;
}
final float fadeThreshold = size * 0.3f;
final float absTrans = Math.abs(transX);
float desiredAlpha = 0;
if (absTrans == 0) {
desiredAlpha = 0;
} else if (absTrans <= fadeThreshold) {
desiredAlpha = 1;
} else {
desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
}
setGearAlpha(desiredAlpha);
}
public void fadeInSettings(final boolean fromLeft, final float transX,
final float notiThreshold) {
if (mDismissing || mAnimating) {
return;
}
if (isIconLocationChange(transX)) {
setGearAlpha(0f);
}
setIconLocation(transX > 0 /* fromLeft */);
mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float absTrans = Math.abs(transX);
boolean pastGear = (fromLeft && transX <= notiThreshold)
|| (!fromLeft && absTrans <= notiThreshold);
if (pastGear && !mSettingsFadedIn) {
setGearAlpha((float) animation.getAnimatedValue());
}
}
});
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mAnimating = true;
}
@Override
public void onAnimationCancel(Animator animation) {
// TODO should animate back to 0f from current alpha
mGearIcon.setAlpha(0f);
}
@Override
public void onAnimationEnd(Animator animation) {
mAnimating = false;
mSettingsFadedIn = mGearIcon.getAlpha() == 1;
}
});
mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
mFadeAnimator.start();
}
public void updateVerticalLocation() {
if (mParent == null) {
return;
}
int parentHeight = mParent.getCollapsedHeight();
if (parentHeight < mVertSpaceForGear) {
mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
} else {
mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
}
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
setIconLocation(mOnLeft);
}
public void setIconLocation(boolean onLeft) {
if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
|| mGearIcon.getWidth() == 0) {
// Do nothing
return;
}
final boolean isRtl = mParent.isLayoutRtl();
// TODO No need to cast to float here once b/28050538 is fixed.
final float left = (float) (isRtl ? -(mParent.getWidth() - mHorizSpaceForGear) : 0);
final float right = (float) (isRtl ? 0 : (mParent.getWidth() - mHorizSpaceForGear));
final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
setTranslationX(onLeft ? left + centerX : right + centerX);
mOnLeft = onLeft;
mIconPlaced = true;
}
public boolean isIconLocationChange(float translation) {
boolean onLeft = translation > mGearIcon.getPaddingStart();
boolean onRight = translation < -mGearIcon.getPaddingStart();
if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
return true;
}
return false;
}
public void setDismissing() {
mDismissing = true;
}
public void setSnapping(boolean snapping) {
mSnapping = snapping;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.gear_icon) {
if (mListener != null) {
mGearIcon.getLocationOnScreen(mGearLocation);
mParent.getLocationOnScreen(mParentLocation);
final int centerX = (int) (mHorizSpaceForGear / 2);
final int centerY =
(int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
final int x = mGearLocation[0] - mParentLocation[0] + centerX;
final int y = mGearLocation[1] - mParentLocation[1] + centerY;
mListener.onGearTouched(mParent, x, y);
}
} else {
// Do nothing when the background is touched.
}
}
}