| /* |
| * 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(); |
| final float left = isRtl |
| ? -(mParent.getWidth() - mHorizSpaceForGear) |
| : 0; |
| final float right = 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. |
| } |
| } |
| } |