am c6587dc8: Merge change 25178 into eclair
Merge commit 'c6587dc870d06e4d5022705d08464fc7abb1c5f8' into eclair-plus-aosp * commit 'c6587dc870d06e4d5022705d08464fc7abb1c5f8': Add RotarySelector widget to android.internal for use by lock screen and incoming call screen.
542
core/java/com/android/internal/widget/RotarySelector.java
Normal file
@@ -0,0 +1,542 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 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;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.animation.AccelerateInterpolator;
|
||||||
|
|
||||||
|
import com.android.internal.R;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom view that presents up to two items that are selectable by rotating a semi-circle from
|
||||||
|
* left to right, or right to left. Used by incoming call screen, and the lock screen when no
|
||||||
|
* security pattern is set.
|
||||||
|
*/
|
||||||
|
public class RotarySelector extends View {
|
||||||
|
private static final String LOG_TAG = "RotarySelector";
|
||||||
|
private static final boolean DBG = false;
|
||||||
|
|
||||||
|
// Listener for onDialTrigger() callbacks.
|
||||||
|
private OnDialTriggerListener mOnDialTriggerListener;
|
||||||
|
|
||||||
|
private float mDensity;
|
||||||
|
|
||||||
|
// UI elements
|
||||||
|
private Drawable mBackground;
|
||||||
|
private Drawable mDimple;
|
||||||
|
|
||||||
|
private Drawable mLeftHandleIcon;
|
||||||
|
private Drawable mRightHandleIcon;
|
||||||
|
|
||||||
|
private Drawable mArrowShortLeftAndRight;
|
||||||
|
private Drawable mArrowLongLeft; // Long arrow starting on the left, pointing clockwise
|
||||||
|
private Drawable mArrowLongRight; // Long arrow starting on the right, pointing CCW
|
||||||
|
|
||||||
|
// positions of the left and right handle
|
||||||
|
private int mLeftHandleX;
|
||||||
|
private int mRightHandleX;
|
||||||
|
|
||||||
|
// current offset of user's dragging
|
||||||
|
private int mTouchDragOffset = 0;
|
||||||
|
|
||||||
|
// state of the animation used to bring the handle back to its start position when
|
||||||
|
// the user lets go before triggering an action
|
||||||
|
private boolean mAnimating = false;
|
||||||
|
private long mAnimationEndTime;
|
||||||
|
private int mAnimatingDelta;
|
||||||
|
AccelerateInterpolator mInterpolator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True after triggering an action if the user of {@link OnDialTriggerListener} wants to
|
||||||
|
* freeze the UI (until they transition to another screen).
|
||||||
|
*/
|
||||||
|
private boolean mFrozen = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user is currently dragging something.
|
||||||
|
*/
|
||||||
|
private int mGrabbedState = NOTHING_GRABBED;
|
||||||
|
private static final int NOTHING_GRABBED = 0;
|
||||||
|
private static final int LEFT_HANDLE_GRABBED = 1;
|
||||||
|
private static final int RIGHT_HANDLE_GRABBED = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the user has triggered something (e.g dragging the left handle all the way over to
|
||||||
|
* the right).
|
||||||
|
*/
|
||||||
|
private boolean mTriggered = false;
|
||||||
|
|
||||||
|
// Vibration (haptic feedback)
|
||||||
|
private Vibrator mVibrator;
|
||||||
|
private static final long VIBRATE_SHORT = 60; // msec
|
||||||
|
private static final long VIBRATE_LONG = 100; // msec
|
||||||
|
|
||||||
|
// Various tweakable layout or behavior parameters:
|
||||||
|
|
||||||
|
// How close to the edge of the screen, we let the handle get before
|
||||||
|
// triggering an action:
|
||||||
|
private static final int EDGE_THRESHOLD_DIP = 70;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The drawable for the arrows need to be scrunched this many dips towards the rotary bg below
|
||||||
|
* it.
|
||||||
|
*/
|
||||||
|
private static final int ARROW_SCRUNCH_DIP = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How far inset the left and right circles should be
|
||||||
|
*/
|
||||||
|
private static final int EDGE_PADDING_DIP = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dimensions of arc in background drawable.
|
||||||
|
*/
|
||||||
|
static final int OUTER_ROTARY_RADIUS_DIP = 390;
|
||||||
|
static final int ROTARY_STROKE_WIDTH_DIP = 83;
|
||||||
|
private static final int ANIMATION_DURATION_MILLIS = 300;
|
||||||
|
|
||||||
|
private static final boolean DRAW_CENTER_DIMPLE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor used when this widget is created from a layout file.
|
||||||
|
*/
|
||||||
|
public RotarySelector(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
if (DBG) log("IncomingCallDialWidget constructor...");
|
||||||
|
|
||||||
|
Resources r = getResources();
|
||||||
|
mDensity = r.getDisplayMetrics().density;
|
||||||
|
if (DBG) log("- Density: " + mDensity);
|
||||||
|
// Density is 1.0 on HVGA (like Dream), and 1.5 on WVGA.
|
||||||
|
// Usage: raw_pixel_value = (int) (dpi_value * mDensity + 0.5f)
|
||||||
|
|
||||||
|
// Assets (all are BitmapDrawables).
|
||||||
|
mBackground = r.getDrawable(R.drawable.jog_dial_bg_cropped);
|
||||||
|
mDimple = r.getDrawable(R.drawable.jog_dial_dimple);
|
||||||
|
|
||||||
|
mArrowLongLeft = r.getDrawable(R.drawable.jog_dial_arrow_long_left_green);
|
||||||
|
mArrowLongRight = r.getDrawable(R.drawable.jog_dial_arrow_long_right_red);
|
||||||
|
mArrowShortLeftAndRight = r.getDrawable(R.drawable.jog_dial_arrow_short_left_and_right);
|
||||||
|
|
||||||
|
mInterpolator = new AccelerateInterpolator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the left handle icon to a given resource.
|
||||||
|
*
|
||||||
|
* The resource should refer to a Drawable object, or use 0 to remove
|
||||||
|
* the icon.
|
||||||
|
*
|
||||||
|
* @param resId the resource ID.
|
||||||
|
*/
|
||||||
|
public void setLeftHandleResource(int resId) {
|
||||||
|
Drawable d = null;
|
||||||
|
if (resId != 0) {
|
||||||
|
d = getResources().getDrawable(resId);
|
||||||
|
}
|
||||||
|
setLeftHandleDrawable(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the left handle icon to a given Drawable.
|
||||||
|
*
|
||||||
|
* @param d the Drawable to use as the icon, or null to remove the icon.
|
||||||
|
*/
|
||||||
|
public void setLeftHandleDrawable(Drawable d) {
|
||||||
|
mLeftHandleIcon = d;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the right handle icon to a given resource.
|
||||||
|
*
|
||||||
|
* The resource should refer to a Drawable object, or use 0 to remove
|
||||||
|
* the icon.
|
||||||
|
*
|
||||||
|
* @param resId the resource ID.
|
||||||
|
*/
|
||||||
|
public void setRightHandleResource(int resId) {
|
||||||
|
Drawable d = null;
|
||||||
|
if (resId != 0) {
|
||||||
|
d = getResources().getDrawable(resId);
|
||||||
|
}
|
||||||
|
setRightHandleDrawable(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the right handle icon to a given Drawable.
|
||||||
|
*
|
||||||
|
* @param d the Drawable to use as the icon, or null to remove the icon.
|
||||||
|
*/
|
||||||
|
public void setRightHandleDrawable(Drawable d) {
|
||||||
|
mRightHandleIcon = d;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
final int width = MeasureSpec.getSize(widthMeasureSpec); // screen width
|
||||||
|
|
||||||
|
final int arrowH = mArrowShortLeftAndRight.getIntrinsicHeight();
|
||||||
|
final int backgroundH = mBackground.getIntrinsicHeight();
|
||||||
|
|
||||||
|
// by making the height less than arrow + bg, arrow and bg will be scrunched together,
|
||||||
|
// overlaying somewhat (though on transparent portions of the drawable).
|
||||||
|
// this works because the arrows are drawn from the top, and the rotary bg is drawn
|
||||||
|
// from the bottom.
|
||||||
|
final int arrowScrunch = (int) (ARROW_SCRUNCH_DIP * mDensity);
|
||||||
|
setMeasuredDimension(width, backgroundH + arrowH - arrowScrunch);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
|
||||||
|
mLeftHandleX = (int) (EDGE_PADDING_DIP * mDensity) + mDimple.getIntrinsicWidth() / 2;
|
||||||
|
mRightHandleX =
|
||||||
|
getWidth() - (int) (EDGE_PADDING_DIP * mDensity) - mDimple.getIntrinsicWidth() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private Paint mPaint = new Paint();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
if (DBG) {
|
||||||
|
log(String.format("onDraw: mAnimating=%s, mTouchDragOffset=%d, mGrabbedState=%d," +
|
||||||
|
"mFrozen=%s",
|
||||||
|
mAnimating, mTouchDragOffset, mGrabbedState, mFrozen));
|
||||||
|
}
|
||||||
|
|
||||||
|
final int height = getHeight();
|
||||||
|
|
||||||
|
// update animating state before we draw anything
|
||||||
|
if (mAnimating && !mFrozen) {
|
||||||
|
long millisLeft = mAnimationEndTime - System.currentTimeMillis();
|
||||||
|
if (DBG) log("millisleft for animating: " + millisLeft);
|
||||||
|
if (millisLeft <= 0) {
|
||||||
|
reset();
|
||||||
|
} else {
|
||||||
|
float interpolation = mInterpolator.getInterpolation(
|
||||||
|
(float) millisLeft / ANIMATION_DURATION_MILLIS);
|
||||||
|
mTouchDragOffset = (int) (mAnimatingDelta * interpolation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Background:
|
||||||
|
final int backgroundW = mBackground.getIntrinsicWidth();
|
||||||
|
final int backgroundH = mBackground.getIntrinsicHeight();
|
||||||
|
final int backgroundY = height - backgroundH;
|
||||||
|
if (DBG) log("- Background INTRINSIC: " + backgroundW + " x " + backgroundH);
|
||||||
|
mBackground.setBounds(0, backgroundY,
|
||||||
|
backgroundW, backgroundY + backgroundH);
|
||||||
|
if (DBG) log(" Background BOUNDS: " + mBackground.getBounds());
|
||||||
|
mBackground.draw(canvas);
|
||||||
|
|
||||||
|
// Arrows:
|
||||||
|
// All arrow assets are the same size (they're the full width of
|
||||||
|
// the screen) regardless of which arrows are actually visible.
|
||||||
|
int arrowW = mArrowShortLeftAndRight.getIntrinsicWidth();
|
||||||
|
int arrowH = mArrowShortLeftAndRight.getIntrinsicHeight();
|
||||||
|
|
||||||
|
// Draw the correct arrow(s) depending on the current state:
|
||||||
|
Drawable currentArrow;
|
||||||
|
switch (mGrabbedState) {
|
||||||
|
case NOTHING_GRABBED:
|
||||||
|
currentArrow = mArrowShortLeftAndRight;
|
||||||
|
break;
|
||||||
|
case LEFT_HANDLE_GRABBED:
|
||||||
|
currentArrow = mArrowLongLeft;
|
||||||
|
break;
|
||||||
|
case RIGHT_HANDLE_GRABBED:
|
||||||
|
currentArrow = mArrowLongRight;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("invalid mGrabbedState: " + mGrabbedState);
|
||||||
|
}
|
||||||
|
currentArrow.setBounds(0, 0, arrowW, arrowH);
|
||||||
|
currentArrow.draw(canvas);
|
||||||
|
|
||||||
|
// debug: draw circle that should match the outer arc (good sanity check)
|
||||||
|
// mPaint.setColor(Color.RED);
|
||||||
|
// mPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
// float or = OUTER_ROTARY_RADIUS_DIP * mDensity;
|
||||||
|
// canvas.drawCircle(getWidth() / 2, or + mBackground.getBounds().top, or, mPaint);
|
||||||
|
|
||||||
|
final int outerRadius = (int) (mDensity * OUTER_ROTARY_RADIUS_DIP);
|
||||||
|
final int innerRadius =
|
||||||
|
(int) ((OUTER_ROTARY_RADIUS_DIP - ROTARY_STROKE_WIDTH_DIP) * mDensity);
|
||||||
|
final int bgTop = mBackground.getBounds().top;
|
||||||
|
{
|
||||||
|
final int xOffset = mLeftHandleX + mTouchDragOffset;
|
||||||
|
final int drawableY = getYOnArc(
|
||||||
|
mBackground,
|
||||||
|
innerRadius,
|
||||||
|
outerRadius,
|
||||||
|
xOffset);
|
||||||
|
|
||||||
|
drawCentered(mDimple, canvas, xOffset, drawableY + bgTop);
|
||||||
|
drawCentered(mLeftHandleIcon, canvas, xOffset, drawableY + bgTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DRAW_CENTER_DIMPLE) {
|
||||||
|
final int xOffset = getWidth() / 2 + mTouchDragOffset;
|
||||||
|
final int drawableY = getYOnArc(
|
||||||
|
mBackground,
|
||||||
|
innerRadius,
|
||||||
|
outerRadius,
|
||||||
|
xOffset);
|
||||||
|
|
||||||
|
drawCentered(mDimple, canvas, xOffset, drawableY + bgTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
final int xOffset = mRightHandleX + mTouchDragOffset;
|
||||||
|
final int drawableY = getYOnArc(
|
||||||
|
mBackground,
|
||||||
|
innerRadius,
|
||||||
|
outerRadius,
|
||||||
|
xOffset);
|
||||||
|
|
||||||
|
drawCentered(mDimple, canvas, xOffset, drawableY + bgTop);
|
||||||
|
drawCentered(mRightHandleIcon, canvas, xOffset, drawableY + bgTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAnimating) invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assuming drawable is a bounding box around a piece of an arc drawn by two concentric circles
|
||||||
|
* (as the background drawable for the rotary widget is), and given an x coordinate along the
|
||||||
|
* drawable, return the y coordinate of a point on the arc that is between the two concentric
|
||||||
|
* circles. The resulting y combined with the incoming x is a point along the circle in
|
||||||
|
* between the two concentric circles.
|
||||||
|
*
|
||||||
|
* @param drawable The drawable.
|
||||||
|
* @param innerRadius The radius of the circle that intersects the drawable at the bottom two
|
||||||
|
* corders of the drawable (top two corners in terms of drawing coordinates).
|
||||||
|
* @param outerRadius The radius of the circle who's top most point is the top center of the
|
||||||
|
* drawable (bottom center in terms of drawing coordinates).
|
||||||
|
* @param x The distance along the x axis of the desired point.
|
||||||
|
* @return The y coordinate, in drawing coordinates, that will place (x, y) along the circle
|
||||||
|
* in between the two concentric circles.
|
||||||
|
*/
|
||||||
|
private int getYOnArc(Drawable drawable, int innerRadius, int outerRadius, int x) {
|
||||||
|
|
||||||
|
// the hypotenuse
|
||||||
|
final int halfWidth = (outerRadius - innerRadius) / 2;
|
||||||
|
final int middleRadius = innerRadius + halfWidth;
|
||||||
|
|
||||||
|
// the bottom leg of the triangle
|
||||||
|
final int triangleBottom = (drawable.getIntrinsicWidth() / 2) - x;
|
||||||
|
|
||||||
|
// "Our offense is like the pythagorean theorem: There is no answer!" - Shaquille O'Neal
|
||||||
|
final int triangleY =
|
||||||
|
(int) Math.sqrt(middleRadius * middleRadius - triangleBottom * triangleBottom);
|
||||||
|
|
||||||
|
// convert to drawing coordinates:
|
||||||
|
// middleRadius - triangleY =
|
||||||
|
// the vertical distance from the outer edge of the circle to the desired point
|
||||||
|
// from there we add the distance from the top of the drawable to the middle circle
|
||||||
|
return middleRadius - triangleY + halfWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle touch screen events.
|
||||||
|
*
|
||||||
|
* @param event The motion event.
|
||||||
|
* @return True if the event was handled, false otherwise.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if (mAnimating || mFrozen) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int eventX = (int) event.getX();
|
||||||
|
final int hitWindow = mDimple.getIntrinsicWidth();
|
||||||
|
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
|
if (DBG) log("touch-down");
|
||||||
|
mTriggered = false;
|
||||||
|
if (mGrabbedState != RotarySelector.NOTHING_GRABBED) {
|
||||||
|
reset();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
if (eventX < mLeftHandleX + hitWindow) {
|
||||||
|
mTouchDragOffset = eventX - mLeftHandleX;
|
||||||
|
mGrabbedState = RotarySelector.LEFT_HANDLE_GRABBED;
|
||||||
|
invalidate();
|
||||||
|
vibrate(VIBRATE_SHORT);
|
||||||
|
} else if (eventX > mRightHandleX - hitWindow) {
|
||||||
|
mTouchDragOffset = eventX - mRightHandleX;
|
||||||
|
mGrabbedState = RotarySelector.RIGHT_HANDLE_GRABBED;
|
||||||
|
invalidate();
|
||||||
|
vibrate(VIBRATE_SHORT);
|
||||||
|
}
|
||||||
|
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
|
if (DBG) log("touch-move");
|
||||||
|
if (mGrabbedState == RotarySelector.LEFT_HANDLE_GRABBED) {
|
||||||
|
mTouchDragOffset = eventX - mLeftHandleX;
|
||||||
|
invalidate();
|
||||||
|
if (eventX >= mRightHandleX - EDGE_PADDING_DIP && !mTriggered) {
|
||||||
|
mTriggered = true;
|
||||||
|
mFrozen = dispatchTriggerEvent(OnDialTriggerListener.LEFT_HANDLE);
|
||||||
|
}
|
||||||
|
} else if (mGrabbedState == RotarySelector.RIGHT_HANDLE_GRABBED) {
|
||||||
|
mTouchDragOffset = eventX - mRightHandleX;
|
||||||
|
invalidate();
|
||||||
|
if (eventX <= mLeftHandleX + EDGE_PADDING_DIP && !mTriggered) {
|
||||||
|
mTriggered = true;
|
||||||
|
mFrozen = dispatchTriggerEvent(OnDialTriggerListener.RIGHT_HANDLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((event.getAction() == MotionEvent.ACTION_UP)) {
|
||||||
|
if (DBG) log("touch-up");
|
||||||
|
// handle animating back to start if they didn't trigger
|
||||||
|
if (mGrabbedState == RotarySelector.LEFT_HANDLE_GRABBED
|
||||||
|
&& Math.abs(eventX - mLeftHandleX) > 5) {
|
||||||
|
mAnimating = true;
|
||||||
|
mAnimationEndTime = System.currentTimeMillis() + ANIMATION_DURATION_MILLIS;
|
||||||
|
mAnimatingDelta = eventX - mLeftHandleX;
|
||||||
|
} else if (mGrabbedState == RotarySelector.RIGHT_HANDLE_GRABBED
|
||||||
|
&& Math.abs(eventX - mRightHandleX) > 5) {
|
||||||
|
mAnimating = true;
|
||||||
|
mAnimationEndTime = System.currentTimeMillis() + ANIMATION_DURATION_MILLIS;
|
||||||
|
mAnimatingDelta = eventX - mRightHandleX;
|
||||||
|
}
|
||||||
|
|
||||||
|
mTouchDragOffset = 0;
|
||||||
|
mGrabbedState = RotarySelector.NOTHING_GRABBED;
|
||||||
|
invalidate();
|
||||||
|
} else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
|
||||||
|
if (DBG) log("touch-cancel");
|
||||||
|
reset();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
mAnimating = false;
|
||||||
|
mTouchDragOffset = 0;
|
||||||
|
mGrabbedState = RotarySelector.NOTHING_GRABBED;
|
||||||
|
mTriggered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers haptic feedback.
|
||||||
|
*/
|
||||||
|
private synchronized void vibrate(long duration) {
|
||||||
|
if (mVibrator == null) {
|
||||||
|
mVibrator = (android.os.Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
}
|
||||||
|
mVibrator.vibrate(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the bounds of the specified Drawable so that it's centered
|
||||||
|
* on the point (x,y), then draws it onto the specified canvas.
|
||||||
|
* TODO: is there already a utility method somewhere for this?
|
||||||
|
*/
|
||||||
|
private static void drawCentered(Drawable d, Canvas c, int x, int y) {
|
||||||
|
int w = d.getIntrinsicWidth();
|
||||||
|
int h = d.getIntrinsicHeight();
|
||||||
|
|
||||||
|
// if (DBG) log("--> drawCentered: " + x + " , " + y + "; intrinsic " + w + " x " + h);
|
||||||
|
d.setBounds(x - (w / 2), y - (h / 2),
|
||||||
|
x + (w / 2), y + (h / 2));
|
||||||
|
d.draw(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback to be invoked when the dial
|
||||||
|
* is "triggered" by rotating it one way or the other.
|
||||||
|
*
|
||||||
|
* @param l the OnDialTriggerListener to attach to this view
|
||||||
|
*/
|
||||||
|
public void setOnDialTriggerListener(OnDialTriggerListener l) {
|
||||||
|
mOnDialTriggerListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a trigger event to our listener.
|
||||||
|
*/
|
||||||
|
private boolean dispatchTriggerEvent(int whichHandle) {
|
||||||
|
vibrate(VIBRATE_LONG);
|
||||||
|
if (mOnDialTriggerListener != null) {
|
||||||
|
return mOnDialTriggerListener.onDialTrigger(this, whichHandle);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for a callback to be invoked when the dial
|
||||||
|
* is "triggered" by rotating it one way or the other.
|
||||||
|
*/
|
||||||
|
public interface OnDialTriggerListener {
|
||||||
|
/**
|
||||||
|
* The dial was triggered because the user grabbed the left handle,
|
||||||
|
* and rotated the dial clockwise.
|
||||||
|
*/
|
||||||
|
public static final int LEFT_HANDLE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dial was triggered because the user grabbed the right handle,
|
||||||
|
* and rotated the dial counterclockwise.
|
||||||
|
*/
|
||||||
|
public static final int RIGHT_HANDLE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
* The center handle is currently unused.
|
||||||
|
*/
|
||||||
|
public static final int CENTER_HANDLE = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the dial is triggered.
|
||||||
|
*
|
||||||
|
* @param v The view that was triggered
|
||||||
|
* @param whichHandle Which "dial handle" the user grabbed,
|
||||||
|
* either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}, or
|
||||||
|
* {@link #CENTER_HANDLE}.
|
||||||
|
* @return Whether the widget should freeze (e.g when the action goes to another screen,
|
||||||
|
* you want the UI to stay put until the transition occurs).
|
||||||
|
*/
|
||||||
|
boolean onDialTrigger(View v, int whichHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Debugging / testing code
|
||||||
|
|
||||||
|
private void log(String msg) {
|
||||||
|
Log.d(LOG_TAG, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
core/res/res/drawable/ic_jog_dial_answer.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
core/res/res/drawable/ic_jog_dial_decline.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
core/res/res/drawable/ic_jog_dial_silence_ringer.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
core/res/res/drawable/ic_jog_dial_turn_ring_vol_off.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
core/res/res/drawable/ic_jog_dial_turn_ring_vol_on.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
core/res/res/drawable/ic_jog_dial_unlock.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_long_left_green.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_long_left_yellow.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_long_middle_yellow.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_long_right_red.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_long_right_yellow.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_short_left.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_short_left_and_right.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
core/res/res/drawable/jog_dial_arrow_short_right.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
core/res/res/drawable/jog_dial_bg_cropped.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
core/res/res/drawable/jog_dial_dimple.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
136
core/res/res/layout/keyguard_screen_rotary_unlock.xml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
**
|
||||||
|
** Copyright 2009, 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- This is the general lock screen which shows information about the
|
||||||
|
state of the device, as well as instructions on how to get past it
|
||||||
|
depending on the state of the device. It is the same for landscape
|
||||||
|
and portrait.-->
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:id="@+id/root"
|
||||||
|
>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:background="#A0000000"
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/carrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="20dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/carrier"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="25dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
android:textSize="55sp"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/time"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="-12dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="1dip"
|
||||||
|
android:layout_marginTop="10dip"
|
||||||
|
android:layout_below="@id/date"
|
||||||
|
android:background="@android:drawable/divider_horizontal_dark"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/divider"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="6dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/status1"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="6dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/screenLocked"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/status2"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="12dip"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- By having the rotary selector hang below "screen locked" text, we get a layout more
|
||||||
|
robust for different screen sizes. On wvga, the widget should be flush with the bottom.-->
|
||||||
|
<com.android.internal.widget.RotarySelector
|
||||||
|
android:id="@+id/rotary"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/screenLocked"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="24dip"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- emergency call button shown when sim is missing or PUKd -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/emergencyCallButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/screenLocked"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="24dip"
|
||||||
|
android:drawableLeft="@drawable/ic_emergency"
|
||||||
|
android:drawablePadding="8dip"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||