上一章把资源文件和布局代码贴上,现在是实际代码。
//文件名ThemeUtils.java
package Notification;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
public final class ThemeUtils {
/** Temporary array used internally to resolve attributes. */
private static final int[] TEMP_ATTR = new int[1];
private ThemeUtils() {
// Prevent instantiation.
}
/**
* Convenience method for retrieving a themed color value.
*
* @param context the {@link Context} to resolve the theme attribute against
* @param attr the attribute corresponding to the color to resolve
* @return the color value of the resolved attribute
*/
public static int resolveColor(Context context, int attr) {
return resolveColor(context, attr, null /* stateSet */);
}
/**
* Convenience method for retrieving a themed color value.
*
* @param context the {@link Context} to resolve the theme attribute against
* @param attr the attribute corresponding to the color to resolve
* @param stateSet an array of {@link android.view.View} states
* @return the color value of the resolved attribute
*/
//根据个人需要改变颜色
public static int resolveColor(Context context, int attr, int[] stateSet) {
final TypedArray a;
synchronized (TEMP_ATTR) {
TEMP_ATTR[0] = attr;
a = context.obtainStyledAttributes(TEMP_ATTR);
}
try {
if (stateSet == null) {
return a.getColor(0, Color.RED);
}
final ColorStateList colorStateList = a.getColorStateList(0);
if (colorStateList != null) {
return colorStateList.getColorForState(stateSet, Color.RED);
}
return Color.RED;
} finally {
a.recycle();
}
}
/**
* Convenience method for retrieving a themed drawable.
*
* @param context the {@link Context} to resolve the theme attribute against
* @param attr the attribute corresponding to the drawable to resolve
* @return the drawable of the resolved attribute
*/
public static Drawable resolveDrawable(Context context, int attr) {
final TypedArray a;
synchronized (TEMP_ATTR) {
TEMP_ATTR[0] = attr;
a = context.obtainStyledAttributes(TEMP_ATTR);
}
try {
return a.getDrawable(0);
} finally {
a.recycle();
}
}
}
//AnimatorUtils.java其中有些无用代码没有剔除
package Notification;
import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.Property;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AnimatorUtils {
public static final Interpolator DECELERATE_ACCELERATE_INTERPOLATOR = new Interpolator() {
@Override
public float getInterpolation(float x) {
return 0.5f + 4.0f * (x - 0.5f) * (x - 0.5f) * (x - 0.5f);
}
};
public static final Interpolator INTERPOLATOR_FAST_OUT_SLOW_IN =
new FastOutSlowInInterpolator();
public static final Property<View, Integer> BACKGROUND_ALPHA =
new Property<View, Integer>(Integer.class, "background.alpha") {
@Override
public Integer get(View view) {
Drawable background = view.getBackground();
if (background instanceof LayerDrawable
&& ((LayerDrawable) background).getNumberOfLayers() > 0) {
background = ((LayerDrawable) background).getDrawable(0);
}
return background.getAlpha();
}
@Override
public void set(View view, Integer value) {
setBackgroundAlpha(view, value);
}
};
/**
* Sets the alpha of the top layer's drawable (of the background) only, if the background is a
* layer drawable, to ensure that the other layers (i.e., the selectable item background, and
* therefore the touch feedback RippleDrawable) are not affected.
*
* @param view the affected view
* @param value the alpha value (0-255)
*/
public static void setBackgroundAlpha(View view, Integer value) {
Drawable background = view.getBackground();
if (background instanceof LayerDrawable
&& ((LayerDrawable) background).getNumberOfLayers() > 0) {
background = ((LayerDrawable) background).getDrawable(0);
}
background.setAlpha(value);
}
public static final Property<ImageView, Integer> DRAWABLE_ALPHA =
new Property<ImageView, Integer>(Integer.class, "drawable.alpha") {
@Override
public Integer get(ImageView view) {
return view.getDrawable().getAlpha();
}
@Override
public void set(ImageView view, Integer value) {
view.getDrawable().setAlpha(value);
}
};
public static final Property<ImageView, Integer> DRAWABLE_TINT =
new Property<ImageView, Integer>(Integer.class, "drawable.tint") {
@Override
public Integer get(ImageView view) {
return null;
}
@Override
public void set(ImageView view, Integer value) {
// Ensure the drawable is wrapped using DrawableCompat.
final Drawable drawable = view.getDrawable();
final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
if (wrappedDrawable != drawable) {
view.setImageDrawable(wrappedDrawable);
}
// Set the new tint value via DrawableCompat.
DrawableCompat.setTint(wrappedDrawable, value);
}
};
@SuppressWarnings("unchecked")
public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator();
private static Method sAnimateValue;
private static boolean sTryAnimateValue = true;
public static void setAnimatedFraction(ValueAnimator animator, float fraction) {
if (sTryAnimateValue) {
// try to set the animated fraction directly so that it isn't affected by the
// internal animator scale or time (b/17938711)
try {
if (sAnimateValue == null) {
sAnimateValue = ValueAnimator.class
.getDeclaredMethod("animateValue", float.class);
sAnimateValue.setAccessible(true);
}
sAnimateValue.invoke(animator, fraction);
return;
} catch (Exception e) {
// something went wrong, don't try that again
sTryAnimateValue = false;
}
}
// if that doesn't work then just fall back to setting the current play time
animator.setCurrentPlayTime(Math.round(fraction * animator.getDuration()));
}
public static void reverse(ValueAnimator... animators) {
for (ValueAnimator animator : animators) {
final float fraction = animator.getAnimatedFraction();
if (fraction > 0.0f) {
animator.reverse();
setAnimatedFraction(animator, 1.0f - fraction);
}
}
}
public static void cancel(ValueAnimator... animators) {
for (ValueAnimator animator : animators) {
animator.cancel();
}
}
public static ValueAnimator getScaleAnimator(View view, float... values) {
return ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat(View.SCALE_X, values),
PropertyValuesHolder.ofFloat(View.SCALE_Y, values));
}
public static ValueAnimator getAlphaAnimator(View view, float... values) {
return ObjectAnimator.ofFloat(view, View.ALPHA, values);
}
public static final Property<View, Integer> VIEW_LEFT =
new Property<View, Integer>(Integer.class, "left") {
@Override
public Integer get(View view) {
return view.getLeft();
}
@Override
public void set(View view, Integer left) {
view.setLeft(left);
}
};
public static final Property<View, Integer> VIEW_TOP =
new Property<View, Integer>(Integer.class, "top") {
@Override
public Integer get(View view) {
return view.getTop();
}
@Override
public void set(View view, Integer top) {
view.setTop(top);
}
};
public static final Property<View, Integer> VIEW_BOTTOM =
new Property<View, Integer>(Integer.class, "bottom") {
@Override
public Integer get(View view) {
return view.getBottom();
}
@Override
public void set(View view, Integer bottom) {
view.setBottom(bottom);
}
};
public static final Property<View, Integer> VIEW_RIGHT =
new Property<View, Integer>(Integer.class, "right") {
@Override
public Integer get(View view) {
return view.getRight();
}
@Override
public void set(View view, Integer right) {
view.setRight(right);
}
};
/**
* @param target the view to be morphed
* @param from the bounds of the {@code target} before animating
* @param to the bounds of the {@code target} after animating
* @return an animator that morphs the {@code target} between the {@code from} bounds and the
* {@code to} bounds. Note that it is the *content* bounds that matter here, so padding
* insets contributed by the background are subtracted from the views when computing the
* {@code target} bounds.
*/
public static Animator getBoundsAnimator(View target, View from, View to) {
// Fetch the content insets for the views. Content bounds are what matter, not total bounds.
final Rect targetInsets = new Rect();
target.getBackground().getPadding(targetInsets);
final Rect fromInsets = new Rect();
from.getBackground().getPadding(fromInsets);
final Rect toInsets = new Rect();
to.getBackground().getPadding(toInsets);
// Before animating, the content bounds of target must match the content bounds of from.
final int startLeft = from.getLeft() - fromInsets.left + targetInsets.left;
final int startTop = from.getTop() - fromInsets.top + targetInsets.top;
final int startRight = from.getRight() - fromInsets.right + targetInsets.right;
final int startBottom = from.getBottom() - fromInsets.bottom + targetInsets.bottom;
// After animating, the content bounds of target must match the content bounds of to.
final int endLeft = to.getLeft() - toInsets.left + targetInsets.left;
final int endTop = to.getTop() - toInsets.top + targetInsets.top;
final int endRight = to.getRight() - toInsets.right + targetInsets.right;
final int endBottom = to.getBottom() - toInsets.bottom + targetInsets.bottom;
return getBoundsAnimator(target, startLeft, startTop, startRight, startBottom, endLeft,
endTop, endRight, endBottom);
}
/**
* Returns an animator that animates the bounds of a single view.
*/
public static Animator getBoundsAnimator(View view, int fromLeft, int fromTop, int fromRight,
int fromBottom, int toLeft, int toTop, int toRight, int toBottom) {
view.setLeft(fromLeft);
view.setTop(fromTop);
view.setRight(fromRight);
view.setBottom(fromBottom);
return ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofInt(VIEW_LEFT, toLeft),
PropertyValuesHolder.ofInt(VIEW_TOP, toTop),
PropertyValuesHolder.ofInt(VIEW_RIGHT, toRight),
PropertyValuesHolder.ofInt(VIEW_BOTTOM, toBottom));
}
public static void startDrawableAnimation(ImageView view) {
final Drawable d = view.getDrawable();
if (d instanceof Animatable) {
((Animatable) d).start();
}
}
}
//CircleView.java圆形扩散效果视图
package Notification;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Property;
import android.view.Gravity;
import android.view.View;
import com.demo.remind.R;
/**
* A {@link View} that draws primitive circles.
*/
public class CircleView extends View {
/**
* A Property wrapper around the fillColor functionality handled by the
* {@link #setFillColor(int)} and {@link #getFillColor()} methods.
*/
public final static Property<CircleView, Integer> FILL_COLOR =
new Property<CircleView, Integer>(Integer.class, "fillColor") {
@Override
public Integer get(CircleView view) {
return view.getFillColor();
}
@Override
public void set(CircleView view, Integer value) {
view.setFillColor(value);
}
};
/**
* A Property wrapper around the radius functionality handled by the
* {@link #setRadius(float)} and {@link #getRadius()} methods.
*/
public final static Property<CircleView, Float> RADIUS =
new Property<CircleView, Float>(Float.class, "radius") {
@Override
public Float get(CircleView view) {
return view.getRadius();
}
@Override
public void set(CircleView view, Float value) {
view.setRadius(value);
}
};
/**
* The {@link Paint} used to draw the circle.
*/
private final Paint mCirclePaint = new Paint();
private int mGravity;
private float mCenterX;
private float mCenterY;
private float mRadius;
public CircleView(Context context) {
this(context, null /* attrs */);
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0 /* defStyleAttr */);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.CircleView, defStyleAttr, 0 /* defStyleRes */);
mGravity = a.getInt(R.styleable.CircleView_android_gravity, Gravity.NO_GRAVITY);
mCenterX = a.getDimension(R.styleable.CircleView_centerX, 0.0f);
mCenterY = a.getDimension(R.styleable.CircleView_centerY, 0.0f);
mRadius = a.getDimension(R.styleable.CircleView_radius, 0.0f);
mCirclePaint.setColor(a.getColor(R.styleable.CircleView_fillColor, Color.WHITE));
a.recycle();
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
if (mGravity != Gravity.NO_GRAVITY) {
applyGravity(mGravity, layoutDirection);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mGravity != Gravity.NO_GRAVITY) {
applyGravity(mGravity, getLayoutDirection());
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw the circle, duh
canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
}
@Override
public boolean hasOverlappingRendering() {
// only if we have a background, which we shouldn't...
return getBackground() != null;
}
/**
* @return the current {@link Gravity} used to align/size the circle
*/
public final int getGravity() {
return mGravity;
}
/**
* Describes how to align/size the circle relative to the view's bounds. Defaults to
* {@link Gravity#NO_GRAVITY}.
* <p/>
* Note: using {@link #setCenterX(float)}, {@link #setCenterY(float)}, or
* {@link #setRadius(float)} will automatically clear any conflicting gravity bits.
*
* @param gravity the {@link Gravity} flags to use
* @return this object, allowing calls to methods in this class to be chained
* @see
*/
public CircleView setGravity(int gravity) {
if (mGravity != gravity) {
mGravity = gravity;
if (gravity != Gravity.NO_GRAVITY && isLayoutDirectionResolved()) {
applyGravity(gravity, getLayoutDirection());
}
}
return this;
}
/**
* @return the ARGB color used to fill the circle
*/
public final int getFillColor() {
return mCirclePaint.getColor();
}
/**
* Sets the ARGB color used to fill the circle and invalidates only the affected area.
*
* @param color the ARGB color to use
* @return this object, allowing calls to methods in this class to be chained
* @see
*/
public CircleView setFillColor(int color) {
if (mCirclePaint.getColor() != color) {
mCirclePaint.setColor(color);
// invalidate the current area
invalidate(mCenterX, mCenterY, mRadius);
}
return this;
}
/**
* Sets the x-coordinate for the center of the circle and invalidates only the affected area.
*
* @param centerX the x-coordinate to use, relative to the view's bounds
* @return this object, allowing calls to methods in this class to be chained
* @see
*/
public CircleView setCenterX(float centerX) {
final float oldCenterX = mCenterX;
if (oldCenterX != centerX) {
mCenterX = centerX;
// invalidate the old/new areas
invalidate(oldCenterX, mCenterY, mRadius);
invalidate(centerX, mCenterY, mRadius);
}
// clear the horizontal gravity flags
mGravity &= ~Gravity.HORIZONTAL_GRAVITY_MASK;
return this;
}
/**
* Sets the y-coordinate for the center of the circle and invalidates only the affected area.
*
* @param centerY the y-coordinate to use, relative to the view's bounds
* @return this object, allowing calls to methods in this class to be chained
* @see
*/
public CircleView setCenterY(float centerY) {
final float oldCenterY = mCenterY;
if (oldCenterY != centerY) {
mCenterY = centerY;
// invalidate the old/new areas
invalidate(mCenterX, oldCenterY, mRadius);
invalidate(mCenterX, centerY, mRadius);
}
// clear the vertical gravity flags
mGravity &= ~Gravity.VERTICAL_GRAVITY_MASK;
return this;
}
/**
* @return the radius of the circle
*/
public final float getRadius() {
return mRadius;
}
/**
* Sets the radius of the circle and invalidates only the affected area.
*
* @param radius the radius to use
* @return this object, allowing calls to methods in this class to be chained
* @see
*/
public CircleView setRadius(float radius) {
final float oldRadius = mRadius;
if (oldRadius != radius) {
mRadius = radius;
// invalidate the old/new areas
invalidate(mCenterX, mCenterY, oldRadius);
if (radius > oldRadius) {
invalidate(mCenterX, mCenterY, radius);
}
}
// clear the fill gravity flags
if ((mGravity & Gravity.FILL_HORIZONTAL) == Gravity.FILL_HORIZONTAL) {
mGravity &= ~Gravity.FILL_HORIZONTAL;
}
if ((mGravity & Gravity.FILL_VERTICAL) == Gravity.FILL_VERTICAL) {
mGravity &= ~Gravity.FILL_VERTICAL;
}
return this;
}
/**
* Invalidates the rectangular area that circumscribes the circle defined by {@code centerX},
* {@code centerY}, and {@code radius}.
*/
private void invalidate(float centerX, float centerY, float radius) {
invalidate((int) (centerX - radius - 0.5f), (int) (centerY - radius - 0.5f),
(int) (centerX + radius + 0.5f), (int) (centerY + radius + 0.5f));
}
/**
* Applies the specified {@code gravity} and {@code layoutDirection}, adjusting the alignment
* and size of the circle depending on the resolved {@link Gravity} flags. Also invalidates the
* affected area if necessary.
*
* @param gravity the {@link Gravity} the {@link Gravity} flags to use
* @param layoutDirection the layout direction used to resolve the absolute gravity
*/
@SuppressLint("RtlHardcoded")
private void applyGravity(int gravity, int layoutDirection) {
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final float oldRadius = mRadius;
final float oldCenterX = mCenterX;
final float oldCenterY = mCenterY;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
mCenterX = 0.0f;
break;
case Gravity.CENTER_HORIZONTAL:
case Gravity.FILL_HORIZONTAL:
mCenterX = getWidth() / 2.0f;
break;
case Gravity.RIGHT:
mCenterX = getWidth();
break;
}
switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
mCenterY = 0.0f;
break;
case Gravity.CENTER_VERTICAL:
case Gravity.FILL_VERTICAL:
mCenterY = getHeight() / 2.0f;
break;
case Gravity.BOTTOM:
mCenterY = getHeight();
break;
}
switch (absoluteGravity & Gravity.FILL) {
case Gravity.FILL:
mRadius = Math.min(getWidth(), getHeight()) / 2.0f;
break;
case Gravity.FILL_HORIZONTAL:
mRadius = getWidth() / 2.0f;
break;
case Gravity.FILL_VERTICAL:
mRadius = getHeight() / 2.0f;
break;
}
if (oldCenterX != mCenterX || oldCenterY != mCenterY || oldRadius != mRadius) {
invalidate(oldCenterX, oldCenterY, oldRadius);
invalidate(mCenterX, mCenterY, mRadius);
}
}
}
//BaseActivity.java
package Notification;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.View;
/**
* Base activity class that changes the app window's color based on the current hour.
*/
public abstract class BaseActivity extends Activity {
public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator();
/** Sets the app window color on each frame of the {@link #mAppColorAnimator}. */
private final AppColorAnimationListener mAppColorAnimationListener
= new AppColorAnimationListener();
/** The current animator that is changing the app window color or {@code null}. */
private ValueAnimator mAppColorAnimator;
/** Draws the app window's color. */
private ColorDrawable mBackground;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Allow the content to layout behind the status and navigation bars.
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
final int color = ThemeUtils.resolveColor(this, android.R.attr.windowBackground);
adjustAppColor(color, false /* animate */);
}
@Override
protected void onStart() {
super.onStart();
// Ensure the app window color is up-to-date.
final int color = ThemeUtils.resolveColor(this, android.R.attr.windowBackground);
adjustAppColor(color, false /* animate */);
}
/**
* Adjusts the current app window color of this activity; animates the change if desired.
*
* @param color the ARGB value to set as the current app window color
* @param animate {@code true} if the change should be animated
*/
protected void adjustAppColor( int color, boolean animate) {
// Create and install the drawable that defines the window color.
if (mBackground == null) {
mBackground = new ColorDrawable(color);
getWindow().setBackgroundDrawable(mBackground);
}
// Cancel the current window color animation if one exists.
if (mAppColorAnimator != null) {
mAppColorAnimator.cancel();
}
final int currentColor = mBackground.getColor();
if (currentColor != color) {
if (animate) {
mAppColorAnimator = ValueAnimator.ofObject(ARGB_EVALUATOR, currentColor, color)
.setDuration(3000L);
mAppColorAnimator.addUpdateListener(mAppColorAnimationListener);
mAppColorAnimator.addListener(mAppColorAnimationListener);
mAppColorAnimator.start();
} else {
setAppColor(color);
}
}
}
private void setAppColor( int color) {
mBackground.setColor(color);
}
/**
* Sets the app window color to the current color produced by the animator.
*/
private final class AppColorAnimationListener extends AnimatorListenerAdapter
implements AnimatorUpdateListener {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
final int color = (int) valueAnimator.getAnimatedValue();
setAppColor(color);
}
@Override
public void onAnimationEnd(Animator animation) {
if (mAppColorAnimator == animation) {
mAppColorAnimator = null;
}
}
}
}
//AlarmActivity.java
package Notification;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.view.animation.PathInterpolatorCompat;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.TextClock;
import android.widget.TextView;
import com.demo.remind.R;
import java.util.List;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
public class AlarmActivity extends BaseActivity
implements View.OnClickListener, View.OnTouchListener {
String TAG = "AlarmActivity";
public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE";
/**
* AlarmActivity and AlarmService listen for this broadcast intent so that other
* applications can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION).
*/
public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS";
/**
* A public action sent by AlarmService when the alarm has started.
*/
public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
/**
* A public action sent by AlarmService when the alarm has stopped for any reason.
*/
public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";
/**
* Private action used to stop an alarm with this service.
*/
public static final String STOP_ALARM_ACTION = "STOP_ALARM";
private static final TimeInterpolator PULSE_INTERPOLATOR =
PathInterpolatorCompat.create(0.4f, 0.0f, 0.2f, 1.0f);
private static final TimeInterpolator REVEAL_INTERPOLATOR =
PathInterpolatorCompat.create(0.0f, 0.0f, 0.2f, 1.0f);
private static final int PULSE_DURATION_MILLIS = 1000;
private static final int ALARM_BOUNCE_DURATION_MILLIS = 500;
private static final int ALERT_REVEAL_DURATION_MILLIS = 500;
private static final int ALERT_FADE_DURATION_MILLIS = 500;
private static final int ALERT_DISMISS_DELAY_MILLIS = 2000;
private static final float BUTTON_SCALE_DEFAULT = 0.7f;
private static final int BUTTON_DRAWABLE_ALPHA_DEFAULT = 165;
private final Handler mHandler = new Handler();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
Log.v(TAG, "Received broadcast: %s" + action);
if (!mAlarmHandled) {
switch (action) {
case ALARM_SNOOZE_ACTION:
snooze();
break;
case ALARM_DISMISS_ACTION:
dismiss();
break;
case ALARM_DONE_ACTION:
finish();
break;
default:
Log.i(TAG, "Unknown broadcast: %s" + action);
break;
}
} else {
Log.v("Ignored broadcast: %s", action);
}
}
};
private boolean mAlarmHandled;
private int mCurrentHourColor;
private AccessibilityManager mAccessibilityManager;
private ViewGroup mAlertView;
private TextView mAlertTitleView;
private TextView mAlertInfoView;
private ViewGroup mContentView;
private ImageView mAlarmButton;
private ImageView mSnoozeButton;
private ImageView mDismissButton;
private TextView mHintView;
private ValueAnimator mAlarmAnimator;
private ValueAnimator mSnoozeAnimator;
private ValueAnimator mDismissAnimator;
private ValueAnimator mPulseAnimator;
private int mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(AudioManager.STREAM_ALARM);
// Get the volume/camera button behavior setting
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
// Hide navigation bar to minimize accidental tap on Home key
hideNavigationBar();
// Close dialogs and window shade, so this is fully visible
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
setContentView(R.layout.alarm_activity);
mAlertView = (ViewGroup) findViewById(R.id.alert);
mAlertTitleView = (TextView) mAlertView.findViewById(R.id.alert_title);
mAlertInfoView = (TextView) mAlertView.findViewById(R.id.alert_info);
mContentView = (ViewGroup) findViewById(R.id.content);
mAlarmButton = (ImageView) mContentView.findViewById(R.id.alarm);
mSnoozeButton = (ImageView) mContentView.findViewById(R.id.snooze);
mDismissButton = (ImageView) mContentView.findViewById(R.id.dismiss);
mHintView = (TextView) mContentView.findViewById(R.id.hint);
final TextView titleView = (TextView) mContentView.findViewById(R.id.title);
final TextClock digitalClock = (TextClock) mContentView.findViewById(R.id.digital_clock);
final CircleView pulseView = (CircleView) mContentView.findViewById(R.id.pulse);
titleView.setText("dddddddddddddddd");
mCurrentHourColor = getResources().getColor(R.color.black_54p);
getWindow().setBackgroundDrawable(new ColorDrawable(mCurrentHourColor));
mAlarmButton.setOnTouchListener(this);
mSnoozeButton.setOnClickListener(this);
mDismissButton.setOnClickListener(this);
mAlarmAnimator = AnimatorUtils.getScaleAnimator(mAlarmButton, 1.0f, 0.0f);
mSnoozeAnimator = getButtonAnimator(mSnoozeButton, Color.WHITE);
mDismissAnimator = getButtonAnimator(mDismissButton, mCurrentHourColor);
mPulseAnimator = ObjectAnimator.ofPropertyValuesHolder(pulseView,
PropertyValuesHolder.ofFloat(CircleView.RADIUS, 0.0f, pulseView.getRadius()),
PropertyValuesHolder.ofObject(CircleView.FILL_COLOR, AnimatorUtils.ARGB_EVALUATOR,
ColorUtils.setAlphaComponent(pulseView.getFillColor(), 0)));
mPulseAnimator.setDuration(PULSE_DURATION_MILLIS);
mPulseAnimator.setInterpolator(PULSE_INTERPOLATOR);
mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
mPulseAnimator.start();
}
@Override
protected void onResume() {
super.onResume();
resetAnimations();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
public boolean dispatchKeyEvent(KeyEvent keyEvent) {
// Do this in dispatch to intercept a few of the system keys.
Log.v(TAG, "dispatchKeyEvent: %s" + keyEvent);
final int keyCode = keyEvent.getKeyCode();
switch (keyCode) {
// Volume keys and camera keys dismiss the alarm.
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_CAMERA:
case KeyEvent.KEYCODE_FOCUS:
if (!mAlarmHandled) {
if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
snooze();
}
return true;
}
}
return super.dispatchKeyEvent(keyEvent);
}
@Override
public void onBackPressed() {
// Don't allow back to dismiss.
}
@Override
public void onClick(View view) {
if (mAlarmHandled) {
return;
}
// If in accessibility mode, allow snooze/dismiss by double tapping on respective icons.
if (isAccessibilityEnabled()) {
if (view == mSnoozeButton) {
snooze();
} else if (view == mDismissButton) {
dismiss();
}
return;
}
if (view == mSnoozeButton) {
hintSnooze();
} else if (view == mDismissButton) {
hintDismiss();
}
}
@Override
public boolean onTouch(View view, MotionEvent event) {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
// Track the pointer that initiated the touch sequence.
mInitialPointerIndex = event.getPointerId(event.getActionIndex());
// Stop the pulse, allowing the last pulse to finish.
mPulseAnimator.setRepeatCount(0);
} else if (action == MotionEvent.ACTION_CANCEL) {
// Clear the pointer index.
mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID;
// Reset everything.
resetAnimations();
}
final int actionIndex = event.getActionIndex();
if (mInitialPointerIndex == MotionEvent.INVALID_POINTER_ID
|| mInitialPointerIndex != event.getPointerId(actionIndex)) {
// Ignore any pointers other than the initial one, bail early.
return true;
}
final int[] contentLocation = {0, 0};
mContentView.getLocationOnScreen(contentLocation);
final float x = event.getRawX() - contentLocation[0];
final float y = event.getRawY() - contentLocation[1];
final int alarmLeft = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft();
final int alarmRight = mAlarmButton.getRight() - mAlarmButton.getPaddingRight();
final float snoozeFraction, dismissFraction;
/* if (mContentView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
snoozeFraction = getFraction(alarmRight, mSnoozeButton.getLeft(), x);
dismissFraction = getFraction(alarmLeft, mDismissButton.getRight(), x);
} else {*/
snoozeFraction = getFraction(alarmLeft, mSnoozeButton.getRight(), x);
dismissFraction = getFraction(alarmRight, mDismissButton.getLeft(), x);
//}
setAnimatedFractions(snoozeFraction, dismissFraction);
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
mInitialPointerIndex = MotionEvent.INVALID_POINTER_ID;
if (snoozeFraction == 1.0f) {
snooze();
} else if (dismissFraction == 1.0f) {
dismiss();
} else {
if (snoozeFraction > 0.0f || dismissFraction > 0.0f) {
// Animate back to the initial state.
AnimatorUtils.reverse(mAlarmAnimator, mSnoozeAnimator, mDismissAnimator);
} else if (mAlarmButton.getTop() <= y && y <= mAlarmButton.getBottom()) {
// User touched the alarm button, hint the dismiss action.
hintDismiss();
}
// Restart the pulse.
mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
if (!mPulseAnimator.isStarted()) {
mPulseAnimator.start();
}
}
}
return true;
}
private void hideNavigationBar() {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
/**
* Returns {@code true} if accessibility is enabled, to enable alternate behavior for click
* handling, etc.
*/
private boolean isAccessibilityEnabled() {
if (mAccessibilityManager == null || !mAccessibilityManager.isEnabled()) {
// Accessibility is unavailable or disabled.
return false;
} else if (mAccessibilityManager.isTouchExplorationEnabled()) {
// TalkBack's touch exploration mode is enabled.
return true;
}
// Check if "Switch Access" is enabled.
final List<AccessibilityServiceInfo> enabledAccessibilityServices =
mAccessibilityManager.getEnabledAccessibilityServiceList(FEEDBACK_GENERIC);
return !enabledAccessibilityServices.isEmpty();
}
private void hintSnooze() {
final int alarmLeft = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft();
final int alarmRight = mAlarmButton.getRight() - mAlarmButton.getPaddingRight();
final float translationX = Math.max(mSnoozeButton.getLeft() - alarmRight, 0)
+ Math.min(mSnoozeButton.getRight() - alarmLeft, 0);
getAlarmBounceAnimator(translationX, translationX < 0.0f ?
R.string.description_direction_left : R.string.description_direction_right).start();
}
private void hintDismiss() {
final int alarmLeft = mAlarmButton.getLeft() + mAlarmButton.getPaddingLeft();
final int alarmRight = mAlarmButton.getRight() - mAlarmButton.getPaddingRight();
final float translationX = Math.max(mDismissButton.getLeft() - alarmRight, 0)
+ Math.min(mDismissButton.getRight() - alarmLeft, 0);
getAlarmBounceAnimator(translationX, translationX < 0.0f ?
R.string.description_direction_left : R.string.description_direction_right).start();
}
/**
* Set animators to initial values and restart pulse on alarm button.
*/
private void resetAnimations() {
// Set the animators to their initial values.
setAnimatedFractions(0.0f /* snoozeFraction */, 0.0f /* dismissFraction */);
// Restart the pulse.
mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
if (!mPulseAnimator.isStarted()) {
mPulseAnimator.start();
}
}
/**
* Perform snooze animation and send snooze intent.
*/
private void snooze() {
mAlarmHandled = true;
final int colorAccent = getResources().getColor(R.color.black_54p);//ThemeUtils.resolveColor(this, R.attr.colorAccent);
setAnimatedFractions(1.0f /* snoozeFraction */, 0.0f /* dismissFraction */);
final String infoText = "";
final String accessibilityText = "hellow";
getAlertAnimator(mSnoozeButton, R.string.alarm_alert_snoozed_text, infoText,
accessibilityText, colorAccent, colorAccent).start();
}
private void dismiss() {
mAlarmHandled = true;
setAnimatedFractions(0.0f /* snoozeFraction */, 1.0f /* dismissFraction */);
getAlertAnimator(mDismissButton, R.string.alarm_alert_off_text, null /* infoText */,
getString(R.string.alarm_alert_off_text) /* accessibilityText */,
Color.WHITE, mCurrentHourColor).start();
}
private void setAnimatedFractions(float snoozeFraction, float dismissFraction) {
final float alarmFraction = Math.max(snoozeFraction, dismissFraction);
AnimatorUtils.setAnimatedFraction(mAlarmAnimator, alarmFraction);
AnimatorUtils.setAnimatedFraction(mSnoozeAnimator, snoozeFraction);
AnimatorUtils.setAnimatedFraction(mDismissAnimator, dismissFraction);
}
private float getFraction(float x0, float x1, float x) {
return Math.max(Math.min((x - x0) / (x1 - x0), 1.0f), 0.0f);
}
private ValueAnimator getButtonAnimator(ImageView button, int tintColor) {
return ObjectAnimator.ofPropertyValuesHolder(button,
PropertyValuesHolder.ofFloat(View.SCALE_X, BUTTON_SCALE_DEFAULT, 1.0f),
PropertyValuesHolder.ofFloat(View.SCALE_Y, BUTTON_SCALE_DEFAULT, 1.0f),
PropertyValuesHolder.ofInt(AnimatorUtils.BACKGROUND_ALPHA, 0, 255),
PropertyValuesHolder.ofInt(AnimatorUtils.DRAWABLE_ALPHA,
BUTTON_DRAWABLE_ALPHA_DEFAULT, 255),
PropertyValuesHolder.ofObject(AnimatorUtils.DRAWABLE_TINT,
AnimatorUtils.ARGB_EVALUATOR, Color.WHITE, tintColor));
}
private ValueAnimator getAlarmBounceAnimator(float translationX, final int hintResId) {
final ValueAnimator bounceAnimator = ObjectAnimator.ofFloat(mAlarmButton,
View.TRANSLATION_X, mAlarmButton.getTranslationX(), translationX, 0.0f);
bounceAnimator.setInterpolator(AnimatorUtils.DECELERATE_ACCELERATE_INTERPOLATOR);
bounceAnimator.setDuration(ALARM_BOUNCE_DURATION_MILLIS);
bounceAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
mHintView.setText(hintResId);
if (mHintView.getVisibility() != View.VISIBLE) {
mHintView.setVisibility(View.VISIBLE);
ObjectAnimator.ofFloat(mHintView, View.ALPHA, 0.0f, 1.0f).start();
}
}
});
return bounceAnimator;
}
private Animator getAlertAnimator(final View source, final int titleResId,
final String infoText, final String accessibilityText, final int revealColor,
final int backgroundColor) {
final ViewGroup containerView = (ViewGroup) findViewById(android.R.id.content);
final Rect sourceBounds = new Rect(0, 0, source.getHeight(), source.getWidth());
containerView.offsetDescendantRectToMyCoords(source, sourceBounds);
final int centerX = sourceBounds.centerX();
final int centerY = sourceBounds.centerY();
final int xMax = Math.max(centerX, containerView.getWidth() - centerX);
final int yMax = Math.max(centerY, containerView.getHeight() - centerY);
final float startRadius = Math.max(sourceBounds.width(), sourceBounds.height()) / 2.0f;
final float endRadius = (float) Math.sqrt(xMax * xMax + yMax * yMax);
final CircleView revealView = new CircleView(this)
.setCenterX(centerX)
.setCenterY(centerY)
.setFillColor(revealColor);
containerView.addView(revealView);
// TODO: Fade out source icon over the reveal (like LOLLIPOP version).
final Animator revealAnimator = ObjectAnimator.ofFloat(
revealView, CircleView.RADIUS, startRadius, endRadius);
revealAnimator.setDuration(ALERT_REVEAL_DURATION_MILLIS);
revealAnimator.setInterpolator(REVEAL_INTERPOLATOR);
revealAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mAlertView.setVisibility(View.VISIBLE);
mAlertTitleView.setText(titleResId);
if (infoText != null) {
mAlertInfoView.setText(infoText);
mAlertInfoView.setVisibility(View.VISIBLE);
}
mContentView.setVisibility(View.GONE);
getWindow().setBackgroundDrawable(new ColorDrawable(backgroundColor));
}
});
final ValueAnimator fadeAnimator = ObjectAnimator.ofFloat(revealView, View.ALPHA, 0.0f);
fadeAnimator.setDuration(ALERT_FADE_DURATION_MILLIS);
fadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
containerView.removeView(revealView);
}
});
final AnimatorSet alertAnimator = new AnimatorSet();
alertAnimator.play(revealAnimator).before(fadeAnimator);
alertAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mAlertView.announceForAccessibility(accessibilityText);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
finish();
}
}, ALERT_DISMISS_DELAY_MILLIS);
}
});
return alertAnimator;
}
}