blob: 355022f9794c1c7e38dbf1c0151239f38d54cb65 [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.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import java.util.ArrayList;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
public class NotificationMenuRow extends FrameLayout
implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
private static final int ICON_ALPHA_ANIM_DURATION = 200;
private ExpandableNotificationRow mParent;
private OnMenuClickListener mListener;
private NotificationMenuRowProvider mMenuProvider;
private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
private ValueAnimator mFadeAnimator;
private boolean mMenuFadedIn = false;
private boolean mAnimating = false;
private boolean mOnLeft = true;
private boolean mDismissing = false;
private boolean mSnapping = false;
private boolean mIconsPlaced = false;
private int[] mIconLocation = new int[2];
private int[] mParentLocation = new int[2];
private float mHorizSpaceForIcon;
private int mVertSpaceForIcons;
private int mIconPadding;
private int mIconTint;
private float mAlpha = 0f;
public NotificationMenuRow(Context context) {
this(context, null);
}
public NotificationMenuRow(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs);
mMenuItems.addAll(getDefaultNotificationMenuItems());
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Dependency.get(PluginManager.class).addPluginListener(
NotificationMenuRowProvider.ACTION, this,
NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Dependency.get(PluginManager.class).removePluginListener(this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final Resources res = getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
mIconTint = res.getColor(R.color.notification_gear_color);
updateMenu(false /* notify */);
}
public static MenuItem getLongpressMenuItem(Context context) {
Resources res = context.getResources();
Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
String settingsDescription = res.getString(R.string.notification_menu_gear_description);
NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
R.layout.notification_info, null, false);
MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
return settings;
}
public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
ArrayList<MenuItem> items = new ArrayList<MenuItem>();
Resources res = getResources();
Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
.inflate(R.layout.notification_snooze, null, false);
String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
items.add(snooze);
Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
String settingsDescription = res.getString(R.string.notification_menu_gear_description);
NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
R.layout.notification_info, null, false);
MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
items.add(settings);
return items;
}
private void updateMenu(boolean notify) {
removeAllViews();
mMenuItems.clear();
if (mMenuProvider != null) {
mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
}
mMenuItems.addAll(getDefaultNotificationMenuItems());
for (int i = 0; i < mMenuItems.size(); i++) {
final View v = createMenuView(mMenuItems.get(i));
mMenuItems.get(i).menuView = v;
}
resetState(notify);
}
private View createMenuView(MenuItem item) {
AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
addView(iv);
iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
iv.setImageDrawable(item.icon);
iv.setOnClickListener(this);
iv.setColorFilter(mIconTint);
iv.setAlpha(mAlpha);
FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
lp.width = (int) mHorizSpaceForIcon;
lp.height = (int) mHorizSpaceForIcon;
return iv;
}
public void resetState(boolean notify) {
setMenuAlpha(0f);
mIconsPlaced = false;
mMenuFadedIn = false;
mAnimating = false;
mSnapping = false;
mDismissing = false;
setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
if (mListener != null && notify) {
mListener.onMenuReset(mParent);
}
}
public void setMenuClickListener(OnMenuClickListener listener) {
mListener = listener;
}
public void setNotificationRowParent(ExpandableNotificationRow parent) {
mParent = parent;
setMenuLocation(mOnLeft ? 1 : -1);
}
public void setAppName(String appName) {
Resources res = getResources();
final int count = mMenuItems.size();
for (int i = 0; i < count; i++) {
MenuItem item = mMenuItems.get(i);
String description = String.format(
res.getString(R.string.notification_menu_accessibility),
appName, item.menuDescription);
item.menuView.setContentDescription(description);
}
}
public ExpandableNotificationRow getNotificationParent() {
return mParent;
}
public void setMenuAlpha(float alpha) {
mAlpha = alpha;
if (alpha == 0) {
mMenuFadedIn = false; // Can fade in again once it's gone.
setVisibility(View.INVISIBLE);
} else {
setVisibility(View.VISIBLE);
}
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setAlpha(mAlpha);
}
}
/**
* Returns whether the menu is displayed on the left side of the view or not.
*/
public boolean isMenuOnLeft() {
return mOnLeft;
}
/**
* Returns the horizontal space in pixels required to display the menu.
*/
public float getSpaceForMenu() {
return mHorizSpaceForIcon * getChildCount();
}
/**
* Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
* visible.
*/
public boolean isVisible() {
return mAlpha > 0;
}
public void cancelFadeAnimator() {
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
}
public void updateMenuAlpha(final float transX, final float size) {
if (mAnimating || !mMenuFadedIn) {
// Don't adjust when animating, or if the menu 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));
}
setMenuAlpha(desiredAlpha);
}
public void fadeInMenu(final boolean fromLeft, final float transX,
final float notiThreshold) {
if (mDismissing || mAnimating) {
return;
}
if (isMenuLocationChange(transX)) {
setMenuAlpha(0f);
}
setMenuLocation((int) transX);
mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 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 && !mMenuFadedIn) {
setMenuAlpha((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
setMenuAlpha(0f);
}
@Override
public void onAnimationEnd(Animator animation) {
mAnimating = false;
mMenuFadedIn = mAlpha == 1;
}
});
mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);
mFadeAnimator.start();
}
public void updateVerticalLocation() {
if (mParent == null || mMenuItems.size() == 0) {
return;
}
int parentHeight = mParent.getCollapsedHeight();
float translationY;
if (parentHeight < mVertSpaceForIcons) {
translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
} else {
translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
}
setTranslationY(translationY);
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
mIconsPlaced = false;
setMenuLocation(mOnLeft ? 1 : -1);
}
public void setMenuLocation(int translation) {
boolean onLeft = translation > 0;
if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
// Do nothing
return;
}
final boolean isRtl = mParent.isLayoutRtl();
final int count = getChildCount();
final int width = getWidth();
for (int i = 0; i < count; i++) {
final View v = getChildAt(i);
final float left = isRtl
? -(width - mHorizSpaceForIcon * (i + 1))
: i * mHorizSpaceForIcon;
final float right = isRtl
? -i * mHorizSpaceForIcon
: width - (mHorizSpaceForIcon * (i + 1));
v.setTranslationX(onLeft ? left : right);
}
mOnLeft = onLeft;
mIconsPlaced = true;
}
public boolean isMenuLocationChange(float translation) {
boolean onLeft = translation > mIconPadding;
boolean onRight = translation < -mIconPadding;
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 (mListener == null) {
// Nothing to do
return;
}
v.getLocationOnScreen(mIconLocation);
mParent.getLocationOnScreen(mParentLocation);
final int centerX = (int) (mHorizSpaceForIcon / 2);
final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
final int x = mIconLocation[0] - mParentLocation[0] + centerX;
final int y = mIconLocation[1] - mParentLocation[1] + centerY;
final int index = indexOfChild(v);
mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
}
@Override
public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
mMenuProvider = plugin;
updateMenu(false /* notify */);
}
@Override
public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
mMenuProvider = null;
updateMenu(false /* notify */);
}
}