diff options
author | Jim Miller <jaggies@google.com> | 2012-06-11 21:06:13 -0700 |
---|---|---|
committer | Jim Miller <jaggies@google.com> | 2012-06-12 19:16:08 -0700 |
commit | 955a016922ea49f154d190b054a202559b41a4d3 (patch) | |
tree | 2031ecb839a21630269b8b93cd3b8ca9048aa469 | |
parent | 1c9581023ced89a209fca9c4e40305f8e0859732 (diff) | |
download | frameworks_base-955a016922ea49f154d190b054a202559b41a4d3.zip frameworks_base-955a016922ea49f154d190b054a202559b41a4d3.tar.gz frameworks_base-955a016922ea49f154d190b054a202559b41a4d3.tar.bz2 |
Fix 6613962: Update keyguard to use new GlowPadView UX design.
Change-Id: I4f1ef3107e5550f7df9dcb412943a84b66432b7d
28 files changed, 1655 insertions, 157 deletions
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java new file mode 100644 index 0000000..5096be6 --- /dev/null +++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java @@ -0,0 +1,1226 @@ +/* + * Copyright (C) 2012 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.internal.widget.multiwaveview; + +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Vibrator; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.R; + +import java.util.ArrayList; + +/** + * A re-usable widget containing a center, outer ring and wave animation. + */ +public class GlowPadView extends View { + private static final String TAG = "GlowPadView"; + private static final boolean DEBUG = false; + + // Wave state machine + private static final int STATE_IDLE = 0; + private static final int STATE_START = 1; + private static final int STATE_FIRST_TOUCH = 2; + private static final int STATE_TRACKING = 3; + private static final int STATE_SNAP = 4; + private static final int STATE_FINISH = 5; + + // Animation properties. + private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it + + public interface OnTriggerListener { + int NO_HANDLE = 0; + int CENTER_HANDLE = 1; + public void onGrabbed(View v, int handle); + public void onReleased(View v, int handle); + public void onTrigger(View v, int target); + public void onGrabbedStateChange(View v, int handle); + public void onFinishFinalAnimation(); + } + + // Tuneable parameters for animation + private static final int WAVE_ANIMATION_DURATION = 1200; + private static final int RETURN_TO_HOME_DELAY = 1200; + private static final int RETURN_TO_HOME_DURATION = 200; + private static final int HIDE_ANIMATION_DELAY = 200; + private static final int HIDE_ANIMATION_DURATION = 200; + private static final int SHOW_ANIMATION_DURATION = 200; + private static final int SHOW_ANIMATION_DELAY = 50; + private static final int INITIAL_SHOW_HANDLE_DURATION = 200; + private static final int REVEAL_GLOW_DELAY = 0; + private static final int REVEAL_GLOW_DURATION = 0; + + private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f; + private static final float TARGET_SCALE_EXPANDED = 1.0f; + private static final float TARGET_SCALE_COLLAPSED = 0.8f; + private static final float RING_SCALE_EXPANDED = 1.0f; + private static final float RING_SCALE_COLLAPSED = 0.5f; + + private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>(); + private AnimationBundle mWaveAnimations = new AnimationBundle(); + private AnimationBundle mTargetAnimations = new AnimationBundle(); + private AnimationBundle mGlowAnimations = new AnimationBundle(); + private ArrayList<String> mTargetDescriptions; + private ArrayList<String> mDirectionDescriptions; + private OnTriggerListener mOnTriggerListener; + private TargetDrawable mHandleDrawable; + private TargetDrawable mOuterRing; + private Vibrator mVibrator; + + private int mFeedbackCount = 3; + private int mVibrationDuration = 0; + private int mGrabbedState; + private int mActiveTarget = -1; + private float mGlowRadius; + private float mWaveCenterX; + private float mWaveCenterY; + private int mMaxTargetHeight; + private int mMaxTargetWidth; + + private float mOuterRadius = 0.0f; + private float mHitRadius = 0.0f; + private float mSnapMargin = 0.0f; + private boolean mDragging; + private int mNewTargetResources; + + private class AnimationBundle extends ArrayList<Tweener> { + private static final long serialVersionUID = 0xA84D78726F127468L; + private boolean mSuspended; + + public void start() { + if (mSuspended) return; // ignore attempts to start animations + final int count = size(); + for (int i = 0; i < count; i++) { + Tweener anim = get(i); + anim.animator.start(); + } + } + + public void cancel() { + final int count = size(); + for (int i = 0; i < count; i++) { + Tweener anim = get(i); + anim.animator.cancel(); + } + clear(); + } + + public void stop() { + final int count = size(); + for (int i = 0; i < count; i++) { + Tweener anim = get(i); + anim.animator.end(); + } + clear(); + } + + public void setSuspended(boolean suspend) { + mSuspended = suspend; + } + }; + + private AnimatorListener mResetListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { + switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); + dispatchOnFinishFinalAnimation(); + } + }; + + private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { + ping(); + switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY); + dispatchOnFinishFinalAnimation(); + } + }; + + private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + invalidate(); + } + }; + + private boolean mAnimatingTargets; + private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { + if (mNewTargetResources != 0) { + internalSetTargetResources(mNewTargetResources); + mNewTargetResources = 0; + hideTargets(false, false); + } + mAnimatingTargets = false; + } + }; + private int mTargetResourceId; + private int mTargetDescriptionsResourceId; + private int mDirectionDescriptionsResourceId; + private boolean mAlwaysTrackFinger; + private int mHorizontalInset; + private int mVerticalInset; + private int mGravity = Gravity.TOP; + private boolean mInitialLayout = true; + private Tweener mBackgroundAnimator; + private PointCloud mPointCloud; + private float mInnerRadius; + + public GlowPadView(Context context) { + this(context, null); + } + + public GlowPadView(Context context, AttributeSet attrs) { + super(context, attrs); + Resources res = context.getResources(); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView); + mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius); + mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius); + mHitRadius = a.getDimension(R.styleable.GlowPadView_hitRadius, mHitRadius); + mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin); + mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration, + mVibrationDuration); + mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount, + mFeedbackCount); + mHandleDrawable = new TargetDrawable(res, + a.peekValue(R.styleable.GlowPadView_handleDrawable).resourceId); + mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); + mOuterRing = new TargetDrawable(res, + getResourceId(a, R.styleable.GlowPadView_outerRingDrawable)); + + mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false); + + int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable); + Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null; + mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f); + + TypedValue outValue = new TypedValue(); + + // Read array of target drawables + if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) { + internalSetTargetResources(outValue.resourceId); + } + if (mTargetDrawables == null || mTargetDrawables.size() == 0) { + throw new IllegalStateException("Must specify at least one target drawable"); + } + + // Read array of target descriptions + if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) { + final int resourceId = outValue.resourceId; + if (resourceId == 0) { + throw new IllegalStateException("Must specify target descriptions"); + } + setTargetDescriptionsResourceId(resourceId); + } + + // Read array of direction descriptions + if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) { + final int resourceId = outValue.resourceId; + if (resourceId == 0) { + throw new IllegalStateException("Must specify direction descriptions"); + } + setDirectionDescriptionsResourceId(resourceId); + } + + a.recycle(); + + // Use gravity attribute from LinearLayout + a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout); + mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP); + a.recycle(); + + setVibrateEnabled(mVibrationDuration > 0); + + assignDefaultsIfNeeded(); + + mPointCloud = new PointCloud(pointDrawable); + mPointCloud.makePointCloud(mInnerRadius, mOuterRadius); + mPointCloud.glowManager.setRadius(mGlowRadius); + } + + private int getResourceId(TypedArray a, int id) { + TypedValue tv = a.peekValue(id); + return tv == null ? 0 : tv.resourceId; + } + + private void dump() { + Log.v(TAG, "Outer Radius = " + mOuterRadius); + Log.v(TAG, "HitRadius = " + mHitRadius); + Log.v(TAG, "SnapMargin = " + mSnapMargin); + Log.v(TAG, "FeedbackCount = " + mFeedbackCount); + Log.v(TAG, "VibrationDuration = " + mVibrationDuration); + Log.v(TAG, "GlowRadius = " + mGlowRadius); + Log.v(TAG, "WaveCenterX = " + mWaveCenterX); + Log.v(TAG, "WaveCenterY = " + mWaveCenterY); + } + + public void suspendAnimations() { + mWaveAnimations.setSuspended(true); + mTargetAnimations.setSuspended(true); + mGlowAnimations.setSuspended(true); + } + + public void resumeAnimations() { + mWaveAnimations.setSuspended(false); + mTargetAnimations.setSuspended(false); + mGlowAnimations.setSuspended(false); + mWaveAnimations.start(); + mTargetAnimations.start(); + mGlowAnimations.start(); + } + + @Override + protected int getSuggestedMinimumWidth() { + // View should be large enough to contain the background + handle and + // target drawable on either edge. + return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth); + } + + @Override + protected int getSuggestedMinimumHeight() { + // View should be large enough to contain the unlock ring + target and + // target drawable on either edge + return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight); + } + + private int resolveMeasured(int measureSpec, int desired) + { + int result = 0; + int specSize = MeasureSpec.getSize(measureSpec); + switch (MeasureSpec.getMode(measureSpec)) { + case MeasureSpec.UNSPECIFIED: + result = desired; + break; + case MeasureSpec.AT_MOST: + result = Math.min(specSize, desired); + break; + case MeasureSpec.EXACTLY: + default: + result = specSize; + } + return result; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int minimumWidth = getSuggestedMinimumWidth(); + final int minimumHeight = getSuggestedMinimumHeight(); + int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth); + int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight); + computeInsets((computedWidth - minimumWidth), (computedHeight - minimumHeight)); + setMeasuredDimension(computedWidth, computedHeight); + } + + private void switchToState(int state, float x, float y) { + switch (state) { + case STATE_IDLE: + deactivateTargets(); + hideGlow(0, 0, 0.0f, null); + startBackgroundAnimation(0, 0.0f); + mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE); + mHandleDrawable.setAlpha(1.0f); + break; + + case STATE_START: + startBackgroundAnimation(0, 0.0f); + break; + + case STATE_FIRST_TOUCH: + mHandleDrawable.setAlpha(0.0f); + deactivateTargets(); + showTargets(true); + startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f); + setGrabbedState(OnTriggerListener.CENTER_HANDLE); + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + announceTargets(); + } + break; + + case STATE_TRACKING: + mHandleDrawable.setAlpha(0.0f); + showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null); + break; + + case STATE_SNAP: + // TODO: Add transition states (see list_selector_background_transition.xml) + mHandleDrawable.setAlpha(0.0f); + showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null); + break; + + case STATE_FINISH: + doFinish(); + break; + } + } + + private void showGlow(int duration, int delay, float finalAlpha, + AnimatorListener finishListener) { + mGlowAnimations.cancel(); + mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration, + "ease", Ease.Cubic.easeIn, + "delay", delay, + "alpha", finalAlpha, + "onUpdate", mUpdateListener, + "onComplete", finishListener)); + mGlowAnimations.start(); + } + + private void hideGlow(int duration, int delay, float finalAlpha, + AnimatorListener finishListener) { + mGlowAnimations.cancel(); + mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration, + "ease", Ease.Quart.easeOut, + "delay", delay, + "alpha", finalAlpha, + "x", 0.0f, + "y", 0.0f, + "onUpdate", mUpdateListener, + "onComplete", finishListener)); + mGlowAnimations.start(); + } + + private void deactivateTargets() { + final int count = mTargetDrawables.size(); + for (int i = 0; i < count; i++) { + TargetDrawable target = mTargetDrawables.get(i); + target.setState(TargetDrawable.STATE_INACTIVE); + } + mActiveTarget = -1; + } + + /** + * Dispatches a trigger event to listener. Ignored if a listener is not set. + * @param whichTarget the target that was triggered. + */ + private void dispatchTriggerEvent(int whichTarget) { + vibrate(); + if (mOnTriggerListener != null) { + mOnTriggerListener.onTrigger(this, whichTarget); + } + } + + private void dispatchOnFinishFinalAnimation() { + if (mOnTriggerListener != null) { + mOnTriggerListener.onFinishFinalAnimation(); + } + } + + private void doFinish() { + final int activeTarget = mActiveTarget; + final boolean targetHit = activeTarget != -1; + + if (targetHit) { + if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit); + + highlightSelected(activeTarget); + + // Inform listener of any active targets. Typically only one will be active. + hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener); + dispatchTriggerEvent(activeTarget); + if (!mAlwaysTrackFinger) { + // Force ring and targets to finish animation to final expanded state + mTargetAnimations.stop(); + } + } else { + // Animate handle back to the center based on current state. + hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing); + hideTargets(true, false); + } + + setGrabbedState(OnTriggerListener.NO_HANDLE); + } + + private void highlightSelected(int activeTarget) { + // Highlight the given target and fade others + mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE); + hideUnselected(activeTarget); + } + + private void hideUnselected(int active) { + for (int i = 0; i < mTargetDrawables.size(); i++) { + if (i != active) { + mTargetDrawables.get(i).setAlpha(0.0f); + } + } + } + + private void hideTargets(boolean animate, boolean expanded) { + mTargetAnimations.cancel(); + // Note: these animations should complete at the same time so that we can swap out + // the target assets asynchronously from the setTargetResources() call. + mAnimatingTargets = animate; + final int duration = animate ? HIDE_ANIMATION_DURATION : 0; + final int delay = animate ? HIDE_ANIMATION_DELAY : 0; + + final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED; + final int length = mTargetDrawables.size(); + final TimeInterpolator interpolator = Ease.Cubic.easeOut; + for (int i = 0; i < length; i++) { + TargetDrawable target = mTargetDrawables.get(i); + target.setState(TargetDrawable.STATE_INACTIVE); + mTargetAnimations.add(Tweener.to(target, duration, + "ease", interpolator, + "alpha", 0.0f, + "scaleX", targetScale, + "scaleY", targetScale, + "delay", delay, + "onUpdate", mUpdateListener)); + } + + final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED; + mTargetAnimations.add(Tweener.to(mOuterRing, duration, + "ease", interpolator, + "alpha", 0.0f, + "scaleX", ringScaleTarget, + "scaleY", ringScaleTarget, + "delay", delay, + "onUpdate", mUpdateListener, + "onComplete", mTargetUpdateListener)); + + mTargetAnimations.start(); + } + + private void showTargets(boolean animate) { + mTargetAnimations.stop(); + mAnimatingTargets = animate; + final int delay = animate ? SHOW_ANIMATION_DELAY : 0; + final int duration = animate ? SHOW_ANIMATION_DURATION : 0; + final int length = mTargetDrawables.size(); + for (int i = 0; i < length; i++) { + TargetDrawable target = mTargetDrawables.get(i); + target.setState(TargetDrawable.STATE_INACTIVE); + mTargetAnimations.add(Tweener.to(target, duration, + "ease", Ease.Cubic.easeOut, + "alpha", 1.0f, + "scaleX", 1.0f, + "scaleY", 1.0f, + "delay", delay, + "onUpdate", mUpdateListener)); + } + mTargetAnimations.add(Tweener.to(mOuterRing, duration, + "ease", Ease.Cubic.easeOut, + "alpha", 1.0f, + "scaleX", 1.0f, + "scaleY", 1.0f, + "delay", delay, + "onUpdate", mUpdateListener, + "onComplete", mTargetUpdateListener)); + + mTargetAnimations.start(); + } + + private void vibrate() { + if (mVibrator != null) { + mVibrator.vibrate(mVibrationDuration); + } + } + + private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) { + Resources res = getContext().getResources(); + TypedArray array = res.obtainTypedArray(resourceId); + final int count = array.length(); + ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count); + for (int i = 0; i < count; i++) { + TypedValue value = array.peekValue(i); + TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0); + drawables.add(target); + } + array.recycle(); + return drawables; + } + + private void internalSetTargetResources(int resourceId) { + final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId); + mTargetDrawables = targets; + mTargetResourceId = resourceId; + + int maxWidth = mHandleDrawable.getWidth(); + int maxHeight = mHandleDrawable.getHeight(); + final int count = targets.size(); + for (int i = 0; i < count; i++) { + TargetDrawable target = targets.get(i); + maxWidth = Math.max(maxWidth, target.getWidth()); + maxHeight = Math.max(maxHeight, target.getHeight()); + } + if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) { + mMaxTargetWidth = maxWidth; + mMaxTargetHeight = maxHeight; + requestLayout(); // required to resize layout and call updateTargetPositions() + } else { + updateTargetPositions(mWaveCenterX, mWaveCenterY); + updatePointCloudPosition(mWaveCenterX, mWaveCenterY); + } + } + + /** + * Loads an array of drawables from the given resourceId. + * + * @param resourceId + */ + public void setTargetResources(int resourceId) { + if (mAnimatingTargets) { + // postpone this change until we return to the initial state + mNewTargetResources = resourceId; + } else { + internalSetTargetResources(resourceId); + } + } + + public int getTargetResourceId() { + return mTargetResourceId; + } + + /** + * Sets the resource id specifying the target descriptions for accessibility. + * + * @param resourceId The resource id. + */ + public void setTargetDescriptionsResourceId(int resourceId) { + mTargetDescriptionsResourceId = resourceId; + if (mTargetDescriptions != null) { + mTargetDescriptions.clear(); + } + } + + /** + * Gets the resource id specifying the target descriptions for accessibility. + * + * @return The resource id. + */ + public int getTargetDescriptionsResourceId() { + return mTargetDescriptionsResourceId; + } + + /** + * Sets the resource id specifying the target direction descriptions for accessibility. + * + * @param resourceId The resource id. + */ + public void setDirectionDescriptionsResourceId(int resourceId) { + mDirectionDescriptionsResourceId = resourceId; + if (mDirectionDescriptions != null) { + mDirectionDescriptions.clear(); + } + } + + /** + * Gets the resource id specifying the target direction descriptions. + * + * @return The resource id. + */ + public int getDirectionDescriptionsResourceId() { + return mDirectionDescriptionsResourceId; + } + + /** + * Enable or disable vibrate on touch. + * + * @param enabled + */ + public void setVibrateEnabled(boolean enabled) { + if (enabled && mVibrator == null) { + mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); + } else { + mVibrator = null; + } + } + + /** + * Starts wave animation. + * + */ + public void ping() { + if (mFeedbackCount > 0) { + startWaveAnimation(); + } + } + + private void stopAndHideWaveAnimation() { + mWaveAnimations.cancel(); + mPointCloud.waveManager.setAlpha(0.0f); + } + + private void startWaveAnimation() { + mWaveAnimations.cancel(); + mPointCloud.waveManager.setAlpha(1.0f); + mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f); + mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION, + "ease", Ease.Linear.easeNone, + "delay", 0, + "radius", 2.0f * mOuterRadius, + "onUpdate", mUpdateListener, + "onComplete", + new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animator) { + mPointCloud.waveManager.setRadius(0.0f); + mPointCloud.waveManager.setAlpha(0.0f); + } + })); + mWaveAnimations.start(); + } + + /** + * Resets the widget to default state and cancels all animation. If animate is 'true', will + * animate objects into place. Otherwise, objects will snap back to place. + * + * @param animate + */ + public void reset(boolean animate) { + mGlowAnimations.stop(); + mTargetAnimations.stop(); + startBackgroundAnimation(0, 0.0f); + stopAndHideWaveAnimation(); + hideTargets(animate, false); + hideGlow(0, 0, 1.0f, null); + Tweener.reset(); + } + + private void startBackgroundAnimation(int duration, float alpha) { + final Drawable background = getBackground(); + if (mAlwaysTrackFinger && background != null) { + if (mBackgroundAnimator != null) { + mBackgroundAnimator.animator.cancel(); + } + mBackgroundAnimator = Tweener.to(background, duration, + "ease", Ease.Cubic.easeIn, + "alpha", (int)(255.0f * alpha), + "delay", SHOW_ANIMATION_DELAY); + mBackgroundAnimator.animator.start(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getAction(); + boolean handled = false; + switch (action) { + case MotionEvent.ACTION_DOWN: + if (DEBUG) Log.v(TAG, "*** DOWN ***"); + handleDown(event); + handleMove(event); + handled = true; + break; + + case MotionEvent.ACTION_MOVE: + if (DEBUG) Log.v(TAG, "*** MOVE ***"); + handleMove(event); + handled = true; + break; + + case MotionEvent.ACTION_UP: + if (DEBUG) Log.v(TAG, "*** UP ***"); + handleMove(event); + handleUp(event); + handled = true; + break; + + case MotionEvent.ACTION_CANCEL: + if (DEBUG) Log.v(TAG, "*** CANCEL ***"); + handleMove(event); + handleCancel(event); + handled = true; + break; + } + invalidate(); + return handled ? true : super.onTouchEvent(event); + } + + private void updateGlowPosition(float x, float y) { + mPointCloud.glowManager.setX(x); + mPointCloud.glowManager.setY(y); + } + + private void handleDown(MotionEvent event) { + float eventX = event.getX(); + float eventY = event.getY(); + switchToState(STATE_START, eventX, eventY); + if (!trySwitchToFirstTouchState(eventX, eventY)) { + mDragging = false; + } else { + updateGlowPosition(eventX, eventY); + } + } + + private void handleUp(MotionEvent event) { + if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE"); + switchToState(STATE_FINISH, event.getX(), event.getY()); + } + + private void handleCancel(MotionEvent event) { + if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL"); + + // We should drop the active target here but it interferes with + // moving off the screen in the direction of the navigation bar. At some point we may + // want to revisit how we handle this. For now we'll allow a canceled event to + // activate the current target. + + // mActiveTarget = -1; // Drop the active target if canceled. + + switchToState(STATE_FINISH, event.getX(), event.getY()); + } + + private void handleMove(MotionEvent event) { + int activeTarget = -1; + final int historySize = event.getHistorySize(); + ArrayList<TargetDrawable> targets = mTargetDrawables; + int ntargets = targets.size(); + final boolean singleTarget = ntargets == 1; + float x = 0.0f; + float y = 0.0f; + for (int k = 0; k < historySize + 1; k++) { + float eventX = k < historySize ? event.getHistoricalX(k) : event.getX(); + float eventY = k < historySize ? event.getHistoricalY(k) : event.getY(); + // tx and ty are relative to wave center + float tx = eventX - mWaveCenterX; + float ty = eventY - mWaveCenterY; + float touchRadius = (float) Math.sqrt(dist2(tx, ty)); + final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f; + float limitX = tx * scale; + float limitY = ty * scale; + + if (!mDragging) { + trySwitchToFirstTouchState(eventX, eventY); + } + + if (mDragging) { + if (singleTarget) { + // Snap to outer ring if there's only one target + float snapRadius = mOuterRadius - mSnapMargin; + if (touchRadius > snapRadius) { + activeTarget = 0; + } + } else { + // For more than one target, snap to the closest one less than hitRadius away. + float best = Float.MAX_VALUE; + final float hitRadius2 = mHitRadius * mHitRadius; + // Find first target in range + for (int i = 0; i < ntargets; i++) { + TargetDrawable target = targets.get(i); + float dx = limitX - target.getX(); + float dy = limitY - target.getY(); + float dist2 = dx*dx + dy*dy; + if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) { + activeTarget = i; + best = dist2; + } + } + } + } + x = limitX; + y = limitY; + } + + if (!mDragging) { + return; + } + + if (activeTarget != -1) { + switchToState(STATE_SNAP, x,y); + TargetDrawable target = targets.get(activeTarget); + final float newX = singleTarget ? x : target.getX(); + final float newY = singleTarget ? y : target.getY(); + updateGlowPosition(newX, newY); + } else { + switchToState(STATE_TRACKING, x, y); + updateGlowPosition(x, y); + } + + if (mActiveTarget != activeTarget) { + // Defocus the old target + if (mActiveTarget != -1) { + TargetDrawable target = targets.get(mActiveTarget); + if (target.hasState(TargetDrawable.STATE_FOCUSED)) { + target.setState(TargetDrawable.STATE_INACTIVE); + } + } + // Focus the new target + if (activeTarget != -1) { + TargetDrawable target = targets.get(activeTarget); + if (target.hasState(TargetDrawable.STATE_FOCUSED)) { + target.setState(TargetDrawable.STATE_FOCUSED); + } + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + String targetContentDescription = getTargetDescription(activeTarget); + announceText(targetContentDescription); + } + } + } + mActiveTarget = activeTarget; + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + event.setAction(MotionEvent.ACTION_DOWN); + break; + case MotionEvent.ACTION_HOVER_MOVE: + event.setAction(MotionEvent.ACTION_MOVE); + break; + case MotionEvent.ACTION_HOVER_EXIT: + event.setAction(MotionEvent.ACTION_UP); + break; + } + onTouchEvent(event); + event.setAction(action); + } + return super.onHoverEvent(event); + } + + /** + * Sets the current grabbed state, and dispatches a grabbed state change + * event to our listener. + */ + private void setGrabbedState(int newState) { + if (newState != mGrabbedState) { + if (newState != OnTriggerListener.NO_HANDLE) { + vibrate(); + } + mGrabbedState = newState; + if (mOnTriggerListener != null) { + if (newState == OnTriggerListener.NO_HANDLE) { + mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE); + } else { + mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE); + } + mOnTriggerListener.onGrabbedStateChange(this, newState); + } + } + } + + private boolean trySwitchToFirstTouchState(float x, float y) { + final float tx = x - mWaveCenterX; + final float ty = y - mWaveCenterY; + if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) { + if (DEBUG) Log.v(TAG, "** Handle HIT"); + switchToState(STATE_FIRST_TOUCH, x, y); + updateGlowPosition(tx, ty); + mDragging = true; + return true; + } + return false; + } + + private void assignDefaultsIfNeeded() { + if (mOuterRadius == 0.0f) { + mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f; + } + if (mHitRadius == 0.0f) { + // Use the radius of inscribed circle of the first target. + mHitRadius = mTargetDrawables.get(0).getWidth() / 2.0f; + } + if (mSnapMargin == 0.0f) { + mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics()); + } + if (mInnerRadius == 0.0f) { + mInnerRadius = mHandleDrawable.getWidth() / 10.0f; + } + } + + private void computeInsets(int dx, int dy) { + final int layoutDirection = getResolvedLayoutDirection(); + final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); + + switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { + case Gravity.LEFT: + mHorizontalInset = 0; + break; + case Gravity.RIGHT: + mHorizontalInset = dx; + break; + case Gravity.CENTER_HORIZONTAL: + default: + mHorizontalInset = dx / 2; + break; + } + switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) { + case Gravity.TOP: + mVerticalInset = 0; + break; + case Gravity.BOTTOM: + mVerticalInset = dy; + break; + case Gravity.CENTER_VERTICAL: + default: + mVerticalInset = dy / 2; + break; + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + final int width = right - left; + final int height = bottom - top; + + // Target placement width/height. This puts the targets on the greater of the ring + // width or the specified outer radius. + final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius); + final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius); + float newWaveCenterX = mHorizontalInset + + Math.max(width, mMaxTargetWidth + placementWidth) / 2; + float newWaveCenterY = mVerticalInset + + Math.max(height, + mMaxTargetHeight + placementHeight) / 2; + + if (mInitialLayout) { + stopAndHideWaveAnimation(); + hideTargets(false, false); + mInitialLayout = false; + } + + mOuterRing.setPositionX(newWaveCenterX); + mOuterRing.setPositionY(newWaveCenterY); + + mHandleDrawable.setPositionX(newWaveCenterX); + mHandleDrawable.setPositionY(newWaveCenterY); + + updateTargetPositions(newWaveCenterX, newWaveCenterY); + updatePointCloudPosition(newWaveCenterX, newWaveCenterY); + updateGlowPosition(newWaveCenterX, newWaveCenterY); + + mWaveCenterX = newWaveCenterX; + mWaveCenterY = newWaveCenterY; + + if (DEBUG) dump(); + } + + private void updateTargetPositions(float centerX, float centerY) { + // Reposition the target drawables if the view changed. + ArrayList<TargetDrawable> targets = mTargetDrawables; + final int size = targets.size(); + final float alpha = (float) (-2.0f * Math.PI / size); + for (int i = 0; i < size; i++) { + final TargetDrawable targetIcon = targets.get(i); + final float angle = alpha * i; + targetIcon.setPositionX(centerX); + targetIcon.setPositionY(centerY); + targetIcon.setX(mOuterRadius * (float) Math.cos(angle)); + targetIcon.setY(mOuterRadius * (float) Math.sin(angle)); + } + } + + private void updatePointCloudPosition(float centerX, float centerY) { + mPointCloud.setCenter(centerX, centerY); + } + + @Override + protected void onDraw(Canvas canvas) { + mPointCloud.draw(canvas); + mOuterRing.draw(canvas); + final int ntargets = mTargetDrawables.size(); + for (int i = 0; i < ntargets; i++) { + TargetDrawable target = mTargetDrawables.get(i); + if (target != null) { + target.draw(canvas); + } + } + mHandleDrawable.draw(canvas); + } + + public void setOnTriggerListener(OnTriggerListener listener) { + mOnTriggerListener = listener; + } + + private float square(float d) { + return d * d; + } + + private float dist2(float dx, float dy) { + return dx*dx + dy*dy; + } + + private float getScaledGlowRadiusSquared() { + final float scaledTapRadius; + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius; + } else { + scaledTapRadius = mGlowRadius; + } + return square(scaledTapRadius); + } + + private void announceTargets() { + StringBuilder utterance = new StringBuilder(); + final int targetCount = mTargetDrawables.size(); + for (int i = 0; i < targetCount; i++) { + String targetDescription = getTargetDescription(i); + String directionDescription = getDirectionDescription(i); + if (!TextUtils.isEmpty(targetDescription) + && !TextUtils.isEmpty(directionDescription)) { + String text = String.format(directionDescription, targetDescription); + utterance.append(text); + } + if (utterance.length() > 0) { + announceText(utterance.toString()); + } + } + } + + private void announceText(String text) { + setContentDescription(text); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + setContentDescription(null); + } + + private String getTargetDescription(int index) { + if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) { + mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId); + if (mTargetDrawables.size() != mTargetDescriptions.size()) { + Log.w(TAG, "The number of target drawables must be" + + " equal to the number of target descriptions."); + return null; + } + } + return mTargetDescriptions.get(index); + } + + private String getDirectionDescription(int index) { + if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) { + mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId); + if (mTargetDrawables.size() != mDirectionDescriptions.size()) { + Log.w(TAG, "The number of target drawables must be" + + " equal to the number of direction descriptions."); + return null; + } + } + return mDirectionDescriptions.get(index); + } + + private ArrayList<String> loadDescriptions(int resourceId) { + TypedArray array = getContext().getResources().obtainTypedArray(resourceId); + final int count = array.length(); + ArrayList<String> targetContentDescriptions = new ArrayList<String>(count); + for (int i = 0; i < count; i++) { + String contentDescription = array.getString(i); + targetContentDescriptions.add(contentDescription); + } + array.recycle(); + return targetContentDescriptions; + } + + public int getResourceIdForTarget(int index) { + final TargetDrawable drawable = mTargetDrawables.get(index); + return drawable == null ? 0 : drawable.getResourceId(); + } + + public void setEnableTarget(int resourceId, boolean enabled) { + for (int i = 0; i < mTargetDrawables.size(); i++) { + final TargetDrawable target = mTargetDrawables.get(i); + if (target.getResourceId() == resourceId) { + target.setEnabled(enabled); + break; // should never be more than one match + } + } + } + + /** + * Gets the position of a target in the array that matches the given resource. + * @param resourceId + * @return the index or -1 if not found + */ + public int getTargetPosition(int resourceId) { + for (int i = 0; i < mTargetDrawables.size(); i++) { + final TargetDrawable target = mTargetDrawables.get(i); + if (target.getResourceId() == resourceId) { + return i; // should never be more than one match + } + } + return -1; + } + + private boolean replaceTargetDrawables(Resources res, int existingResourceId, + int newResourceId) { + if (existingResourceId == 0 || newResourceId == 0) { + return false; + } + + boolean result = false; + final ArrayList<TargetDrawable> drawables = mTargetDrawables; + final int size = drawables.size(); + for (int i = 0; i < size; i++) { + final TargetDrawable target = drawables.get(i); + if (target != null && target.getResourceId() == existingResourceId) { + target.setDrawable(res, newResourceId); + result = true; + } + } + + if (result) { + requestLayout(); // in case any given drawable's size changes + } + + return result; + } + + /** + * Searches the given package for a resource to use to replace the Drawable on the + * target with the given resource id + * @param component of the .apk that contains the resource + * @param name of the metadata in the .apk + * @param existingResId the resource id of the target to search for + * @return true if found in the given package and replaced at least one target Drawables + */ + public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name, + int existingResId) { + if (existingResId == 0) return false; + + try { + PackageManager packageManager = mContext.getPackageManager(); + // Look for the search icon specified in the activity meta-data + Bundle metaData = packageManager.getActivityInfo( + component, PackageManager.GET_META_DATA).metaData; + if (metaData != null) { + int iconResId = metaData.getInt(name); + if (iconResId != 0) { + Resources res = packageManager.getResourcesForActivity(component); + return replaceTargetDrawables(res, existingResId, iconResId); + } + } + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to swap drawable; " + + component.flattenToShortString() + " not found", e); + } catch (Resources.NotFoundException nfe) { + Log.w(TAG, "Failed to swap drawable from " + + component.flattenToShortString(), nfe); + } + return false; + } +} diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java index 89dbd1b..afeac00 100644 --- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java +++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java @@ -117,8 +117,6 @@ public class MultiWaveView extends View { private float mWaveCenterY; private int mMaxTargetHeight; private int mMaxTargetWidth; - private float mHorizontalOffset; - private float mVerticalOffset; private float mOuterRadius = 0.0f; private float mHitRadius = 0.0f; @@ -215,9 +213,6 @@ public class MultiWaveView extends View { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView); mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius); -// mHorizontalOffset = a.getDimension(R.styleable.MultiWaveView_horizontalOffset, -// mHorizontalOffset); -// mVerticalOffset = a.getDimension(R.styleable.MultiWaveView_verticalOffset, mVerticalOffset); mHitRadius = a.getDimension(R.styleable.MultiWaveView_hitRadius, mHitRadius); mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin); mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration, @@ -230,7 +225,6 @@ public class MultiWaveView extends View { mOuterRing = new TargetDrawable(res, a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId); mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false); - mGravity = a.getInt(R.styleable.MultiWaveView_gravity, Gravity.TOP); // Read array of chevron drawables TypedValue outValue = new TypedValue(); @@ -244,24 +238,6 @@ public class MultiWaveView extends View { } } - // Support old-style chevron specification if new specification not found - if (mChevronDrawables.size() == 0) { - final int chevronResIds[] = { - R.styleable.MultiWaveView_rightChevronDrawable, - R.styleable.MultiWaveView_topChevronDrawable, - R.styleable.MultiWaveView_leftChevronDrawable, - R.styleable.MultiWaveView_bottomChevronDrawable - }; - - for (int i = 0; i < chevronResIds.length; i++) { - TypedValue typedValue = a.peekValue(chevronResIds[i]); - for (int k = 0; k < mFeedbackCount; k++) { - mChevronDrawables.add( - typedValue != null ? new TargetDrawable(res, typedValue.resourceId) : null); - } - } - } - // Read array of target drawables if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) { internalSetTargetResources(outValue.resourceId); @@ -289,6 +265,12 @@ public class MultiWaveView extends View { } a.recycle(); + + // Use gravity attribute from LinearLayout + a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout); + mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP); + a.recycle(); + setVibrateEnabled(mVibrationDuration > 0); assignDefaultsIfNeeded(); } @@ -302,8 +284,6 @@ public class MultiWaveView extends View { Log.v(TAG, "TapRadius = " + mTapRadius); Log.v(TAG, "WaveCenterX = " + mWaveCenterX); Log.v(TAG, "WaveCenterY = " + mWaveCenterY); - Log.v(TAG, "HorizontalOffset = " + mHorizontalOffset); - Log.v(TAG, "VerticalOffset = " + mVerticalOffset); } public void suspendAnimations() { @@ -1042,9 +1022,9 @@ public class MultiWaveView extends View { // width or the specified outer radius. final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius); final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius); - float newWaveCenterX = mHorizontalOffset + mHorizontalInset + float newWaveCenterX = mHorizontalInset + Math.max(width, mMaxTargetWidth + placementWidth) / 2; - float newWaveCenterY = mVerticalOffset + mVerticalInset + float newWaveCenterY = mVerticalInset + Math.max(height, + mMaxTargetHeight + placementHeight) / 2; if (mInitialLayout) { diff --git a/core/java/com/android/internal/widget/multiwaveview/PointCloud.java b/core/java/com/android/internal/widget/multiwaveview/PointCloud.java new file mode 100644 index 0000000..2ef8c78 --- /dev/null +++ b/core/java/com/android/internal/widget/multiwaveview/PointCloud.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 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.internal.widget.multiwaveview; + +import java.util.ArrayList; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.util.FloatMath; +import android.util.Log; + +public class PointCloud { + private static final float MIN_POINT_SIZE = 2.0f; + private static final float MAX_POINT_SIZE = 4.0f; + private static final int INNER_POINTS = 8; + private static final String TAG = "PointCloud"; + private ArrayList<Point> mPointCloud = new ArrayList<Point>(); + private Drawable mDrawable; + private float mCenterX; + private float mCenterY; + private Paint mPaint; + private float mScale = 1.0f; + private static final float PI = (float) Math.PI; + + // These allow us to have multiple concurrent animations. + WaveManager waveManager = new WaveManager(); + GlowManager glowManager = new GlowManager(); + private float mOuterRadius; + + public class WaveManager { + private float radius = 50; + private float width = 200.0f; // TODO: Make configurable + private float alpha = 0.0f; + public void setRadius(float r) { + radius = r; + } + + public float getRadius() { + return radius; + } + + public void setAlpha(float a) { + alpha = a; + } + + public float getAlpha() { + return alpha; + } + }; + + public class GlowManager { + private float x; + private float y; + private float radius = 0.0f; + private float alpha = 0.0f; + + public void setX(float x1) { + x = x1; + } + + public float getX() { + return x; + } + + public void setY(float y1) { + y = y1; + } + + public float getY() { + return y; + } + + public void setAlpha(float a) { + alpha = a; + } + + public float getAlpha() { + return alpha; + } + + public void setRadius(float r) { + radius = r; + } + + public float getRadius() { + return radius; + } + } + + class Point { + float x; + float y; + float radius; + + public Point(float x2, float y2, float r) { + x = (float) x2; + y = (float) y2; + radius = r; + } + } + + public PointCloud(Drawable drawable) { + mPaint = new Paint(); + mPaint.setFilterBitmap(true); + mPaint.setColor(Color.rgb(255, 255, 255)); // TODO: make configurable + mPaint.setAntiAlias(true); + mPaint.setDither(true); + + mDrawable = drawable; + if (mDrawable != null) { + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + } + } + + public void setCenter(float x, float y) { + mCenterX = x; + mCenterY = y; + } + + public void makePointCloud(float innerRadius, float outerRadius) { + if (innerRadius == 0) { + Log.w(TAG, "Must specify an inner radius"); + return; + } + mOuterRadius = outerRadius; + mPointCloud.clear(); + final float pointAreaRadius = (outerRadius - innerRadius); + final float ds = (2.0f * PI * innerRadius / INNER_POINTS); + final int bands = (int) Math.round(pointAreaRadius / ds); + final float dr = pointAreaRadius / bands; + float r = innerRadius; + for (int b = 0; b <= bands; b++, r += dr) { + float circumference = 2.0f * PI * r; + final int pointsInBand = (int) (circumference / ds); + float eta = PI/2.0f; + float dEta = 2.0f * PI / pointsInBand; + for (int i = 0; i < pointsInBand; i++) { + float x = r * FloatMath.cos(eta); + float y = r * FloatMath.sin(eta); + eta += dEta; + mPointCloud.add(new Point(x, y, r)); + } + } + } + + public void setScale(float scale) { + mScale = scale; + } + + public float getScale() { + return mScale; + } + + private static float hypot(float x, float y) { + return FloatMath.sqrt(x*x + y*y); + } + + private static float max(float a, float b) { + return a > b ? a : b; + } + + public int getAlphaForPoint(Point point) { + // Contribution from positional glow + float glowDistance = hypot(glowManager.x - point.x, glowManager.y - point.y); + float glowAlpha = 0.0f; + if (glowDistance < glowManager.radius) { + float cosf = FloatMath.cos(PI * 0.5f * glowDistance / glowManager.radius); + glowAlpha = glowManager.alpha * max(0.0f, (float) Math.pow(cosf, 0.5f)); + } + + // Compute contribution from Wave + float radius = hypot(point.x, point.y); + float distanceToWaveRing = Math.abs(radius - waveManager.radius); + float waveAlpha = 0.0f; + if (distanceToWaveRing < waveManager.width * 0.5f) { + float cosf = FloatMath.cos(PI * 0.5f * distanceToWaveRing / waveManager.width); + waveAlpha = waveManager.alpha * max(0.0f, (float) Math.pow(cosf, 15.0f)); + } + + return (int) (max(glowAlpha, waveAlpha) * 255); + } + + private float interp(float min, float max, float f) { + return min + (max - min) * f; + } + + public void draw(Canvas canvas) { + ArrayList<Point> points = mPointCloud; + final float cx = mDrawable != null ? (-mDrawable.getIntrinsicWidth() / 2) : 0; + final float cy = mDrawable != null ? (-mDrawable.getIntrinsicHeight() / 2) : 0; + canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.scale(mScale, mScale, mCenterX, mCenterY); + for (int i = 0; i < points.size(); i++) { + Point point = points.get(i); + final float pointSize = interp(MAX_POINT_SIZE, MIN_POINT_SIZE, + point.radius / mOuterRadius); + final float px = point.x + cx + mCenterX; + final float py = point.y + cy + mCenterY; + int alpha = getAlphaForPoint(point); + + if (alpha == 0) continue; + + if (mDrawable != null) { + canvas.save(Canvas.MATRIX_SAVE_FLAG); + float s = pointSize / MAX_POINT_SIZE; + canvas.scale(s, s, px, py); + canvas.translate(px, py); + mDrawable.setAlpha(alpha); + mDrawable.draw(canvas); + canvas.restore(); + } else { + mPaint.setAlpha(alpha); + canvas.drawCircle(px, py, pointSize, mPaint); + } + } + canvas.restore(); + } + +} diff --git a/core/res/res/drawable-hdpi/ic_lockscreen_glowdot.png b/core/res/res/drawable-hdpi/ic_lockscreen_glowdot.png Binary files differnew file mode 100644 index 0000000..983c45e --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_lockscreen_glowdot.png diff --git a/core/res/res/drawable-mdpi/ic_lockscreen_glowdot.png b/core/res/res/drawable-mdpi/ic_lockscreen_glowdot.png Binary files differnew file mode 100644 index 0000000..056c3f17 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_lockscreen_glowdot.png diff --git a/core/res/res/drawable-xhdpi/ic_lockscreen_glowdot.png b/core/res/res/drawable-xhdpi/ic_lockscreen_glowdot.png Binary files differnew file mode 100644 index 0000000..cbd039a --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_lockscreen_glowdot.png diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml index a666077..cd9c913 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml @@ -82,7 +82,7 @@ android:drawablePadding="4dip" /> - <com.android.internal.widget.multiwaveview.MultiWaveView + <com.android.internal.widget.multiwaveview.GlowPadView android:id="@+id/unlock_widget" android:orientation="horizontal" android:layout_width="wrap_content" @@ -94,13 +94,15 @@ android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" android:directionDescriptions="@array/lockscreen_direction_descriptions" android:handleDrawable="@drawable/ic_lockscreen_handle" - android:waveDrawable="@drawable/ic_lockscreen_outerring" - android:outerRadius="@dimen/multiwaveview_target_placement_radius" - android:snapMargin="@dimen/multiwaveview_snap_margin" - android:hitRadius="@dimen/multiwaveview_hit_radius" - android:chevronDrawables="@array/lockscreen_chevron_drawables" - android:feedbackCount="3" + android:outerRingDrawable="@drawable/ic_lockscreen_outerring" + android:outerRadius="@dimen/glowpadview_target_placement_radius" + android:innerRadius="@dimen/glowpadview_inner_radius" + android:snapMargin="@dimen/glowpadview_snap_margin" + android:hitRadius="@dimen/glowpadview_hit_radius" + android:feedbackCount="1" android:vibrationDuration="20" + android:glowRadius="@dimen/glowpadview_glow_radius" + android:pointDrawable="@drawable/ic_lockscreen_glowdot" /> <!-- emergency call button shown when sim is PUKd and tab_selector is hidden --> diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml index 17a3c84..32ca602 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml @@ -82,7 +82,7 @@ android:layout_alignParentTop="true" android:drawablePadding="4dip"/> - <com.android.internal.widget.multiwaveview.MultiWaveView + <com.android.internal.widget.multiwaveview.GlowPadView android:id="@+id/unlock_widget" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -94,13 +94,15 @@ android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" android:directionDescriptions="@array/lockscreen_direction_descriptions" android:handleDrawable="@drawable/ic_lockscreen_handle" - android:waveDrawable="@drawable/ic_lockscreen_outerring" - android:outerRadius="@dimen/multiwaveview_target_placement_radius" - android:snapMargin="@dimen/multiwaveview_snap_margin" - android:hitRadius="@dimen/multiwaveview_hit_radius" - android:chevronDrawables="@array/lockscreen_chevron_drawables" - android:feedbackCount="3" + android:outerRingDrawable="@drawable/ic_lockscreen_outerring" + android:outerRadius="@dimen/glowpadview_target_placement_radius" + android:innerRadius="@dimen/glowpadview_inner_radius" + android:snapMargin="@dimen/glowpadview_snap_margin" + android:hitRadius="@dimen/glowpadview_hit_radius" + android:feedbackCount="1" android:vibrationDuration="20" + android:glowRadius="@dimen/glowpadview_glow_radius" + android:pointDrawable="@drawable/ic_lockscreen_glowdot" /> <!-- emergency call button shown when sim is PUKd and tab_selector is hidden --> diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml index 2dcb774..4e646a6 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml @@ -123,7 +123,7 @@ android:layout_width="match_parent" android:layout_height="302dip"> - <com.android.internal.widget.multiwaveview.MultiWaveView + <com.android.internal.widget.multiwaveview.GlowPadView android:id="@+id/unlock_widget" android:orientation="horizontal" android:layout_width="match_parent" @@ -135,13 +135,15 @@ android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" android:directionDescriptions="@array/lockscreen_direction_descriptions" android:handleDrawable="@drawable/ic_lockscreen_handle" - android:waveDrawable="@drawable/ic_lockscreen_outerring" - android:outerRadius="@dimen/multiwaveview_target_placement_radius" - android:snapMargin="@dimen/multiwaveview_snap_margin" - android:hitRadius="@dimen/multiwaveview_hit_radius" - android:chevronDrawables="@array/lockscreen_chevron_drawables" - android:feedbackCount="3" + android:outerRingDrawable="@drawable/ic_lockscreen_outerring" + android:outerRadius="@dimen/glowpadview_target_placement_radius" + android:innerRadius="@dimen/glowpadview_inner_radius" + android:snapMargin="@dimen/glowpadview_snap_margin" + android:hitRadius="@dimen/glowpadview_hit_radius" + android:feedbackCount="1" android:vibrationDuration="20" + android:glowRadius="@dimen/glowpadview_glow_radius" + android:pointDrawable="@drawable/ic_lockscreen_glowdot" /> <TextView diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index 10ddd1e..5a357fb 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -129,24 +129,26 @@ <Space android:layout_width="64dip" android:layout_rowSpan="7" /> <!-- Column 2 --> - <com.android.internal.widget.multiwaveview.MultiWaveView + <com.android.internal.widget.multiwaveview.GlowPadView android:id="@+id/unlock_widget" android:layout_width="302dip" android:layout_height="match_parent" android:layout_rowSpan="7" - android:gravity="center" + android:gravity="left|center_vertical" android:targetDrawables="@array/lockscreen_targets_with_camera" android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera" android:directionDescriptions="@array/lockscreen_direction_descriptions" android:handleDrawable="@drawable/ic_lockscreen_handle" - android:waveDrawable="@drawable/ic_lockscreen_outerring" - android:outerRadius="@dimen/multiwaveview_target_placement_radius" - android:snapMargin="@dimen/multiwaveview_snap_margin" - android:hitRadius="@dimen/multiwaveview_hit_radius" - android:chevronDrawables="@array/lockscreen_chevron_drawables" - android:feedbackCount="3" + android:outerRingDrawable="@drawable/ic_lockscreen_outerring" + android:outerRadius="@dimen/glowpadview_target_placement_radius" + android:innerRadius="@dimen/glowpadview_inner_radius" + android:snapMargin="@dimen/glowpadview_snap_margin" + android:hitRadius="@dimen/glowpadview_hit_radius" + android:feedbackCount="1" android:vibrationDuration="20" + android:glowRadius="@dimen/glowpadview_glow_radius" + android:pointDrawable="@drawable/ic_lockscreen_glowdot" /> <!-- Music transport control --> diff --git a/core/res/res/values-land/arrays.xml b/core/res/res/values-land/arrays.xml index 7095c02..f2df3fa 100644 --- a/core/res/res/values-land/arrays.xml +++ b/core/res/res/values-land/arrays.xml @@ -19,7 +19,7 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Resources for MultiWaveView in LockScreen --> + <!-- Resources for GlowPadView in LockScreen --> <array name="lockscreen_targets_when_silent"> <item>@null</item>" <item>@drawable/ic_lockscreen_unlock</item> diff --git a/core/res/res/values-sw600dp-land/arrays.xml b/core/res/res/values-sw600dp-land/arrays.xml index 6a09cf8..2b5fd99 100644 --- a/core/res/res/values-sw600dp-land/arrays.xml +++ b/core/res/res/values-sw600dp-land/arrays.xml @@ -19,7 +19,7 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Resources for MultiWaveView in LockScreen --> + <!-- Resources for GlowPadView in LockScreen --> <array name="lockscreen_targets_when_silent"> <item>@drawable/ic_lockscreen_unlock</item> <item>@null</item> diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index d26666e..8937c2a 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -45,8 +45,8 @@ <!-- Size of lockscreen outerring on unsecure unlock LockScreen --> <dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen> - <!-- target placement radius for MultiWaveView --> - <dimen name="multiwaveview_target_placement_radius">182dip</dimen> + <!-- target placement radius for GlowPadView. Should be 1/2 of outerring diameter. --> + <dimen name="glowpadview_target_placement_radius">182dip</dimen> <!-- Size of status line font in LockScreen. --> <dimen name="keyguard_pattern_unlock_status_line_font_size">14sp</dimen> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 1eeca59..aeb6b4f 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -350,7 +350,7 @@ <item>中文 (繁體)</item> </string-array> - <!-- Resources for MultiWaveView in LockScreen --> + <!-- Resources for GlowPadView in LockScreen --> <array name="lockscreen_targets_when_silent"> <item>@drawable/ic_lockscreen_unlock</item> <item>@drawable/ic_lockscreen_search</item> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index a68011d..00077512 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5377,6 +5377,54 @@ </declare-styleable> <!-- =============================== --> + <!-- GlowPadView class attributes --> + <!-- =============================== --> + <eat-comment /> + <declare-styleable name="GlowPadView"> + <!-- Reference to an array resource that be shown as targets around a circle. --> + <attr name="targetDrawables"/> + + <!-- Reference to an array resource that be used as description for the targets around the circle. --> + <attr name="targetDescriptions"/> + + <!-- Reference to an array resource that be used to announce the directions with targets around the circle. --> + <attr name="directionDescriptions"/> + + <!-- Sets a drawable as the center. --> + <attr name="handleDrawable"/> + + <!-- Drawable to use for wave ripple animation. --> + <attr name="outerRingDrawable" format="reference"/> + + <!-- Drawble used for drawing points --> + <attr name="pointDrawable" format="reference" /> + + <!-- Inner radius of glow area. --> + <attr name="innerRadius"/> + + <!-- Outer radius of glow area. Target icons will be drawn on this circle. --> + <attr name="outerRadius"/> + + <!-- Size of target radius. Points within this distance of target center is a "hit". --> + <attr name="hitRadius"/> + + <!-- Radius of glow under finger. --> + <attr name="glowRadius" format="dimension" /> + + <!-- Tactile feedback duration for actions. Set to '0' for no vibration. --> + <attr name="vibrationDuration"/> + + <!-- How close we need to be before snapping to a target. --> + <attr name="snapMargin"/> + + <!-- Number of waves/chevrons to show in animation. --> + <attr name="feedbackCount"/> + + <!-- Used when the handle shouldn't wait to be hit before following the finger --> + <attr name="alwaysTrackFinger"/> + </declare-styleable> + + <!-- =============================== --> <!-- MultiWaveView class attributes --> <!-- =============================== --> <eat-comment /> @@ -5393,22 +5441,6 @@ <!-- Sets a drawable as the drag center. --> <attr name="handleDrawable" format="reference" /> - <!-- Drawable to use for chevron animation on the left. May be null. - @deprecated use chevronDrawables instead --> - <attr name="leftChevronDrawable" format="reference" /> - - <!-- Drawable to use for chevron animation on the right. May be null. - @deprecated use chevronDrawables instead --> - <attr name="rightChevronDrawable" format="reference" /> - - <!-- Drawable to use for chevron animation on the top. May be null. - @deprecated use chevronDrawables instead --> - <attr name="topChevronDrawable" format="reference" /> - - <!-- Drawable to use for chevron animation on the bottom. May be null. - @deprecated use chevronDrawables instead --> - <attr name="bottomChevronDrawable" format="reference" /> - <!-- Drawables to use for chevron animations. May be null. --> <attr name="chevronDrawables" format="reference"/> @@ -5430,17 +5462,6 @@ <!-- Number of waves/chevrons to show in animation. --> <attr name="feedbackCount" format="integer" /> - <!-- {@deprecated Not used by the framework. Use android:gravity instead} - Used to shift center of pattern vertically. --> - <attr name="verticalOffset" format="dimension" /> - - <!-- {@deprecated Not used by the framework. Use android:gravity instead} - Used to shift center of pattern horizontally. --> - <attr name="horizontalOffset" format="dimension" /> - - <!-- How the items in this layout should be positioned --> - <attr name="gravity" /> - <!-- Used when the handle shouldn't wait to be hit before following the finger --> <attr name="alwaysTrackFinger" format="boolean" /> </declare-styleable> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index d549644..ffbcb95 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -73,14 +73,20 @@ <!-- Size of lockscreen outerring on unsecure unlock LockScreen --> <dimen name="keyguard_lockscreen_outerring_diameter">270dp</dimen> - <!-- Default target placement radius for MultiWaveView --> - <dimen name="multiwaveview_target_placement_radius">135dip</dimen> + <!-- Default target placement radius for GlowPadView. Should be 1/2 of outerring diameter. --> + <dimen name="glowpadview_target_placement_radius">135dip</dimen> - <!-- Default distance beyond which MultiWaveView snaps to the target radius --> - <dimen name="multiwaveview_snap_margin">20dip</dimen> + <!-- Default glow radius for GlowPadView --> + <dimen name="glowpadview_glow_radius">75dip</dimen> - <!-- Default distance from each snap target that MultiWaveView considers a "hit" --> - <dimen name="multiwaveview_hit_radius">60dip</dimen> + <!-- Default distance beyond which GlowPadView snaps to the target radius --> + <dimen name="glowpadview_snap_margin">20dip</dimen> + + <!-- Default distance from each snap target that GlowPadView considers a "hit" --> + <dimen name="glowpadview_hit_radius">60dip</dimen> + + <!-- Default distance from each snap target that GlowPadView considers a "hit" --> + <dimen name="glowpadview_inner_radius">15dip</dimen> <!-- Preference activity side margins --> <dimen name="preference_screen_side_margin">0dp</dimen> @@ -228,10 +234,10 @@ a few are present. --> <dimen name="action_bar_stacked_tab_max_width">180dp</dimen> - <!-- Size of notification text (see TextAppearance.StatusBar.EventContent) --> + <!-- Size of notification text (see TextAppearance.StatusBar.EventContent) --> <dimen name="notification_text_size">14dp</dimen> - <!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) --> + <!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) --> <dimen name="notification_title_text_size">18dp</dimen> - <!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) --> + <!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) --> <dimen name="notification_subtext_size">12dp</dimen> </resources> diff --git a/packages/SystemUI/res/drawable-hdpi/navbar_search_bg_scrim.9.png b/packages/SystemUI/res/drawable-hdpi/navbar_search_bg_scrim.9.png Binary files differdeleted file mode 100644 index 4c163a2..0000000 --- a/packages/SystemUI/res/drawable-hdpi/navbar_search_bg_scrim.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/navbar_search_bg_scrim.9.png b/packages/SystemUI/res/drawable-mdpi/navbar_search_bg_scrim.9.png Binary files differdeleted file mode 100644 index 21c5abd..0000000 --- a/packages/SystemUI/res/drawable-mdpi/navbar_search_bg_scrim.9.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/navbar_search_bg_scrim.9.png b/packages/SystemUI/res/drawable-xhdpi/navbar_search_bg_scrim.9.png Binary files differdeleted file mode 100644 index 7874c63..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/navbar_search_bg_scrim.9.png +++ /dev/null diff --git a/packages/SystemUI/res/layout-land/status_bar_search_panel.xml b/packages/SystemUI/res/layout-land/status_bar_search_panel.xml index ae81167..e6c0087 100644 --- a/packages/SystemUI/res/layout-land/status_bar_search_panel.xml +++ b/packages/SystemUI/res/layout-land/status_bar_search_panel.xml @@ -38,25 +38,29 @@ android:layout_height="match_parent" android:layout_alignParentRight="true"> - <com.android.internal.widget.multiwaveview.MultiWaveView - android:id="@+id/multi_wave_view" + <com.android.internal.widget.multiwaveview.GlowPadView + android:id="@+id/glow_pad_view" android:orientation="vertical" android:layout_width="@dimen/navbar_search_panel_height" android:layout_height="match_parent" android:layout_alignParentBottom="true" - android:background="@drawable/navbar_search_bg_scrim" android:gravity="left" prvandroid:targetDrawables="@array/navbar_search_targets" prvandroid:targetDescriptions="@array/navbar_search_target_descriptions" prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions" prvandroid:handleDrawable="@drawable/navbar_search_handle" - prvandroid:waveDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRingDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRadius="@dimen/navbar_search_outerring_radius" + prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius" prvandroid:snapMargin="@dimen/navbar_search_snap_margin" prvandroid:hitRadius="@dimen/navbar_search_hit_radius" prvandroid:feedbackCount="0" prvandroid:vibrationDuration="@integer/config_vibration_duration" - prvandroid:alwaysTrackFinger="true"/> + prvandroid:alwaysTrackFinger="true" + prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius" + prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot" + /> </RelativeLayout> diff --git a/packages/SystemUI/res/layout-port/status_bar_search_panel.xml b/packages/SystemUI/res/layout-port/status_bar_search_panel.xml index 785d5dd..3828136 100644 --- a/packages/SystemUI/res/layout-port/status_bar_search_panel.xml +++ b/packages/SystemUI/res/layout-port/status_bar_search_panel.xml @@ -38,25 +38,29 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true"> - <com.android.internal.widget.multiwaveview.MultiWaveView - android:id="@+id/multi_wave_view" + <com.android.internal.widget.multiwaveview.GlowPadView + android:id="@+id/glow_pad_view" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="@dimen/navbar_search_panel_height" android:layout_alignParentBottom="true" - android:background="@drawable/navbar_search_bg_scrim" android:gravity="top" prvandroid:targetDrawables="@array/navbar_search_targets" prvandroid:targetDescriptions="@array/navbar_search_target_descriptions" prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions" prvandroid:handleDrawable="@drawable/navbar_search_handle" - prvandroid:waveDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRingDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRadius="@dimen/navbar_search_outerring_radius" + prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius" prvandroid:snapMargin="@dimen/navbar_search_snap_margin" prvandroid:hitRadius="@dimen/navbar_search_hit_radius" prvandroid:feedbackCount="0" prvandroid:vibrationDuration="@integer/config_vibration_duration" - prvandroid:alwaysTrackFinger="true"/> + prvandroid:alwaysTrackFinger="true" + prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius" + prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot" + /> </RelativeLayout> diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml index 74a15f2..c17f858 100644 --- a/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml +++ b/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml @@ -25,23 +25,26 @@ android:layout_height="match_parent" android:layout_width="match_parent"> - <com.android.internal.widget.multiwaveview.MultiWaveView - android:id="@+id/multi_wave_view" + <com.android.internal.widget.multiwaveview.GlowPadView + android:id="@+id/glow_pad_view" android:layout_width="wrap_content" android:layout_height="@dimen/navbar_search_panel_height" android:layout_gravity="center_horizontal|bottom" android:gravity="center_horizontal|top" - android:background="@drawable/navbar_search_bg_scrim" prvandroid:targetDrawables="@array/navbar_search_targets" prvandroid:targetDescriptions="@array/navbar_search_target_descriptions" prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions" prvandroid:handleDrawable="@drawable/navbar_search_handle" - prvandroid:waveDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRingDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRadius="@dimen/navbar_search_outerring_radius" + prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius" prvandroid:snapMargin="@dimen/navbar_search_snap_margin" prvandroid:hitRadius="@dimen/navbar_search_hit_radius" prvandroid:feedbackCount="0" prvandroid:vibrationDuration="@integer/config_vibration_duration" - prvandroid:alwaysTrackFinger="true"/> + prvandroid:alwaysTrackFinger="true" + prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius" + prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/> </com.android.systemui.SearchPanelView> diff --git a/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml b/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml index 2a97307..100f81d 100644 --- a/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml +++ b/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml @@ -25,24 +25,27 @@ android:layout_height="match_parent" android:layout_width="match_parent"> - <com.android.internal.widget.multiwaveview.MultiWaveView - android:id="@+id/multi_wave_view" + <com.android.internal.widget.multiwaveview.GlowPadView + android:id="@+id/glow_pad_view" android:layout_width="wrap_content" android:layout_height="@dimen/navbar_search_panel_height" android:layout_gravity="left|bottom" android:gravity="top|right" android:layout_marginLeft="-150dip" - android:background="@drawable/navbar_search_bg_scrim" prvandroid:targetDrawables="@array/navbar_search_targets" prvandroid:targetDescriptions="@array/navbar_search_target_descriptions" prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions" prvandroid:handleDrawable="@drawable/navbar_search_handle" - prvandroid:waveDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRingDrawable="@drawable/navbar_search_outerring" + prvandroid:outerRadius="@dimen/navbar_search_outerring_radius" + prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius" prvandroid:snapMargin="@dimen/navbar_search_snap_margin" prvandroid:hitRadius="@dimen/navbar_search_hit_radius" prvandroid:feedbackCount="0" prvandroid:vibrationDuration="@integer/config_vibration_duration" - prvandroid:alwaysTrackFinger="true"/> + prvandroid:alwaysTrackFinger="true" + prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius" + prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/> </com.android.systemui.SearchPanelView> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 1bf59b0..2b5248f 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -30,6 +30,9 @@ <!-- Diameter of outer shape drawable shown in navbar search--> <dimen name="navbar_search_outerring_diameter">430dip</dimen> + <!-- Diameter of outer shape drawable shown in navbar search. Should be 1/2 of above value. --> + <dimen name="navbar_search_outerring_radius">215dip</dimen> + <!-- Height of search panel including navigation bar height --> <dimen name="navbar_search_panel_height">280dip</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f40ffd4..c88ae2a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -119,6 +119,9 @@ <!-- Diameter of outer shape drawable shown in navbar search--> <dimen name="navbar_search_outerring_diameter">340dp</dimen> + <!-- Diameter of outer shape drawable shown in navbar search. Should be 1/2 of above value --> + <dimen name="navbar_search_outerring_radius">170dp</dimen> + <!-- Threshold for swipe-up gesture to activate search dialog --> <dimen name="navbar_search_up_threshhold">40dip</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java index 8b8a814..923bcba 100644 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java @@ -35,8 +35,8 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnPreDrawListener; import android.widget.FrameLayout; -import com.android.internal.widget.multiwaveview.MultiWaveView; -import com.android.internal.widget.multiwaveview.MultiWaveView.OnTriggerListener; +import com.android.internal.widget.multiwaveview.GlowPadView; +import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener; import com.android.systemui.R; import com.android.systemui.recent.StatusBarTouchProxy; import com.android.systemui.statusbar.BaseStatusBar; @@ -58,7 +58,7 @@ public class SearchPanelView extends FrameLayout implements private boolean mShowing; private View mSearchTargetsContainer; - private MultiWaveView mMultiWaveView; + private GlowPadView mGlowPadView; public SearchPanelView(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -125,7 +125,7 @@ public class SearchPanelView extends FrameLayout implements } } - class MultiWaveTriggerListener implements MultiWaveView.OnTriggerListener { + class GlowPadTriggerListener implements GlowPadView.OnTriggerListener { boolean mWaitingForLaunch; public void onGrabbed(View v, int handle) { @@ -141,7 +141,7 @@ public class SearchPanelView extends FrameLayout implements } public void onTrigger(View v, final int target) { - final int resId = mMultiWaveView.getResourceIdForTarget(target); + final int resId = mGlowPadView.getResourceIdForTarget(target); switch (resId) { case com.android.internal.R.drawable.ic_lockscreen_search: mWaitingForLaunch = true; @@ -154,13 +154,13 @@ public class SearchPanelView extends FrameLayout implements public void onFinishFinalAnimation() { } } - final MultiWaveTriggerListener mMultiWaveViewListener = new MultiWaveTriggerListener(); + final GlowPadTriggerListener mGlowPadViewListener = new GlowPadTriggerListener(); @Override public void onAnimationStarted() { postDelayed(new Runnable() { public void run() { - mMultiWaveViewListener.mWaitingForLaunch = false; + mGlowPadViewListener.mWaitingForLaunch = false; mBar.hideSearchPanel(); } }, SEARCH_PANEL_HOLD_DURATION); @@ -173,13 +173,13 @@ public class SearchPanelView extends FrameLayout implements mSearchTargetsContainer = findViewById(R.id.search_panel_container); mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy); // TODO: fetch views - mMultiWaveView = (MultiWaveView) findViewById(R.id.multi_wave_view); - mMultiWaveView.setOnTriggerListener(mMultiWaveViewListener); + mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view); + mGlowPadView.setOnTriggerListener(mGlowPadViewListener); SearchManager searchManager = getSearchManager(); if (searchManager != null) { ComponentName component = searchManager.getGlobalSearchActivity(); if (component != null) { - if (!mMultiWaveView.replaceTargetDrawablesIfPresent(component, + if (!mGlowPadView.replaceTargetDrawablesIfPresent(component, ASSIST_ICON_METADATA_NAME, com.android.internal.R.drawable.ic_lockscreen_search)) { Slog.w(TAG, "Couldn't grab icon from component " + component); @@ -214,7 +214,7 @@ public class SearchPanelView extends FrameLayout implements private final OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); - mMultiWaveView.resumeAnimations(); + mGlowPadView.resumeAnimations(); return false; } }; @@ -240,7 +240,8 @@ public class SearchPanelView extends FrameLayout implements setVisibility(View.VISIBLE); // Don't start the animation until we've created the layer, which is done // right before we are drawn - mMultiWaveView.suspendAnimations(); + mGlowPadView.suspendAnimations(); + mGlowPadView.ping(); getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); vibrate(); } @@ -299,7 +300,7 @@ public class SearchPanelView extends FrameLayout implements public void setStatusBarView(final View statusBarView) { if (mStatusBarTouchProxy != null) { mStatusBarTouchProxy.setStatusBar(statusBarView); -// mMultiWaveView.setOnTouchListener(new OnTouchListener() { +// mGlowPadView.setOnTouchListener(new OnTouchListener() { // public boolean onTouch(View v, MotionEvent event) { // return statusBarView.onTouchEvent(event); // } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 8df9b85..9b46af8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -404,6 +404,7 @@ public class TabletStatusBar extends BaseStatusBar implements mRecentsPanel.updateValuesFromResources(); mShowSearchHoldoff = mContext.getResources().getInteger( R.integer.config_show_search_delay); + updateSearchPanel(); } protected void loadDimens() { diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java index d37207c..c9388cb 100644 --- a/policy/src/com/android/internal/policy/impl/LockScreen.java +++ b/policy/src/com/android/internal/policy/impl/LockScreen.java @@ -23,7 +23,7 @@ import com.android.internal.telephony.IccCard.State; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.SlidingTab; import com.android.internal.widget.WaveView; -import com.android.internal.widget.multiwaveview.MultiWaveView; +import com.android.internal.widget.multiwaveview.GlowPadView; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -286,16 +286,16 @@ class LockScreen extends LinearLayout implements KeyguardScreen { return mSearchManager; } - class MultiWaveViewMethods implements MultiWaveView.OnTriggerListener, + class GlowPadViewMethods implements GlowPadView.OnTriggerListener, UnlockWidgetCommonMethods { - private final MultiWaveView mMultiWaveView; + private final GlowPadView mGlowPadView; - MultiWaveViewMethods(MultiWaveView multiWaveView) { - mMultiWaveView = multiWaveView; + GlowPadViewMethods(GlowPadView glowPadView) { + mGlowPadView = glowPadView; } public boolean isTargetPresent(int resId) { - return mMultiWaveView.getTargetPosition(resId) != -1; + return mGlowPadView.getTargetPosition(resId) != -1; } public void updateResources() { @@ -307,8 +307,8 @@ class LockScreen extends LinearLayout implements KeyguardScreen { } else { resId = R.array.lockscreen_targets_with_camera; } - if (mMultiWaveView.getTargetResourceId() != resId) { - mMultiWaveView.setTargetResources(resId); + if (mGlowPadView.getTargetResourceId() != resId) { + mGlowPadView.setTargetResources(resId); } // Update the search icon with drawable from the search .apk @@ -317,7 +317,7 @@ class LockScreen extends LinearLayout implements KeyguardScreen { if (searchManager != null) { ComponentName component = searchManager.getGlobalSearchActivity(); if (component != null) { - if (!mMultiWaveView.replaceTargetDrawablesIfPresent(component, + if (!mGlowPadView.replaceTargetDrawablesIfPresent(component, ASSIST_ICON_METADATA_NAME, com.android.internal.R.drawable.ic_lockscreen_search)) { Slog.w(TAG, "Couldn't grab icon from package " + component); @@ -343,7 +343,7 @@ class LockScreen extends LinearLayout implements KeyguardScreen { } public void onTrigger(View v, int target) { - final int resId = mMultiWaveView.getResourceIdForTarget(target); + final int resId = mGlowPadView.getResourceIdForTarget(target); switch (resId) { case com.android.internal.R.drawable.ic_lockscreen_search: Intent assistIntent = getAssistIntent(); @@ -393,33 +393,33 @@ class LockScreen extends LinearLayout implements KeyguardScreen { // Don't poke the wake lock when returning to a state where the handle is // not grabbed since that can happen when the system (instead of the user) // cancels the grab. - if (handle != MultiWaveView.OnTriggerListener.NO_HANDLE) { + if (handle != GlowPadView.OnTriggerListener.NO_HANDLE) { mCallback.pokeWakelock(); } } public View getView() { - return mMultiWaveView; + return mGlowPadView; } public void reset(boolean animate) { - mMultiWaveView.reset(animate); + mGlowPadView.reset(animate); } public void ping() { - mMultiWaveView.ping(); + mGlowPadView.ping(); } public void setEnabled(int resourceId, boolean enabled) { - mMultiWaveView.setEnableTarget(resourceId, enabled); + mGlowPadView.setEnableTarget(resourceId, enabled); } public int getTargetPosition(int resourceId) { - return mMultiWaveView.getTargetPosition(resourceId); + return mGlowPadView.getTargetPosition(resourceId); } public void cleanUp() { - mMultiWaveView.setOnTriggerListener(null); + mGlowPadView.setOnTriggerListener(null); } public void onFinishFinalAnimation() { @@ -531,11 +531,11 @@ class LockScreen extends LinearLayout implements KeyguardScreen { WaveViewMethods waveViewMethods = new WaveViewMethods(waveView); waveView.setOnTriggerListener(waveViewMethods); return waveViewMethods; - } else if (unlockWidget instanceof MultiWaveView) { - MultiWaveView multiWaveView = (MultiWaveView) unlockWidget; - MultiWaveViewMethods multiWaveViewMethods = new MultiWaveViewMethods(multiWaveView); - multiWaveView.setOnTriggerListener(multiWaveViewMethods); - return multiWaveViewMethods; + } else if (unlockWidget instanceof GlowPadView) { + GlowPadView glowPadView = (GlowPadView) unlockWidget; + GlowPadViewMethods glowPadViewMethods = new GlowPadViewMethods(glowPadView); + glowPadView.setOnTriggerListener(glowPadViewMethods); + return glowPadViewMethods; } else { throw new IllegalStateException("Unrecognized unlock widget: " + unlockWidget); } @@ -545,12 +545,12 @@ class LockScreen extends LinearLayout implements KeyguardScreen { boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager() .getCameraDisabled(null); boolean disabledBySimState = mUpdateMonitor.isSimLocked(); - boolean cameraTargetPresent = (mUnlockWidgetMethods instanceof MultiWaveViewMethods) - ? ((MultiWaveViewMethods) mUnlockWidgetMethods) + boolean cameraTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods) + ? ((GlowPadViewMethods) mUnlockWidgetMethods) .isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera) : false; - boolean searchTargetPresent = (mUnlockWidgetMethods instanceof MultiWaveViewMethods) - ? ((MultiWaveViewMethods) mUnlockWidgetMethods) + boolean searchTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods) + ? ((GlowPadViewMethods) mUnlockWidgetMethods) .isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_search) : false; |