summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk9
-rw-r--r--cmds/rawbu/backup.cpp1
-rw-r--r--core/java/android/app/TimePickerDialog.java32
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java15
-rw-r--r--core/java/android/webkit/WebViewCore.java2
-rw-r--r--core/java/android/webkit/ZoomManager.java28
-rw-r--r--core/java/android/widget/NumberPicker.java1380
-rw-r--r--core/java/android/widget/NumberPickerButton.java96
-rw-r--r--core/java/android/widget/TextView.java108
-rw-r--r--core/java/android/widget/TimePicker.java94
-rw-r--r--core/jni/android_database_CursorWindow.cpp14
-rw-r--r--core/jni/android_util_AssetManager.cpp2
-rw-r--r--core/jni/android_view_Surface.cpp84
-rw-r--r--core/res/res/drawable/timepicker_down_btn.xml31
-rw-r--r--core/res/res/drawable/timepicker_down_btn_holo_dark.xml43
-rw-r--r--core/res/res/drawable/timepicker_down_btn_holo_light.xml43
-rw-r--r--core/res/res/drawable/timepicker_input.xml31
-rw-r--r--core/res/res/drawable/timepicker_up_btn.xml31
-rw-r--r--core/res/res/drawable/timepicker_up_btn_holo_dark.xml43
-rw-r--r--core/res/res/drawable/timepicker_up_btn_holo_light.xml43
-rw-r--r--core/res/res/layout/number_picker.xml21
-rw-r--r--core/res/res/layout/time_picker.xml40
-rwxr-xr-xcore/res/res/values/attrs.xml15
-rw-r--r--core/res/res/values/donottranslate.xml2
-rwxr-xr-xcore/res/res/values/strings.xml4
-rw-r--r--core/res/res/values/styles.xml61
-rw-r--r--core/res/res/values/themes.xml20
-rw-r--r--data/keyboards/Apple_Wireless_Keyboard.kl119
-rw-r--r--data/keyboards/Generic.kl12
-rw-r--r--data/keyboards/Logitech_USB_Receiver.kl133
-rw-r--r--data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kcm370
-rw-r--r--data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kl6
-rw-r--r--data/keyboards/common.mk7
-rw-r--r--docs/html/images/preview_hc/actionbar.pngbin0 -> 29116 bytes
-rw-r--r--docs/html/images/preview_hc/fragments_layout.pngbin0 -> 118190 bytes
-rw-r--r--docs/html/sdk/index.jd2
-rw-r--r--docs/html/sdk/preview/features.jd185
-rw-r--r--docs/html/sdk/preview/installing.jd61
-rw-r--r--docs/html/sdk/sdk_toc.cs11
-rw-r--r--libs/binder/CursorWindow.cpp6
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java33
-rw-r--r--opengl/tests/hwc/Android.mk27
-rw-r--r--opengl/tests/hwc/hwc_stress.cpp1193
-rw-r--r--packages/SystemUI/res/layout-xlarge/status_bar.xml16
-rw-r--r--packages/SystemUI/res/layout-xlarge/status_bar_notification_panel.xml14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/DoNotDisturb.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DoNotDisturbController.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tablet/SettingsView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java13
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java54
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java22
-rw-r--r--tools/layoutlib/bridge/src/android/os/Handler_Delegate.java54
-rw-r--r--tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java66
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java81
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java38
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java35
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java121
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java252
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java2
61 files changed, 4328 insertions, 1080 deletions
diff --git a/Android.mk b/Android.mk
index 4c135da..0fe1164 100644
--- a/Android.mk
+++ b/Android.mk
@@ -449,12 +449,12 @@ web_docs_sample_code_flags := \
framework_docs_SDK_VERSION:=2.3
# release version (ie "Release x") (full releases only)
framework_docs_SDK_REL_ID:=1
- # flag to build offline docs for a preview release
-framework_docs_SDK_PREVIEW:=0
framework_docs_LOCAL_DROIDDOC_OPTIONS += \
-hdf sdk.version $(framework_docs_SDK_VERSION) \
- -hdf sdk.rel.id $(framework_docs_SDK_REL_ID)
+ -hdf sdk.rel.id $(framework_docs_SDK_REL_ID) \
+ -hdf sdk.preview true \
+ -hdf sdk.preview.version Honeycomb
# ==== the api stubs and current.xml ===========================
include $(CLEAR_VARS)
@@ -540,9 +540,6 @@ LOCAL_DROIDDOC_OPTIONS:=\
-sdkvalues $(OUT_DOCS) \
-hdf android.whichdoc offline
-ifeq ($(framework_docs_SDK_PREVIEW),true)
- LOCAL_DROIDDOC_OPTIONS += -hdf sdk.preview true
-endif
LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
diff --git a/cmds/rawbu/backup.cpp b/cmds/rawbu/backup.cpp
index c4fa765..9ea046d 100644
--- a/cmds/rawbu/backup.cpp
+++ b/cmds/rawbu/backup.cpp
@@ -14,6 +14,7 @@
#include <utime.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <stdint.h>
#include <cutils/properties.h>
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 381143c..26af4eb 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -16,29 +16,26 @@
package android.app;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Build;
import android.os.Bundle;
-import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
-import com.android.internal.R;
-
-import java.util.Calendar;
-
/**
* A dialog that prompts the user for the time of day using a {@link TimePicker}.
*
* <p>See the <a href="{@docRoot}resources/tutorials/views/hello-timepicker.html">Time Picker
* tutorial</a>.</p>
*/
-public class TimePickerDialog extends AlertDialog implements OnClickListener,
- OnTimeChangedListener {
+public class TimePickerDialog extends AlertDialog
+ implements OnClickListener, OnTimeChangedListener {
/**
* The callback interface used to indicate the user is done filling in
@@ -60,8 +57,6 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
private final TimePicker mTimePicker;
private final OnTimeSetListener mCallback;
- private final Calendar mCalendar;
- private final java.text.DateFormat mDateFormat;
int mInitialHourOfDay;
int mInitialMinute;
@@ -102,14 +97,13 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
mInitialMinute = minute;
mIs24HourView = is24HourView;
- mDateFormat = DateFormat.getTimeFormat(context);
- mCalendar = Calendar.getInstance();
- updateTitle(mInitialHourOfDay, mInitialMinute);
+ setCanceledOnTouchOutside(false);
+ setIcon(0);
+ setTitle(R.string.time_picker_dialog_title);
setButton(BUTTON_POSITIVE, context.getText(R.string.date_time_set), this);
setButton(BUTTON_NEGATIVE, context.getText(R.string.cancel),
(OnClickListener) null);
- setIcon(R.drawable.ic_dialog_time);
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -132,19 +126,13 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
}
}
- public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
- updateTitle(hourOfDay, minute);
- }
-
public void updateTime(int hourOfDay, int minutOfHour) {
mTimePicker.setCurrentHour(hourOfDay);
mTimePicker.setCurrentMinute(minutOfHour);
}
- private void updateTitle(int hour, int minute) {
- mCalendar.set(Calendar.HOUR_OF_DAY, hour);
- mCalendar.set(Calendar.MINUTE, minute);
- setTitle(mDateFormat.format(mCalendar.getTime()));
+ public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
+ /* do nothing */
}
@Override
@@ -164,7 +152,5 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
mTimePicker.setCurrentHour(hour);
mTimePicker.setCurrentMinute(minute);
mTimePicker.setIs24HourView(savedInstanceState.getBoolean(IS_24_HOUR));
- mTimePicker.setOnTimeChangedListener(this);
- updateTitle(hour, minute);
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 7efb7fd..184988b 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1122,11 +1122,11 @@ public class SQLiteDatabase extends SQLiteClosable {
Map.Entry<SQLiteClosable, Object> entry = iter.next();
SQLiteClosable program = entry.getKey();
if (program != null && program instanceof SQLiteProgram) {
- SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql;
- if (compiledSql.nStatement == stmtId) {
- msg = compiledSql.toString();
- found = true;
- }
+ SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql;
+ if (compiledSql.nStatement == stmtId) {
+ msg = compiledSql.toString();
+ found = true;
+ }
}
}
if (!found) {
@@ -1140,8 +1140,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
} else {
// the statement is not yet closed. most probably programming error in the app.
- Log.w(TAG, "dbclose failed due to un-close()d SQL statements: " + msg);
- throw e;
+ throw new SQLiteUnfinalizedObjectsException(
+ "close() on database: " + getPath() +
+ " failed due to un-close()d SQL statements: " + msg);
}
}
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 17f0a97..b7d20b4 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1884,6 +1884,7 @@ final class WebViewCore {
int mScrollX;
int mScrollY;
boolean mMobileSite;
+ boolean mIsRestored;
}
static class DrawData {
@@ -2285,6 +2286,7 @@ final class WebViewCore {
mInitialViewState.mScrollY = mRestoredY;
mInitialViewState.mMobileSite = (0 == mViewportWidth);
if (mRestoredScale > 0) {
+ mInitialViewState.mIsRestored = true;
mInitialViewState.mViewScale = mRestoredScale / 100.0f;
if (mRestoredTextWrapScale > 0) {
mInitialViewState.mTextWrapScale = mRestoredTextWrapScale / 100.0f;
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 69db6b2..b94dc3b 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -260,12 +260,7 @@ class ZoomManager {
public final float getReadingLevelScale() {
// The reading scale is at least 0.5f apart from the overview scale.
final float MIN_SCALE_DIFF = 0.5f;
- final float zoomOverviewScale = getZoomOverviewScale();
- if (zoomOverviewScale > DEFAULT_READING_LEVEL_SCALE) {
- return Math.min(DEFAULT_READING_LEVEL_SCALE,
- zoomOverviewScale - MIN_SCALE_DIFF);
- }
- return Math.max(zoomOverviewScale + MIN_SCALE_DIFF,
+ return Math.max(getZoomOverviewScale() + MIN_SCALE_DIFF,
DEFAULT_READING_LEVEL_SCALE);
}
@@ -864,32 +859,33 @@ class ZoomManager {
if (!mWebView.drawHistory()) {
float scale;
- final boolean reflowText;
- WebSettings settings = mWebView.getSettings();
+ final float overviewScale = getZoomOverviewScale();
if (mInitialScale > 0) {
scale = mInitialScale;
- reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
} else if (viewState.mViewScale > 0) {
mTextWrapScale = viewState.mTextWrapScale;
scale = viewState.mViewScale;
- reflowText = false;
} else {
- scale = getZoomOverviewScale();
- if (settings.getUseWideViewPort()
- && settings.getLoadWithOverviewMode()) {
- mInitialZoomOverview = true;
- } else {
+ scale = overviewScale;
+ WebSettings settings = mWebView.getSettings();
+ if (!settings.getUseWideViewPort()
+ || !settings.getLoadWithOverviewMode()) {
scale = Math.max(viewState.mTextWrapScale, scale);
- mInitialZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale());
}
if (settings.isNarrowColumnLayout() && settings.getUseFixedViewport()) {
// When first layout, reflow using the reading level scale to avoid
// reflow when double tapped.
mTextWrapScale = getReadingLevelScale();
}
+ }
+ boolean reflowText = false;
+ if (!viewState.mIsRestored) {
+ scale = Math.max(scale, overviewScale);
+ mTextWrapScale = Math.max(mTextWrapScale, overviewScale);
reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
}
+ mInitialZoomOverview = !exceedsMinScaleIncrement(scale, overviewScale);
setZoomScale(scale, reflowText);
// update the zoom buttons as the scale can be changed
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 4482b5b..0c298b0 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -18,80 +18,134 @@ package android.widget;
import com.android.internal.R;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.Widget;
import android.content.Context;
-import android.os.Handler;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Paint.Align;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
+import android.text.TextUtils;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.LayoutInflater.Filter;
+import android.view.animation.OvershootInterpolator;
+import android.view.inputmethod.InputMethodManager;
/**
- * A view for selecting a number
+ * A view for selecting a number For a dialog using this view, see
+ * {@link android.app.TimePickerDialog}.
*
- * For a dialog using this view, see {@link android.app.TimePickerDialog}.
* @hide
*/
@Widget
public class NumberPicker extends LinearLayout {
/**
- * The callback interface used to indicate the number value has been adjusted.
+ * The index of the middle selector item.
*/
- public interface OnChangedListener {
- /**
- * @param picker The NumberPicker associated with this listener.
- * @param oldVal The previous value.
- * @param newVal The new value.
- */
- void onChanged(NumberPicker picker, int oldVal, int newVal);
- }
+ private static final int SELECTOR_MIDDLE_ITEM_INDEX = 2;
/**
- * Interface used to format the number into a string for presentation
+ * The coefficient by which to adjust (divide) the max fling velocity.
*/
- public interface Formatter {
- String toString(int value);
- }
+ private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;
- /*
- * Use a custom NumberPicker formatting callback to use two-digit
- * minutes strings like "01". Keeping a static formatter etc. is the
- * most efficient way to do this; it avoids creating temporary objects
- * on every call to format().
- */
- public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER =
- new NumberPicker.Formatter() {
- final StringBuilder mBuilder = new StringBuilder();
- final java.util.Formatter mFmt = new java.util.Formatter(mBuilder);
- final Object[] mArgs = new Object[1];
- public String toString(int value) {
- mArgs[0] = value;
- mBuilder.delete(0, mBuilder.length());
- mFmt.format("%02d", mArgs);
- return mFmt.toString();
- }
- };
+ /**
+ * The the duration for adjusting the selector wheel.
+ */
+ private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
- private final Handler mHandler;
- private final Runnable mRunnable = new Runnable() {
- public void run() {
- if (mIncrement) {
- changeCurrent(mCurrent + 1);
- mHandler.postDelayed(this, mSpeed);
- } else if (mDecrement) {
- changeCurrent(mCurrent - 1);
- mHandler.postDelayed(this, mSpeed);
- }
+ /**
+ * The the delay for showing the input controls after a single tap on the
+ * input text.
+ */
+ private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration
+ .getDoubleTapTimeout();
+
+ /**
+ * The update step for incrementing the current value.
+ */
+ private static final int UPDATE_STEP_INCREMENT = 1;
+
+ /**
+ * The update step for decrementing the current value.
+ */
+ private static final int UPDATE_STEP_DECREMENT = -1;
+
+ /**
+ * The strength of fading in the top and bottom while drawing the selector.
+ */
+ private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
+
+ /**
+ * The numbers accepted by the input text's {@link Filter}
+ */
+ private static final char[] DIGIT_CHARACTERS = new char[] {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+
+ /**
+ * Use a custom NumberPicker formatting callback to use two-digit minutes
+ * strings like "01". Keeping a static formatter etc. is the most efficient
+ * way to do this; it avoids creating temporary objects on every call to
+ * format().
+ */
+ public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() {
+ final StringBuilder mBuilder = new StringBuilder();
+
+ final java.util.Formatter mFmt = new java.util.Formatter(mBuilder);
+
+ final Object[] mArgs = new Object[1];
+
+ public String toString(int value) {
+ mArgs[0] = value;
+ mBuilder.delete(0, mBuilder.length());
+ mFmt.format("%02d", mArgs);
+ return mFmt.toString();
}
};
- private final EditText mText;
- private final InputFilter mNumberInputFilter;
+ /**
+ * The increment button.
+ */
+ private final ImageButton mIncrementButton;
+
+ /**
+ * The decrement button.
+ */
+ private final ImageButton mDecrementButton;
+
+ /**
+ * The text for showing the current value.
+ */
+ private final EditText mInputText;
+
+ /**
+ * The height of the text.
+ */
+ private final int mTextSize;
+ /**
+ * The values to be displayed instead the indices.
+ */
private String[] mDisplayedValues;
/**
@@ -110,104 +164,466 @@ public class NumberPicker extends LinearLayout {
private int mCurrent;
/**
- * Previous value of this NumberPicker.
+ * Listener to be notified upon current value change.
*/
- private int mPrevious;
private OnChangedListener mListener;
+
+ /**
+ * Formatter for for displaying the current value.
+ */
private Formatter mFormatter;
- private long mSpeed = 300;
- private boolean mIncrement;
- private boolean mDecrement;
+ /**
+ * The speed for updating the value form long press.
+ */
+ private long mLongPressUpdateSpeed = 300;
/**
- * Create a new number picker
- * @param context the application environment
+ * Cache for the string representation of selector indices.
+ */
+ private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();
+
+ /**
+ * The selector indices whose value are show by the selector.
+ */
+ private final int[] mSelectorIndices = new int[] {
+ Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
+ Integer.MIN_VALUE
+ };
+
+ /**
+ * The {@link Paint} for drawing the selector.
+ */
+ private final Paint mSelectorPaint;
+
+ /**
+ * The height of a selector element (text + gap).
+ */
+ private int mSelectorElementHeight;
+
+ /**
+ * The initial offset of the scroll selector.
+ */
+ private int mInitialScrollOffset = Integer.MIN_VALUE;
+
+ /**
+ * The current offset of the scroll selector.
+ */
+ private int mCurrentScrollOffset;
+
+ /**
+ * The {@link Scroller} responsible for flinging the selector.
+ */
+ private final Scroller mFlingScroller;
+
+ /**
+ * The {@link Scroller} responsible for adjusting the selector.
+ */
+ private final Scroller mAdjustScroller;
+
+ /**
+ * The previous Y coordinate while scrolling the selector.
+ */
+ private int mPreviousScrollerY;
+
+ /**
+ * Handle to the reusable command for setting the input text selection.
+ */
+ private SetSelectionCommand mSetSelectionCommand;
+
+ /**
+ * Handle to the reusable command for adjusting the scroller.
+ */
+ private AdjustScrollerCommand mAdjustScrollerCommand;
+
+ /**
+ * Handle to the reusable command for updating the current value from long
+ * press.
+ */
+ private UpdateValueFromLongPressCommand mUpdateFromLongPressCommand;
+
+ /**
+ * {@link Animator} for showing the up/down arrows.
+ */
+ private final AnimatorSet mShowInputControlsAnimator;
+
+ /**
+ * The Y position of the last down event.
+ */
+ private float mLastDownEventY;
+
+ /**
+ * The Y position of the last motion event.
+ */
+ private float mLastMotionEventY;
+
+ /**
+ * Flag if to begin edit on next up event.
+ */
+ private boolean mBeginEditOnUpEvent;
+
+ /**
+ * Flag if to adjust the selector wheel on next up event.
+ */
+ private boolean mAdjustScrollerOnUpEvent;
+
+ /**
+ * Flag if to draw the selector wheel.
+ */
+ private boolean mDrawSelectorWheel;
+
+ /**
+ * Determines speed during touch scrolling.
+ */
+ private VelocityTracker mVelocityTracker;
+
+ /**
+ * @see ViewConfiguration#getScaledTouchSlop()
+ */
+ private int mTouchSlop;
+
+ /**
+ * @see ViewConfiguration#getScaledMinimumFlingVelocity()
+ */
+ private int mMinimumFlingVelocity;
+
+ /**
+ * @see ViewConfiguration#getScaledMaximumFlingVelocity()
+ */
+ private int mMaximumFlingVelocity;
+
+ /**
+ * Flag whether the selector should wrap around.
+ */
+ private boolean mWrapSelector;
+
+ /**
+ * The back ground color used to optimize scroller fading.
*/
- public NumberPicker(Context context) {
- this(context, null);
+ private final int mSolidColor;
+
+ /**
+ * Reusable {@link Rect} instance.
+ */
+ private final Rect mTempRect = new Rect();
+
+ /**
+ * The callback interface used to indicate the number value has changed.
+ */
+ public interface OnChangedListener {
+ /**
+ * @param picker The NumberPicker associated with this listener.
+ * @param oldVal The previous value.
+ * @param newVal The new value.
+ */
+ void onChanged(NumberPicker picker, int oldVal, int newVal);
+ }
+
+ /**
+ * Interface used to format the number into a string for presentation
+ */
+ public interface Formatter {
+ String toString(int value);
}
/**
* Create a new number picker
- * @param context the application environment
- * @param attrs a collection of attributes
+ *
+ * @param context The application environment.
+ * @param attrs A collection of attributes.
*/
public NumberPicker(Context context, AttributeSet attrs) {
- super(context, attrs);
- setOrientation(VERTICAL);
- LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ this(context, attrs, R.attr.numberPickerStyle);
+ }
+
+ /**
+ * Create a new number picker
+ *
+ * @param context the application environment.
+ * @param attrs a collection of attributes.
+ * @param defStyle The default style to apply to this view.
+ */
+ public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ // process style attributes
+ TypedArray attributesArray = context.obtainStyledAttributes(attrs,
+ R.styleable.NumberPicker, defStyle, 0);
+ int orientation = attributesArray.getInt(R.styleable.NumberPicker_orientation, VERTICAL);
+ setOrientation(orientation);
+ mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
+ attributesArray.recycle();
+
+ // By default Linearlayout that we extend is not drawn. This is
+ // its draw() method is not called but dispatchDraw() is called
+ // directly (see ViewGroup.drawChild()). However, this class uses
+ // the fading edge effect implemented by View and we need our
+ // draw() method to be called. Therefore, we declare we will draw.
+ setWillNotDraw(false);
+ setDrawSelectorWheel(false);
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.number_picker, this, true);
- mHandler = new Handler();
- OnClickListener clickListener = new OnClickListener() {
+ OnClickListener onClickListener = new OnClickListener() {
public void onClick(View v) {
- validateInput(mText);
- if (!mText.hasFocus()) mText.requestFocus();
-
- // now perform the increment/decrement
- if (R.id.increment == v.getId()) {
+ mInputText.clearFocus();
+ if (v.getId() == R.id.increment) {
changeCurrent(mCurrent + 1);
- } else if (R.id.decrement == v.getId()) {
+ } else {
changeCurrent(mCurrent - 1);
}
}
};
- OnFocusChangeListener focusListener = new OnFocusChangeListener() {
- public void onFocusChange(View v, boolean hasFocus) {
+ OnLongClickListener onLongClickListener = new OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ mInputText.clearFocus();
+ if (v.getId() == R.id.increment) {
+ postUpdateValueFromLongPress(UPDATE_STEP_INCREMENT);
+ } else {
+ postUpdateValueFromLongPress(UPDATE_STEP_DECREMENT);
+ }
+ return true;
+ }
+ };
- /* When focus is lost check that the text field
- * has valid values.
- */
+ // increment button
+ mIncrementButton = (ImageButton) findViewById(R.id.increment);
+ mIncrementButton.setOnClickListener(onClickListener);
+ mIncrementButton.setOnLongClickListener(onLongClickListener);
+
+ // decrement button
+ mDecrementButton = (ImageButton) findViewById(R.id.decrement);
+ mDecrementButton.setOnClickListener(onClickListener);
+ mDecrementButton.setOnLongClickListener(onLongClickListener);
+
+ // input text
+ mInputText = (EditText) findViewById(R.id.timepicker_input);
+ mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
+ public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
- validateInput(v);
+ validateInputTextView(v);
}
}
- };
+ });
+ mInputText.setFilters(new InputFilter[] {
+ new InputTextFilter()
+ });
- OnLongClickListener longClickListener = new OnLongClickListener() {
- /**
- * We start the long click here but rely on the {@link NumberPickerButton}
- * to inform us when the long click has ended.
- */
- public boolean onLongClick(View v) {
- /* The text view may still have focus so clear it's focus which will
- * trigger the on focus changed and any typed values to be pulled.
- */
- mText.clearFocus();
-
- if (R.id.increment == v.getId()) {
- mIncrement = true;
- mHandler.post(mRunnable);
- } else if (R.id.decrement == v.getId()) {
- mDecrement = true;
- mHandler.post(mRunnable);
+ mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
+
+ // initialize constants
+ mTouchSlop = ViewConfiguration.getTapTimeout();
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity()
+ / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
+ mTextSize = (int) mInputText.getTextSize();
+
+ // create the selector wheel paint
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setTextAlign(Align.CENTER);
+ paint.setTextSize(mTextSize);
+ paint.setTypeface(mInputText.getTypeface());
+ ColorStateList colors = mInputText.getTextColors();
+ int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
+ paint.setColor(color);
+ mSelectorPaint = paint;
+
+ // create the animator for showing the input controls
+ final ValueAnimator fadeScroller = ObjectAnimator.ofInt(this, "selectorPaintAlpha", 255, 0);
+ final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton,
+ "alpha", 0, 1);
+ final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton,
+ "alpha", 0, 1);
+ mShowInputControlsAnimator = new AnimatorSet();
+ mShowInputControlsAnimator.playTogether(fadeScroller, showIncrementButton,
+ showDecrementButton);
+ mShowInputControlsAnimator.setDuration(getResources().getInteger(
+ R.integer.config_longAnimTime));
+ mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCanceled) {
+ // if canceled => we still want the wheel drawn
+ setDrawSelectorWheel(false);
}
- return true;
+ mCanceled = false;
+ mSelectorPaint.setAlpha(255);
+ invalidate();
}
- };
- InputFilter inputFilter = new NumberPickerInputFilter();
- mNumberInputFilter = new NumberRangeKeyListener();
- mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
- mIncrementButton.setOnClickListener(clickListener);
- mIncrementButton.setOnLongClickListener(longClickListener);
- mIncrementButton.setNumberPicker(this);
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mShowInputControlsAnimator.isRunning()) {
+ mCanceled = true;
+ }
+ }
+ });
- mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
- mDecrementButton.setOnClickListener(clickListener);
- mDecrementButton.setOnLongClickListener(longClickListener);
- mDecrementButton.setNumberPicker(this);
+ // create the fling and adjust scrollers
+ mFlingScroller = new Scroller(getContext());
+ mAdjustScroller = new Scroller(getContext(), new OvershootInterpolator());
- mText = (EditText) findViewById(R.id.timepicker_input);
- mText.setOnFocusChangeListener(focusListener);
- mText.setFilters(new InputFilter[] {inputFilter});
- mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
+ updateInputTextView();
+ updateIncrementAndDecrementButtonsVisibilityState();
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (!hasWindowFocus) {
+ removeAllCallbacks();
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastMotionEventY = mLastDownEventY = event.getY();
+ removeAllCallbacks();
+ mBeginEditOnUpEvent = false;
+ mAdjustScrollerOnUpEvent = true;
+ if (mDrawSelectorWheel) {
+ mBeginEditOnUpEvent = mFlingScroller.isFinished()
+ && mAdjustScroller.isFinished();
+ mAdjustScrollerOnUpEvent = true;
+ mFlingScroller.forceFinished(true);
+ mAdjustScroller.forceFinished(true);
+ hideInputControls();
+ return true;
+ }
+ if (isEventInInputText(event)) {
+ mAdjustScrollerOnUpEvent = false;
+ setDrawSelectorWheel(true);
+ hideInputControls();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ float currentMoveY = event.getY();
+ int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
+ if (deltaDownY > mTouchSlop) {
+ mBeginEditOnUpEvent = false;
+ setDrawSelectorWheel(true);
+ hideInputControls();
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+ int action = ev.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ float currentMoveY = ev.getY();
+ if (mBeginEditOnUpEvent) {
+ int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
+ if (deltaDownY > mTouchSlop) {
+ mBeginEditOnUpEvent = false;
+ }
+ }
+ int deltaMoveY = (int) (currentMoveY - mLastMotionEventY);
+ scrollBy(0, deltaMoveY);
+ invalidate();
+ mLastMotionEventY = currentMoveY;
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mBeginEditOnUpEvent) {
+ setDrawSelectorWheel(false);
+ showInputControls();
+ mInputText.requestFocus();
+ InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mInputText, 0);
+ return true;
+ }
+ VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+ int initialVelocity = (int) velocityTracker.getYVelocity();
+ if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
+ fling(initialVelocity);
+ } else {
+ if (mAdjustScrollerOnUpEvent) {
+ if (mFlingScroller.isFinished() && mAdjustScroller.isFinished()) {
+ postAdjustScrollerCommand(0);
+ }
+ } else {
+ postAdjustScrollerCommand(SHOW_INPUT_CONTROLS_DELAY_MILLIS);
+ }
+ }
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ if ((action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP)
+ && !isEventInInputText(event)) {
+ removeAllCallbacks();
+ }
+ return super.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
+ removeAllCallbacks();
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ removeAllCallbacks();
+ }
+ return super.dispatchTrackballEvent(event);
+ }
- if (!isEnabled()) {
- setEnabled(false);
+ @Override
+ public void computeScroll() {
+ if (!mDrawSelectorWheel) {
+ return;
+ }
+ Scroller scroller = mFlingScroller;
+ if (scroller.isFinished()) {
+ scroller = mAdjustScroller;
+ if (scroller.isFinished()) {
+ return;
+ }
+ }
+ scroller.computeScrollOffset();
+ int currentScrollerY = scroller.getCurrY();
+ if (mPreviousScrollerY == 0) {
+ mPreviousScrollerY = scroller.getStartY();
+ }
+ scrollBy(0, currentScrollerY - mPreviousScrollerY);
+ mPreviousScrollerY = currentScrollerY;
+ if (scroller.isFinished()) {
+ onScrollerFinished(scroller);
+ } else {
+ invalidate();
}
}
@@ -222,11 +638,57 @@ public class NumberPicker extends LinearLayout {
super.setEnabled(enabled);
mIncrementButton.setEnabled(enabled);
mDecrementButton.setEnabled(enabled);
- mText.setEnabled(enabled);
+ mInputText.setEnabled(enabled);
+ }
+
+ /**
+ * Scrolls the selector with the given <code>vertical offset</code>.
+ */
+ @Override
+ public void scrollBy(int x, int y) {
+ int[] selectorIndices = getSelectorIndices();
+ if (mInitialScrollOffset == Integer.MIN_VALUE) {
+ int totalTextHeight = selectorIndices.length * mTextSize;
+ int totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
+ int textGapCount = selectorIndices.length - 1;
+ int selectorTextGapHeight = totalTextGapHeight / textGapCount;
+ // compensate for integer division loss of the components used to
+ // calculate the text gap
+ int integerDivisionLoss = (mTextSize + mBottom - mTop) % textGapCount;
+ mInitialScrollOffset = mCurrentScrollOffset = mTextSize - integerDivisionLoss / 2;
+ mSelectorElementHeight = mTextSize + selectorTextGapHeight;
+ }
+
+ if (!mWrapSelector && y > 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mStart) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ return;
+ }
+ if (!mWrapSelector && y < 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mEnd) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ return;
+ }
+ mCurrentScrollOffset += y;
+ while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorElementHeight) {
+ mCurrentScrollOffset -= mSelectorElementHeight;
+ decrementSelectorIndices(selectorIndices);
+ changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
+ if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mStart) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ }
+ }
+ while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorElementHeight) {
+ mCurrentScrollOffset += mSelectorElementHeight;
+ incrementScrollSelectorIndices(selectorIndices);
+ changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
+ if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mEnd) {
+ mCurrentScrollOffset = mInitialScrollOffset;
+ }
+ }
}
/**
* Set the callback that indicates the number has been adjusted by the user.
+ *
* @param listener the callback, should not be null.
*/
public void setOnChangeListener(OnChangedListener listener) {
@@ -235,292 +697,678 @@ public class NumberPicker extends LinearLayout {
/**
* Set the formatter that will be used to format the number for presentation
- * @param formatter the formatter object. If formatter is null, String.valueOf()
- * will be used
+ *
+ * @param formatter the formatter object. If formatter is null,
+ * String.valueOf() will be used
*/
public void setFormatter(Formatter formatter) {
mFormatter = formatter;
}
/**
- * Set the range of numbers allowed for the number picker. The current
- * value will be automatically set to the start.
+ * Set the range of numbers allowed for the number picker. The current value
+ * will be automatically set to the start.
*
* @param start the start of the range (inclusive)
* @param end the end of the range (inclusive)
*/
public void setRange(int start, int end) {
- setRange(start, end, null/*displayedValues*/);
+ setRange(start, end, null);
}
/**
- * Set the range of numbers allowed for the number picker. The current
- * value will be automatically set to the start. Also provide a mapping
- * for values used to display to the user.
+ * Set the range of numbers allowed for the number picker. The current value
+ * will be automatically set to the start. Also provide a mapping for values
+ * used to display to the user.
*
* @param start the start of the range (inclusive)
* @param end the end of the range (inclusive)
* @param displayedValues the values displayed to the user.
*/
public void setRange(int start, int end, String[] displayedValues) {
+ boolean wrapSelector = (end - start) >= mSelectorIndices.length;
+ setRange(start, end, displayedValues, wrapSelector);
+ }
+
+ /**
+ * Set the range of numbers allowed for the number picker. The current value
+ * will be automatically set to the start. Also provide a mapping for values
+ * used to display to the user.
+ *
+ * @param start the start of the range (inclusive)
+ * @param end the end of the range (inclusive)
+ * @param displayedValues the values displayed to the user.
+ */
+ public void setRange(int start, int end, String[] displayedValues, boolean wrapSelector) {
+ if (start < 0 || end < 0) {
+ throw new IllegalArgumentException("start and end must be > 0");
+ }
+
mDisplayedValues = displayedValues;
mStart = start;
mEnd = end;
mCurrent = start;
- updateView();
+
+ setWrapSelector(wrapSelector);
+ updateInputTextView();
if (displayedValues != null) {
// Allow text entry rather than strictly numeric entry.
- mText.setRawInputType(InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ } else {
+ mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
}
+
+ // make sure cached string representations are dropped
+ mSelectorIndexToStringCache.clear();
}
/**
* Set the current value for the number picker.
*
* @param current the current value the start of the range (inclusive)
- * @throws IllegalArgumentException when current is not within the range
- * of of the number picker
+ * @throws IllegalArgumentException when current is not within the range of
+ * of the number picker
*/
public void setCurrent(int current) {
if (current < mStart || current > mEnd) {
- throw new IllegalArgumentException(
- "current should be >= start and <= end");
+ throw new IllegalArgumentException("current should be >= start and <= end");
}
mCurrent = current;
- updateView();
+ updateInputTextView();
+ updateIncrementAndDecrementButtonsVisibilityState();
+ }
+
+ /**
+ * Sets whether the selector shown during flinging/scrolling should wrap
+ * around the beginning and end values.
+ *
+ * @param wrapSelector Whether to wrap.
+ */
+ public void setWrapSelector(boolean wrapSelector) {
+ if (wrapSelector && (mEnd - mStart) < mSelectorIndices.length) {
+ throw new IllegalStateException("Range less than selector items count.");
+ }
+ if (wrapSelector != mWrapSelector) {
+ // force the selector indices array to be reinitialized
+ mSelectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] = Integer.MAX_VALUE;
+ mWrapSelector = wrapSelector;
+ }
}
/**
- * Sets the speed at which the numbers will scroll when the +/-
- * buttons are longpressed
+ * Sets the speed at which the numbers will scroll when the +/- buttons are
+ * longpressed
*
* @param speed The speed (in milliseconds) at which the numbers will scroll
- * default 300ms
+ * default 300ms
*/
public void setSpeed(long speed) {
- mSpeed = speed;
+ mLongPressUpdateSpeed = speed;
}
- private String formatNumber(int value) {
- return (mFormatter != null)
- ? mFormatter.toString(value)
- : String.valueOf(value);
+ /**
+ * Returns the current value of the NumberPicker
+ *
+ * @return the current value.
+ */
+ public int getCurrent() {
+ return mCurrent;
+ }
+
+ @Override
+ public int getSolidColor() {
+ return mSolidColor;
+ }
+
+ @Override
+ protected float getTopFadingEdgeStrength() {
+ return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
+ }
+
+ @Override
+ protected float getBottomFadingEdgeStrength() {
+ return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeAllCallbacks();
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ // There is a good reason for doing this. See comments in draw().
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // Dispatch draw to our children only if we are not currently running
+ // the animation for simultaneously fading out the scroll wheel and
+ // showing in the buttons. This class takes advantage of the View
+ // implementation of fading edges effect to draw the selector wheel.
+ // However, in View.draw(), the fading is applied after all the children
+ // have been drawn and we do not want this fading to be applied to the
+ // buttons which are currently showing in. Therefore, we draw our
+ // children
+ // after we have completed drawing ourselves.
+
+ // Draw the selector wheel if needed
+ if (mDrawSelectorWheel) {
+ super.draw(canvas);
+ }
+
+ // Draw our children if we are not showing the selector wheel of fading
+ // it out
+ if (mShowInputControlsAnimator.isRunning() || !mDrawSelectorWheel) {
+ long drawTime = getDrawingTime();
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ View child = getChildAt(i);
+ if (!child.isShown()) {
+ continue;
+ }
+ drawChild(canvas, getChildAt(i), drawTime);
+ }
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // we only draw the selector wheel
+ if (!mDrawSelectorWheel) {
+ return;
+ }
+ float x = (mRight - mLeft) / 2;
+ float y = mCurrentScrollOffset;
+
+ int[] selectorIndices = getSelectorIndices();
+ for (int i = 0; i < selectorIndices.length; i++) {
+ int selectorIndex = selectorIndices[i];
+ String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
+ canvas.drawText(scrollSelectorValue, x, y, mSelectorPaint);
+ y += mSelectorElementHeight;
+ }
}
/**
- * Sets the current value of this NumberPicker, and sets mPrevious to the previous
- * value. If current is greater than mEnd less than mStart, the value of mCurrent
- * is wrapped around.
+ * Returns the upper value of the range of the NumberPicker
*
- * Subclasses can override this to change the wrapping behavior
+ * @return the uppper number of the range.
+ */
+ protected int getEndRange() {
+ return mEnd;
+ }
+
+ /**
+ * Returns the lower value of the range of the NumberPicker
+ *
+ * @return the lower number of the range.
+ */
+ protected int getBeginRange() {
+ return mStart;
+ }
+
+ /**
+ * Sets the current value of this NumberPicker, and sets mPrevious to the
+ * previous value. If current is greater than mEnd less than mStart, the
+ * value of mCurrent is wrapped around. Subclasses can override this to
+ * change the wrapping behavior
*
* @param current the new value of the NumberPicker
*/
- protected void changeCurrent(int current) {
+ private void changeCurrent(int current) {
+ if (mCurrent == current) {
+ return;
+ }
// Wrap around the values if we go past the start or end
- if (current > mEnd) {
- current = mStart;
- } else if (current < mStart) {
- current = mEnd;
+ if (mWrapSelector) {
+ current = getWrappedSelectorIndex(current);
}
- mPrevious = mCurrent;
- mCurrent = current;
- notifyChange();
- updateView();
+ int previous = mCurrent;
+ setCurrent(current);
+ notifyChange(previous, current);
}
/**
- * Notifies the listener, if registered, of a change of the value of this
- * NumberPicker.
+ * Sets the <code>alpha</code> of the {@link Paint} for drawing the selector
+ * wheel.
*/
- private void notifyChange() {
- if (mListener != null) {
- mListener.onChanged(this, mPrevious, mCurrent);
+ @SuppressWarnings("unused")
+ // Called by ShowInputControlsAnimator via reflection
+ private void setSelectorPaintAlpha(int alpha) {
+ mSelectorPaint.setAlpha(alpha);
+ if (mDrawSelectorWheel) {
+ invalidate();
}
}
/**
- * Updates the view of this NumberPicker. If displayValues were specified
- * in {@link #setRange}, the string corresponding to the index specified by
- * the current value will be returned. Otherwise, the formatter specified
- * in {@link setFormatter} will be used to format the number.
+ * @return If the <code>event</code> is in the input text.
*/
- private void updateView() {
- /* If we don't have displayed values then use the
- * current number else find the correct value in the
- * displayed values for the current number.
- */
- if (mDisplayedValues == null) {
- mText.setText(formatNumber(mCurrent));
+ private boolean isEventInInputText(MotionEvent event) {
+ mInputText.getHitRect(mTempRect);
+ return mTempRect.contains((int) event.getX(), (int) event.getY());
+ }
+
+ /**
+ * Sets if to <code>drawSelectionWheel</code>.
+ */
+ private void setDrawSelectorWheel(boolean drawSelectorWheel) {
+ mDrawSelectorWheel = drawSelectorWheel;
+ // do not fade if the selector wheel not shown
+ setVerticalFadingEdgeEnabled(drawSelectorWheel);
+ }
+
+ /**
+ * Callback invoked upon completion of a given <code>scroller</code>.
+ */
+ private void onScrollerFinished(Scroller scroller) {
+ if (scroller == mFlingScroller) {
+ postAdjustScrollerCommand(0);
} else {
- mText.setText(mDisplayedValues[mCurrent - mStart]);
+ showInputControls();
+ updateInputTextView();
}
- mText.setSelection(mText.getText().length());
}
- private void validateCurrentView(CharSequence str) {
- int val = getSelectedPos(str.toString());
- if ((val >= mStart) && (val <= mEnd)) {
- if (mCurrent != val) {
- mPrevious = mCurrent;
- mCurrent = val;
- notifyChange();
+ /**
+ * Flings the selector with the given <code>velocityY</code>.
+ */
+ private void fling(int velocityY) {
+ mPreviousScrollerY = 0;
+ Scroller flingScroller = mFlingScroller;
+
+ if (mWrapSelector) {
+ if (velocityY > 0) {
+ flingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
+ } else {
+ flingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
+ }
+ } else {
+ if (velocityY > 0) {
+ int maxY = mTextSize * (mCurrent - mStart);
+ flingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, maxY);
+ } else {
+ int startY = mTextSize * (mEnd - mCurrent);
+ int maxY = startY;
+ flingScroller.fling(0, startY, 0, velocityY, 0, 0, 0, maxY);
}
}
- updateView();
+
+ postAdjustScrollerCommand(flingScroller.getDuration());
+ invalidate();
}
- private void validateInput(View v) {
- String str = String.valueOf(((TextView) v).getText());
- if ("".equals(str)) {
+ /**
+ * Hides the input controls which is the up/down arrows and the text field.
+ */
+ private void hideInputControls() {
+ mShowInputControlsAnimator.cancel();
+ mIncrementButton.setVisibility(INVISIBLE);
+ mDecrementButton.setVisibility(INVISIBLE);
+ mInputText.setVisibility(INVISIBLE);
+ }
- // Restore to the old value as we don't allow empty values
- updateView();
- } else {
+ /**
+ * Show the input controls by making them visible and animating the alpha
+ * property up/down arrows.
+ */
+ private void showInputControls() {
+ updateIncrementAndDecrementButtonsVisibilityState();
+ mInputText.setVisibility(VISIBLE);
+ mShowInputControlsAnimator.start();
+ }
- // Check the new value and ensure it's in range
- validateCurrentView(str);
+ /**
+ * Updates the visibility state of the increment and decrement buttons.
+ */
+ private void updateIncrementAndDecrementButtonsVisibilityState() {
+ if (mWrapSelector || mCurrent < mEnd) {
+ mIncrementButton.setVisibility(VISIBLE);
+ } else {
+ mIncrementButton.setVisibility(INVISIBLE);
+ }
+ if (mWrapSelector || mCurrent > mStart) {
+ mDecrementButton.setVisibility(VISIBLE);
+ } else {
+ mDecrementButton.setVisibility(INVISIBLE);
}
}
/**
- * @hide
+ * @return The selector indices array with proper values with the current as
+ * the middle one.
*/
- public void cancelIncrement() {
- mIncrement = false;
+ private int[] getSelectorIndices() {
+ int current = getCurrent();
+ if (mSelectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] != current) {
+ for (int i = 0; i < mSelectorIndices.length; i++) {
+ int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
+ if (mWrapSelector) {
+ selectorIndex = getWrappedSelectorIndex(selectorIndex);
+ }
+ mSelectorIndices[i] = selectorIndex;
+ ensureCachedScrollSelectorValue(mSelectorIndices[i]);
+ }
+ }
+ return mSelectorIndices;
}
/**
- * @hide
+ * @return The wrapped index <code>selectorIndex</code> value.
+ * <p>
+ * Note: The absolute value of the argument is never larger than
+ * mEnd - mStart.
+ * </p>
*/
- public void cancelDecrement() {
- mDecrement = false;
+ private int getWrappedSelectorIndex(int selectorIndex) {
+ if (selectorIndex > mEnd) {
+ return (Math.abs(selectorIndex) - mEnd);
+ } else if (selectorIndex < mStart) {
+ return (mEnd - Math.abs(selectorIndex));
+ }
+ return selectorIndex;
}
- private static final char[] DIGIT_CHARACTERS = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
- };
+ /**
+ * Increments the <code>selectorIndices</code> whose string representations
+ * will be displayed in the selector.
+ */
+ private void incrementScrollSelectorIndices(int[] selectorIndices) {
+ for (int i = 0; i < selectorIndices.length - 1; i++) {
+ selectorIndices[i] = selectorIndices[i + 1];
+ }
+ int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
+ if (mWrapSelector && nextScrollSelectorIndex > mEnd) {
+ nextScrollSelectorIndex = mStart;
+ }
+ selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
+ ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
+ }
- private NumberPickerButton mIncrementButton;
- private NumberPickerButton mDecrementButton;
+ /**
+ * Decrements the <code>selectorIndices</code> whose string representations
+ * will be displayed in the selector.
+ */
+ private void decrementSelectorIndices(int[] selectorIndices) {
+ for (int i = selectorIndices.length - 1; i > 0; i--) {
+ selectorIndices[i] = selectorIndices[i - 1];
+ }
+ int nextScrollSelectorIndex = selectorIndices[1] - 1;
+ if (mWrapSelector && nextScrollSelectorIndex < mStart) {
+ nextScrollSelectorIndex = mEnd;
+ }
+ selectorIndices[0] = nextScrollSelectorIndex;
+ ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
+ }
- private class NumberPickerInputFilter implements InputFilter {
- public CharSequence filter(CharSequence source, int start, int end,
- Spanned dest, int dstart, int dend) {
- if (mDisplayedValues == null) {
- return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
- }
- CharSequence filtered = String.valueOf(source.subSequence(start, end));
- String result = String.valueOf(dest.subSequence(0, dstart))
- + filtered
- + dest.subSequence(dend, dest.length());
- String str = String.valueOf(result).toLowerCase();
- for (String val : mDisplayedValues) {
- val = val.toLowerCase();
- if (val.startsWith(str)) {
- return filtered;
- }
+ /**
+ * Ensures we have a cached string representation of the given <code>
+ * selectorIndex</code>
+ * to avoid multiple instantiations of the same string.
+ */
+ private void ensureCachedScrollSelectorValue(int selectorIndex) {
+ SparseArray<String> cache = mSelectorIndexToStringCache;
+ String scrollSelectorValue = cache.get(selectorIndex);
+ if (scrollSelectorValue != null) {
+ return;
+ }
+ if (selectorIndex < mStart || selectorIndex > mEnd) {
+ scrollSelectorValue = "";
+ } else {
+ if (mDisplayedValues != null) {
+ scrollSelectorValue = mDisplayedValues[selectorIndex];
+ } else {
+ scrollSelectorValue = formatNumber(selectorIndex);
}
- return "";
}
+ cache.put(selectorIndex, scrollSelectorValue);
}
- private class NumberRangeKeyListener extends NumberKeyListener {
+ private String formatNumber(int value) {
+ return (mFormatter != null) ? mFormatter.toString(value) : String.valueOf(value);
+ }
- // XXX This doesn't allow for range limits when controlled by a
- // soft input method!
- public int getInputType() {
- return InputType.TYPE_CLASS_NUMBER;
+ private void validateInputTextView(View v) {
+ String str = String.valueOf(((TextView) v).getText());
+ if (TextUtils.isEmpty(str)) {
+ // Restore to the old value as we don't allow empty values
+ updateInputTextView();
+ } else {
+ // Check the new value and ensure it's in range
+ int current = getSelectedPos(str.toString());
+ changeCurrent(current);
}
+ }
- @Override
- protected char[] getAcceptedChars() {
- return DIGIT_CHARACTERS;
+ /**
+ * Updates the view of this NumberPicker. If displayValues were specified in
+ * {@link #setRange}, the string corresponding to the index specified by the
+ * current value will be returned. Otherwise, the formatter specified in
+ * {@link #setFormatter} will be used to format the number.
+ */
+ private void updateInputTextView() {
+ /*
+ * If we don't have displayed values then use the current number else
+ * find the correct value in the displayed values for the current
+ * number.
+ */
+ if (mDisplayedValues == null) {
+ mInputText.setText(formatNumber(mCurrent));
+ } else {
+ mInputText.setText(mDisplayedValues[mCurrent - mStart]);
}
+ mInputText.setSelection(mInputText.getText().length());
+ }
- @Override
- public CharSequence filter(CharSequence source, int start, int end,
- Spanned dest, int dstart, int dend) {
-
- CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
- if (filtered == null) {
- filtered = source.subSequence(start, end);
- }
-
- String result = String.valueOf(dest.subSequence(0, dstart))
- + filtered
- + dest.subSequence(dend, dest.length());
+ /**
+ * Notifies the listener, if registered, of a change of the value of this
+ * NumberPicker.
+ */
+ private void notifyChange(int previous, int current) {
+ if (mListener != null) {
+ mListener.onChanged(this, previous, mCurrent);
+ }
+ }
- if ("".equals(result)) {
- return result;
- }
- int val = getSelectedPos(result);
+ /**
+ * Posts a command for updating the current value every <code>updateMillis
+ * </code>.
+ */
+ private void postUpdateValueFromLongPress(int updateMillis) {
+ mInputText.clearFocus();
+ removeAllCallbacks();
+ if (mUpdateFromLongPressCommand == null) {
+ mUpdateFromLongPressCommand = new UpdateValueFromLongPressCommand();
+ }
+ mUpdateFromLongPressCommand.setUpdateStep(updateMillis);
+ post(mUpdateFromLongPressCommand);
+ }
- /* Ensure the user can't type in a value greater
- * than the max allowed. We have to allow less than min
- * as the user might want to delete some numbers
- * and then type a new number.
- */
- if (val > mEnd) {
- return "";
- } else {
- return filtered;
- }
+ /**
+ * Removes all pending callback from the message queue.
+ */
+ private void removeAllCallbacks() {
+ if (mUpdateFromLongPressCommand != null) {
+ removeCallbacks(mUpdateFromLongPressCommand);
+ }
+ if (mAdjustScrollerCommand != null) {
+ removeCallbacks(mAdjustScrollerCommand);
+ }
+ if (mSetSelectionCommand != null) {
+ removeCallbacks(mSetSelectionCommand);
}
}
- private int getSelectedPos(String str) {
+ /**
+ * @return The selected index given its displayed <code>value</code>.
+ */
+ private int getSelectedPos(String value) {
if (mDisplayedValues == null) {
try {
- return Integer.parseInt(str);
+ return Integer.parseInt(value);
} catch (NumberFormatException e) {
- /* Ignore as if it's not a number we don't care */
+ // Ignore as if it's not a number we don't care
}
} else {
for (int i = 0; i < mDisplayedValues.length; i++) {
- /* Don't force the user to type in jan when ja will do */
- str = str.toLowerCase();
- if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
+ // Don't force the user to type in jan when ja will do
+ value = value.toLowerCase();
+ if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
return mStart + i;
}
}
- /* The user might have typed in a number into the month field i.e.
+ /*
+ * The user might have typed in a number into the month field i.e.
* 10 instead of OCT so support that too.
*/
try {
- return Integer.parseInt(str);
+ return Integer.parseInt(value);
} catch (NumberFormatException e) {
- /* Ignore as if it's not a number we don't care */
+ // Ignore as if it's not a number we don't care
}
}
return mStart;
}
/**
- * Returns the current value of the NumberPicker
- * @return the current value.
+ * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
+ * </code> to
+ * <code>selectionEnd</code>.
*/
- public int getCurrent() {
- return mCurrent;
+ private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
+ if (mSetSelectionCommand == null) {
+ mSetSelectionCommand = new SetSelectionCommand();
+ } else {
+ removeCallbacks(mSetSelectionCommand);
+ }
+ mSetSelectionCommand.mSelectionStart = selectionStart;
+ mSetSelectionCommand.mSelectionEnd = selectionEnd;
+ post(mSetSelectionCommand);
}
/**
- * Returns the upper value of the range of the NumberPicker
- * @return the uppper number of the range.
+ * Posts an {@link AdjustScrollerCommand} within the given <code>
+ * delayMillis</code>
+ * .
*/
- protected int getEndRange() {
- return mEnd;
+ private void postAdjustScrollerCommand(int delayMillis) {
+ if (mAdjustScrollerCommand == null) {
+ mAdjustScrollerCommand = new AdjustScrollerCommand();
+ } else {
+ removeCallbacks(mAdjustScrollerCommand);
+ }
+ postDelayed(mAdjustScrollerCommand, delayMillis);
}
/**
- * Returns the lower value of the range of the NumberPicker
- * @return the lower number of the range.
+ * Filter for accepting only valid indices or prefixes of the string
+ * representation of valid indices.
*/
- protected int getBeginRange() {
- return mStart;
+ class InputTextFilter extends NumberKeyListener {
+
+ // XXX This doesn't allow for range limits when controlled by a
+ // soft input method!
+ public int getInputType() {
+ return InputType.TYPE_CLASS_TEXT;
+ }
+
+ @Override
+ protected char[] getAcceptedChars() {
+ return DIGIT_CHARACTERS;
+ }
+
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
+ int dstart, int dend) {
+ if (mDisplayedValues == null) {
+ CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
+ if (filtered == null) {
+ filtered = source.subSequence(start, end);
+ }
+
+ String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
+ + dest.subSequence(dend, dest.length());
+
+ if ("".equals(result)) {
+ return result;
+ }
+ int val = getSelectedPos(result);
+
+ /*
+ * Ensure the user can't type in a value greater than the max
+ * allowed. We have to allow less than min as the user might
+ * want to delete some numbers and then type a new number.
+ */
+ if (val > mEnd) {
+ return "";
+ } else {
+ return filtered;
+ }
+ } else {
+ CharSequence filtered = String.valueOf(source.subSequence(start, end));
+ if (TextUtils.isEmpty(filtered)) {
+ return "";
+ }
+ String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
+ + dest.subSequence(dend, dest.length());
+ String str = String.valueOf(result).toLowerCase();
+ for (String val : mDisplayedValues) {
+ String valLowerCase = val.toLowerCase();
+ if (valLowerCase.startsWith(str)) {
+ postSetSelectionCommand(result.length(), val.length());
+ return val.subSequence(dstart, val.length());
+ }
+ }
+ return "";
+ }
+ }
+ }
+
+ /**
+ * Command for setting the input text selection.
+ */
+ class SetSelectionCommand implements Runnable {
+ private int mSelectionStart;
+
+ private int mSelectionEnd;
+
+ public void run() {
+ mInputText.setSelection(mSelectionStart, mSelectionEnd);
+ }
+ }
+
+ /**
+ * Command for adjusting the scroller to show in its center the closest of
+ * the displayed items.
+ */
+ class AdjustScrollerCommand implements Runnable {
+ public void run() {
+ mPreviousScrollerY = 0;
+ int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
+ float delayCoef = (float) Math.abs(deltaY) / (float) mTextSize;
+ int duration = (int) (delayCoef * SELECTOR_ADJUSTMENT_DURATION_MILLIS);
+ mAdjustScroller.startScroll(0, 0, 0, deltaY, duration);
+ invalidate();
+ }
+ }
+
+ /**
+ * Command for updating the current value from a long press.
+ */
+ class UpdateValueFromLongPressCommand implements Runnable {
+ private int mUpdateStep = 0;
+
+ private void setUpdateStep(int updateStep) {
+ mUpdateStep = updateStep;
+ }
+
+ public void run() {
+ changeCurrent(mCurrent + mUpdateStep);
+ postDelayed(this, mLongPressUpdateSpeed);
+ }
}
}
diff --git a/core/java/android/widget/NumberPickerButton.java b/core/java/android/widget/NumberPickerButton.java
deleted file mode 100644
index 292b668..0000000
--- a/core/java/android/widget/NumberPickerButton.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2008 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 android.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.widget.ImageButton;
-import android.widget.NumberPicker;
-
-import com.android.internal.R;
-
-/**
- * This class exists purely to cancel long click events, that got
- * started in NumberPicker
- */
-class NumberPickerButton extends ImageButton {
-
- private NumberPicker mNumberPicker;
-
- public NumberPickerButton(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public NumberPickerButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public NumberPickerButton(Context context) {
- super(context);
- }
-
- public void setNumberPicker(NumberPicker picker) {
- mNumberPicker = picker;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- cancelLongpressIfRequired(event);
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- cancelLongpressIfRequired(event);
- return super.onTrackballEvent(event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
- || (keyCode == KeyEvent.KEYCODE_ENTER)) {
- cancelLongpress();
- }
- return super.onKeyUp(keyCode, event);
- }
-
- private void cancelLongpressIfRequired(MotionEvent event) {
- if ((event.getAction() == MotionEvent.ACTION_CANCEL)
- || (event.getAction() == MotionEvent.ACTION_UP)) {
- cancelLongpress();
- }
- }
-
- private void cancelLongpress() {
- if (R.id.increment == getId()) {
- mNumberPicker.cancelIncrement();
- } else if (R.id.decrement == getId()) {
- mNumberPicker.cancelDecrement();
- }
- }
-
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus) {
- cancelLongpress();
- }
- }
-
-}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 88d3f7a..75ba704 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7024,8 +7024,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
getInsertionController().show();
}
}
- } else if (hasSelection() && hasSelectionController()) {
- getSelectionController().show();
}
}
@@ -7103,7 +7101,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- if (isTextEditable()) {
+ if (isTextEditable() || mTextIsSelectable) {
if (mScrollX != oldScrollX || mScrollY != oldScrollY) {
// Hide insertion anchor while scrolling. Leave selection.
hideInsertionPointCursorController();
@@ -7153,7 +7151,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mInsertionControllerEnabled = windowSupportsHandles && isTextEditable() && mCursorVisible &&
- mLayout != null && !mTextIsSelectable;
+ mLayout != null;
mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
mLayout != null;
@@ -7172,8 +7170,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* a selectable TextView.
*/
private boolean isTextEditable() {
- return (mText instanceof Editable && onCheckIsTextEditor() && isEnabled())
- || mTextIsSelectable;
+ return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
}
/**
@@ -7748,7 +7745,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (clip != null) {
- clipboard.setPrimaryClip(clip);
+ setPrimaryClip(clip);
}
}
return true;
@@ -7839,26 +7836,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) && mInsertionControllerEnabled) {
+ if (!isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
+ mInsertionControllerEnabled) {
+ // Long press in empty space moves cursor and shows the Paste affordance if available.
final int offset = getOffset(mLastDownPositionX, mLastDownPositionY);
Selection.setSelection((Spannable)mText, offset);
- if (canPaste()) {
- getInsertionController().showWithPaste();
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- } else {
- getInsertionController().show();
- }
+ getInsertionController().show(0);
mEatTouchRelease = true;
return true;
}
- if (mSelectionActionMode != null && touchPositionIsInSelection()) {
- final int start = getSelectionStart();
- final int end = getSelectionEnd();
- CharSequence selectedText = mTransformed.subSequence(start, end);
- ClipData data = ClipData.newPlainText(null, null, selectedText);
- startDrag(data, getTextThumbnailBuilder(selectedText), false);
- stopSelectionActionMode();
+ if (mSelectionActionMode != null) {
+ if (touchPositionIsInSelection()) {
+ // Start a drag
+ final int start = getSelectionStart();
+ final int end = getSelectionEnd();
+ CharSequence selectedText = mTransformed.subSequence(start, end);
+ ClipData data = ClipData.newPlainText(null, null, selectedText);
+ startDrag(data, getTextThumbnailBuilder(selectedText), false);
+ stopSelectionActionMode();
+ } else {
+ selectCurrentWord();
+ getSelectionController().show();
+ }
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
mEatTouchRelease = true;
return true;
@@ -7950,10 +7950,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Paste clipboard content between min and max positions.
- *
- * @param clipboard getSystemService(Context.CLIPBOARD_SERVICE)
*/
- private void paste(ClipboardManager clipboard, int min, int max) {
+ private void paste(int min, int max) {
+ ClipboardManager clipboard =
+ (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {
boolean didfirst = false;
@@ -7973,9 +7973,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
stopSelectionActionMode();
+ sLastCutOrCopyTime = 0;
}
}
+ private void setPrimaryClip(ClipData clip) {
+ ClipboardManager clipboard = (ClipboardManager) getContext().
+ getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(clip);
+ sLastCutOrCopyTime = SystemClock.uptimeMillis();
+ }
+
private class SelectionActionModeCallback implements ActionMode.Callback {
@Override
@@ -8061,9 +8069,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- ClipboardManager clipboard = (ClipboardManager) getContext().
- getSystemService(Context.CLIPBOARD_SERVICE);
-
int min = 0;
int max = mText.length();
@@ -8077,18 +8082,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (item.getItemId()) {
case ID_PASTE:
- paste(clipboard, min, max);
+ paste(min, max);
return true;
case ID_CUT:
- clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+ setPrimaryClip(ClipData.newPlainText(null, null,
mTransformed.subSequence(min, max)));
((Editable) mText).delete(min, max);
stopSelectionActionMode();
return true;
case ID_COPY:
- clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+ setPrimaryClip(ClipData.newPlainText(null, null,
mTransformed.subSequence(min, max)));
stopSelectionActionMode();
return true;
@@ -8211,9 +8216,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public void onClick(View v) {
if (canPaste()) {
- ClipboardManager clipboard =
- (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- paste(clipboard, getSelectionStart(), getSelectionEnd());
+ paste(getSelectionStart(), getSelectionEnd());
}
hide();
}
@@ -8502,6 +8505,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
case MotionEvent.ACTION_UP:
if (mPastePopupWindow != null) {
+ // Will show the paste popup after a delay.
+ mController.show();
+ /* TEMP USER TEST: Display Paste as soon as handle is draggged
long delay = SystemClock.uptimeMillis() - mTouchTimer;
if (delay < ViewConfiguration.getTapTimeout()) {
final float touchOffsetX = ev.getRawX() - mPositionX;
@@ -8515,7 +8521,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (distanceSquared < slopSquared) {
showPastePopupWindow();
}
- }
+ }*/
}
mIsDragging = false;
break;
@@ -8561,6 +8567,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private class InsertionPointCursorController implements CursorController {
private static final int DELAY_BEFORE_FADE_OUT = 4100;
+ private static final int DELAY_BEFORE_PASTE = 2000;
+ private static final int RECENT_CUT_COPY_DURATION = 15 * 1000;
// The cursor controller image. Lazily created.
private HandleView mHandle;
@@ -8571,14 +8579,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
};
+ private final Runnable mPastePopupShower = new Runnable() {
+ public void run() {
+ getHandle().showPastePopupWindow();
+ }
+ };
+
public void show() {
- updatePosition();
- getHandle().show();
+ show(DELAY_BEFORE_PASTE);
}
- void showWithPaste() {
- show();
- getHandle().showPastePopupWindow();
+ public void show(int delayBeforePaste) {
+ updatePosition();
+ hideDelayed();
+ getHandle().show();
+ removeCallbacks(mPastePopupShower);
+ if (canPaste()) {
+ final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
+ if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)
+ delayBeforePaste = 0;
+ postDelayed(mPastePopupShower, delayBeforePaste);
+ }
}
public void hide() {
@@ -8586,6 +8607,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mHandle.hide();
}
removeCallbacks(mHider);
+ removeCallbacks(mPastePopupShower);
}
private void hideDelayed() {
@@ -8618,7 +8640,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return;
}
- // updatePosition is called only when isShowing. Handle has been created at this point.
getHandle().positionAtCursor(offset, true);
}
@@ -8681,6 +8702,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mEndHandle.show();
hideInsertionPointCursorController();
+ hideDelayed();
}
public void hide() {
@@ -8735,6 +8757,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
updatePosition();
+ hideDelayed();
}
public void updatePosition() {
@@ -8755,13 +8778,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// The handles have been created since the controller isShowing().
mStartHandle.positionAtCursor(selectionStart, true);
mEndHandle.positionAtCursor(selectionEnd, true);
- hideDelayed();
}
public boolean onTouchEvent(MotionEvent event) {
// This is done even when the View does not have focus, so that long presses can start
// selection and tap can move cursor from this tap position.
- if (isTextEditable()) {
+ if (isTextEditable() || mTextIsSelectable) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
final int x = (int) event.getX();
@@ -9143,4 +9165,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private InputFilter[] mFilters = NO_FILTERS;
private static final Spanned EMPTY_SPANNED = new SpannedString("");
private static int DRAG_THUMBNAIL_MAX_TEXT_LENGTH = 20;
+ // System wide time for last cut or copy action.
+ private static long sLastCutOrCopyTime;
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index e61fac3..6cf1387 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.annotation.Widget;
import android.content.Context;
import android.os.Parcel;
@@ -23,9 +25,7 @@ import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.NumberPicker;
-
-import com.android.internal.R;
+import android.widget.NumberPicker.OnChangedListener;
import java.text.DateFormatSymbols;
import java.util.Calendar;
@@ -51,7 +51,7 @@ import java.util.Calendar;
*/
@Widget
public class TimePicker extends FrameLayout {
-
+
/**
* A no-op callback used in the constructor to avoid null checks
* later in the code.
@@ -70,10 +70,11 @@ public class TimePicker extends FrameLayout {
// ui components
private final NumberPicker mHourPicker;
private final NumberPicker mMinutePicker;
- private final Button mAmPmButton;
- private final String mAmText;
- private final String mPmText;
-
+ private final NumberPicker mAmPmPicker;
+ private final TextView mDivider;
+
+ private final String[] mAmPmStrings;
+
// callbacks
private OnTimeChangedListener mOnTimeChangedListener;
@@ -127,6 +128,10 @@ public class TimePicker extends FrameLayout {
}
});
+ // divider
+ mDivider = (TextView) findViewById(R.id.divider);
+ mDivider.setText(R.string.time_picker_separator);
+
// digits of minute
mMinutePicker = (NumberPicker) findViewById(R.id.minute);
mMinutePicker.setRange(0, 59);
@@ -140,50 +145,41 @@ public class TimePicker extends FrameLayout {
});
// am/pm
- mAmPmButton = (Button) findViewById(R.id.amPm);
-
- // now that the hour/minute picker objects have been initialized, set
- // the hour range properly based on the 12/24 hour display mode.
- configurePickerRanges();
-
- // initialize to current time
- Calendar cal = Calendar.getInstance();
- setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
-
- // by default we're not in 24 hour mode
- setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
- setCurrentMinute(cal.get(Calendar.MINUTE));
-
- mIsAm = (mCurrentHour < 12);
-
- /* Get the localized am/pm strings and use them in the spinner */
- DateFormatSymbols dfs = new DateFormatSymbols();
- String[] dfsAmPm = dfs.getAmPmStrings();
- mAmText = dfsAmPm[Calendar.AM];
- mPmText = dfsAmPm[Calendar.PM];
- mAmPmButton.setText(mIsAm ? mAmText : mPmText);
- mAmPmButton.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- requestFocus();
+ mAmPmPicker = (NumberPicker) findViewById(R.id.amPm);
+ mAmPmPicker.setOnChangeListener(new OnChangedListener() {
+ public void onChanged(NumberPicker picker, int oldVal, int newVal) {
+ picker.requestFocus();
if (mIsAm) {
-
// Currently AM switching to PM
if (mCurrentHour < 12) {
mCurrentHour += 12;
- }
+ }
} else {
-
// Currently PM switching to AM
if (mCurrentHour >= 12) {
mCurrentHour -= 12;
}
}
mIsAm = !mIsAm;
- mAmPmButton.setText(mIsAm ? mAmText : mPmText);
onTimeChanged();
}
});
-
+
+ /* Get the localized am/pm strings and use them in the spinner */
+ mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
+
+ // now that the hour/minute picker objects have been initialized, set
+ // the hour range properly based on the 12/24 hour display mode.
+ configurePickerRanges();
+
+ // initialize to current time
+ Calendar cal = Calendar.getInstance();
+ setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
+
+ // by default we're not in 24 hour mode
+ setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
+ setCurrentMinute(cal.get(Calendar.MINUTE));
+
if (!isEnabled()) {
setEnabled(false);
}
@@ -194,7 +190,7 @@ public class TimePicker extends FrameLayout {
super.setEnabled(enabled);
mMinutePicker.setEnabled(enabled);
mHourPicker.setEnabled(enabled);
- mAmPmButton.setEnabled(enabled);
+ mAmPmPicker.setEnabled(enabled);
}
/**
@@ -327,12 +323,15 @@ public class TimePicker extends FrameLayout {
int currentHour = mCurrentHour;
if (!mIs24HourView) {
// convert [0,23] ordinal to wall clock display
- if (currentHour > 12) currentHour -= 12;
- else if (currentHour == 0) currentHour = 12;
+ if (currentHour > 12) {
+ currentHour -= 12;
+ } else if (currentHour == 0) {
+ currentHour = 12;
+ }
}
mHourPicker.setCurrent(currentHour);
mIsAm = mCurrentHour < 12;
- mAmPmButton.setText(mIsAm ? mAmText : mPmText);
+ mAmPmPicker.setCurrent(mIsAm ? Calendar.AM : Calendar.PM);
onTimeChanged();
}
@@ -340,16 +339,19 @@ public class TimePicker extends FrameLayout {
if (mIs24HourView) {
mHourPicker.setRange(0, 23);
mHourPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
- mAmPmButton.setVisibility(View.GONE);
+ mAmPmPicker.setVisibility(View.GONE);
} else {
mHourPicker.setRange(1, 12);
mHourPicker.setFormatter(null);
- mAmPmButton.setVisibility(View.VISIBLE);
+ mAmPmPicker.setVisibility(View.VISIBLE);
+ mAmPmPicker.setRange(0, 1, mAmPmStrings);
}
}
private void onTimeChanged() {
- mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
+ }
}
/**
@@ -357,6 +359,6 @@ public class TimePicker extends FrameLayout {
*/
private void updateMinuteDisplay() {
mMinutePicker.setCurrent(mCurrentMinute);
- mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
+ onTimeChanged();
}
}
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 040dac3..fad9539 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -63,7 +63,8 @@ static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly)
}
if (!window->initBuffer(localOnly)) {
- jniThrowException(env, "java/lang/IllegalStateException", "Couldn't init cursor window");
+ jniThrowException(env, "java/lang/RuntimeException",
+ "Memory couldn't be allocated for 1MB CursorWindow object.");
delete window;
return;
}
@@ -82,11 +83,13 @@ static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
CursorWindow * window = new CursorWindow();
if (!window) {
- jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
+ jniThrowException(env, "java/lang/RuntimeException",
+ "CursorWindow of size 1MB couldn't be created. No memory?");
return;
}
if (!window->setMemory(memory)) {
- jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj");
+ jniThrowException(env, "java/lang/RuntimeException",
+ "Memory couldn't be initialized for 1MB CursorWindow object.");
delete window;
return;
}
@@ -131,8 +134,9 @@ LOG_WINDOW("Closing window %p", window);
static void throwExceptionWithRowCol(JNIEnv * env, jint row, jint column)
{
- char buf[100];
- snprintf(buf, sizeof(buf), "get field slot from row %d col %d failed", row, column);
+ char buf[200];
+ snprintf(buf, sizeof(buf), "Couldn't read row %d, col %d from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it",
+ row, column);
jniThrowException(env, "java/lang/IllegalStateException", buf);
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index c4056a4..2528db1 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1296,7 +1296,7 @@ static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
- return NULL;
+ return 0;
}
const ResTable& res(am->getResources());
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 9a85edc..206e320 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -32,6 +32,7 @@
#include <SkCanvas.h>
#include <SkBitmap.h>
#include <SkRegion.h>
+#include <SkPixelRef.h>
#include "jni.h"
#include <android_runtime/AndroidRuntime.h>
@@ -437,9 +438,15 @@ static void Surface_unfreezeDisplay(
}
}
-class ScreenshotBitmap : public SkBitmap {
+class ScreenshotPixelRef : public SkPixelRef {
public:
- ScreenshotBitmap() {
+ ScreenshotPixelRef(SkColorTable* ctable) {
+ fCTable = ctable;
+ ctable->safeRef();
+ setImmutable();
+ }
+ virtual ~ScreenshotPixelRef() {
+ SkSafeUnref(fCTable);
}
status_t update(int width, int height) {
@@ -450,40 +457,71 @@ public:
return res;
}
- void const* base = mScreenshot.getPixels();
- uint32_t w = mScreenshot.getWidth();
- uint32_t h = mScreenshot.getHeight();
- uint32_t s = mScreenshot.getStride();
- uint32_t f = mScreenshot.getFormat();
+ return NO_ERROR;
+ }
- ssize_t bpr = s * android::bytesPerPixel(f);
- setConfig(convertPixelFormat(f), w, h, bpr);
- if (f == PIXEL_FORMAT_RGBX_8888) {
- setIsOpaque(true);
- }
- if (w > 0 && h > 0) {
- setPixels((void*)base);
- } else {
- // be safe with an empty bitmap.
- setPixels(NULL);
- }
+ uint32_t getWidth() const {
+ return mScreenshot.getWidth();
+ }
- return NO_ERROR;
+ uint32_t getHeight() const {
+ return mScreenshot.getHeight();
+ }
+
+ uint32_t getStride() const {
+ return mScreenshot.getStride();
+ }
+
+ uint32_t getFormat() const {
+ return mScreenshot.getFormat();
+ }
+
+protected:
+ // overrides from SkPixelRef
+ virtual void* onLockPixels(SkColorTable** ct) {
+ *ct = fCTable;
+ return (void*)mScreenshot.getPixels();
+ }
+
+ virtual void onUnlockPixels() {
}
private:
ScreenshotClient mScreenshot;
+ SkColorTable* fCTable;
+
+ typedef SkPixelRef INHERITED;
};
static jobject Surface_screenshot(JNIEnv* env, jobject clazz, jint width, jint height)
{
- ScreenshotBitmap* bitmap = new ScreenshotBitmap();
-
- if (bitmap->update(width, height) != NO_ERROR) {
- delete bitmap;
+ ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
+ if (pixels->update(width, height) != NO_ERROR) {
+ delete pixels;
return 0;
}
+ uint32_t w = pixels->getWidth();
+ uint32_t h = pixels->getHeight();
+ uint32_t s = pixels->getStride();
+ uint32_t f = pixels->getFormat();
+ ssize_t bpr = s * android::bytesPerPixel(f);
+
+ SkBitmap* bitmap = new SkBitmap();
+ bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
+ if (f == PIXEL_FORMAT_RGBX_8888) {
+ bitmap->setIsOpaque(true);
+ }
+
+ if (w > 0 && h > 0) {
+ bitmap->setPixelRef(pixels)->unref();
+ bitmap->lockPixels();
+ } else {
+ // be safe with an empty bitmap.
+ delete pixels;
+ bitmap->setPixels(NULL);
+ }
+
return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
}
diff --git a/core/res/res/drawable/timepicker_down_btn.xml b/core/res/res/drawable/timepicker_down_btn.xml
index 61a252a..d4908cb 100644
--- a/core/res/res/drawable/timepicker_down_btn.xml
+++ b/core/res/res/drawable/timepicker_down_btn.xml
@@ -16,15 +16,28 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="false" android:state_enabled="true"
- android:state_focused="false" android:drawable="@drawable/timepicker_down_normal" />
- <item android:state_pressed="true" android:state_enabled="true"
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_down_normal" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
android:drawable="@drawable/timepicker_down_pressed" />
- <item android:state_pressed="false" android:state_enabled="true"
- android:state_focused="true" android:drawable="@drawable/timepicker_down_selected" />
- <item android:state_pressed="false" android:state_enabled="false"
- android:state_focused="false" android:drawable="@drawable/timepicker_down_disabled" />
- <item android:state_pressed="false" android:state_enabled="false"
- android:state_focused="true" android:drawable="@drawable/timepicker_down_disabled_focused" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_down_selected" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_down_disabled" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_down_disabled_focused" />
</selector>
diff --git a/core/res/res/drawable/timepicker_down_btn_holo_dark.xml b/core/res/res/drawable/timepicker_down_btn_holo_dark.xml
new file mode 100644
index 0000000..b4b824d
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_btn_holo_dark.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_down_normal_holo_dark" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
+ android:drawable="@drawable/timepicker_down_pressed_holo_dark" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_down_focused_holo_dark" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_down_disabled_holo_dark" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_down_disabled_focused_holo_dark" />
+
+</selector>
diff --git a/core/res/res/drawable/timepicker_down_btn_holo_light.xml b/core/res/res/drawable/timepicker_down_btn_holo_light.xml
new file mode 100644
index 0000000..7ae5e53
--- /dev/null
+++ b/core/res/res/drawable/timepicker_down_btn_holo_light.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_down_normal_holo_light" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
+ android:drawable="@drawable/timepicker_down_pressed_holo_light" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_down_focused_holo_light" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_down_disabled_holo_light" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_down_disabled_focused_holo_light" />
+
+</selector>
diff --git a/core/res/res/drawable/timepicker_input.xml b/core/res/res/drawable/timepicker_input.xml
index b811d4e..1768673 100644
--- a/core/res/res/drawable/timepicker_input.xml
+++ b/core/res/res/drawable/timepicker_input.xml
@@ -16,15 +16,28 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="false" android:state_enabled="true"
- android:state_focused="false" android:drawable="@drawable/timepicker_input_normal" />
- <item android:state_pressed="true" android:state_enabled="true"
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_input_normal" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
android:drawable="@drawable/timepicker_input_pressed" />
- <item android:state_pressed="false" android:state_enabled="true"
- android:state_focused="true" android:drawable="@drawable/timepicker_input_selected" />
- <item android:state_pressed="false" android:state_enabled="false"
- android:state_focused="false" android:drawable="@drawable/timepicker_input_disabled" />
- <item android:state_pressed="false" android:state_enabled="false"
- android:state_focused="true" android:drawable="@drawable/timepicker_input_normal" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_input_selected" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_input_disabled" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_input_normal" />
</selector>
diff --git a/core/res/res/drawable/timepicker_up_btn.xml b/core/res/res/drawable/timepicker_up_btn.xml
index 5428aee..fbaed08 100644
--- a/core/res/res/drawable/timepicker_up_btn.xml
+++ b/core/res/res/drawable/timepicker_up_btn.xml
@@ -16,15 +16,28 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="false" android:state_enabled="true"
- android:state_focused="false" android:drawable="@drawable/timepicker_up_normal" />
- <item android:state_pressed="true" android:state_enabled="true"
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_up_normal" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
android:drawable="@drawable/timepicker_up_pressed" />
- <item android:state_pressed="false" android:state_enabled="true"
- android:state_focused="true" android:drawable="@drawable/timepicker_up_selected" />
- <item android:state_pressed="false" android:state_enabled="false"
- android:state_focused="false" android:drawable="@drawable/timepicker_up_disabled" />
- <item android:state_pressed="false" android:state_enabled="false"
- android:state_focused="true" android:drawable="@drawable/timepicker_up_disabled_focused" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_up_selected" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_up_disabled" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_up_disabled_focused" />
</selector>
diff --git a/core/res/res/drawable/timepicker_up_btn_holo_dark.xml b/core/res/res/drawable/timepicker_up_btn_holo_dark.xml
new file mode 100644
index 0000000..af5f6eb
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_btn_holo_dark.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_up_normal_holo_dark" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
+ android:drawable="@drawable/timepicker_up_pressed_holo_dark" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_up_focused_holo_dark" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_up_disabled_holo_dark" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_up_disabled_focused_holo_dark" />
+
+</selector>
diff --git a/core/res/res/drawable/timepicker_up_btn_holo_light.xml b/core/res/res/drawable/timepicker_up_btn_holo_light.xml
new file mode 100644
index 0000000..6025d3a
--- /dev/null
+++ b/core/res/res/drawable/timepicker_up_btn_holo_light.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_up_normal_holo_light" />
+
+ <item android:state_pressed="true"
+ android:state_enabled="true"
+ android:drawable="@drawable/timepicker_up_pressed_holo_light" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="true"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_up_focused_holo_light" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="false"
+ android:drawable="@drawable/timepicker_up_focused_holo_light" />
+
+ <item android:state_pressed="false"
+ android:state_enabled="false"
+ android:state_focused="true"
+ android:drawable="@drawable/timepicker_up_disabled_focused_holo_light" />
+
+</selector>
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
index 9241708..44c99cf 100644
--- a/core/res/res/layout/number_picker.xml
+++ b/core/res/res/layout/number_picker.xml
@@ -19,24 +19,19 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <NumberPickerButton android:id="@+id/increment"
- android:layout_width="match_parent"
+ <ImageButton android:id="@+id/increment"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:background="@drawable/timepicker_up_btn" />
+ style="?android:attr/numberPickerUpButtonStyle" />
<EditText android:id="@+id/timepicker_input"
- android:layout_width="match_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:gravity="center"
- android:singleLine="true"
- style="?android:attr/textAppearanceLargeInverse"
- android:textColor="@android:color/primary_text_light"
- android:textSize="30sp"
- android:background="@drawable/timepicker_input" />
+ style="?android:attr/numberPickerInputTextStyle" />
- <NumberPickerButton android:id="@+id/decrement"
- android:layout_width="match_parent"
+ <ImageButton android:id="@+id/decrement"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:background="@drawable/timepicker_down_btn" />
+ style="?android:attr/numberPickerDownButtonStyle" />
</merge>
diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml
index 6124ea8..fa28842 100644
--- a/core/res/res/layout/time_picker.xml
+++ b/core/res/res/layout/time_picker.xml
@@ -28,32 +28,46 @@
<!-- hour -->
<NumberPicker
android:id="@+id/hour"
- android:layout_width="70dip"
+ android:layout_width="48dip"
android:layout_height="wrap_content"
+ android:layout_marginRight="20dip"
+ android:layout_marginTop="35dip"
+ android:layout_marginBottom="35dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
-
+
+ <!-- divider -->
+ <TextView
+ android:id="@+id/divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ />
+
<!-- minute -->
<NumberPicker
android:id="@+id/minute"
- android:layout_width="70dip"
+ android:layout_width="48dip"
android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="22dip"
+ android:layout_marginTop="35dip"
+ android:layout_marginBottom="35dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
-
+
<!-- AM / PM -->
- <Button
+ <NumberPicker
android:id="@+id/amPm"
- android:layout_width="wrap_content"
+ android:layout_width="48dip"
android:layout_height="wrap_content"
- android:layout_marginTop="43dip"
- android:layout_marginLeft="5dip"
- android:paddingLeft="20dip"
- android:paddingRight="20dip"
- style="?android:attr/textAppearanceLargeInverse"
- android:textColor="@android:color/primary_text_light_nodisable"
+ android:layout_marginLeft="22dip"
+ android:layout_marginTop="35dip"
+ android:layout_marginBottom="35dip"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
/>
+
</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a8099e3..5d4fd4e 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -503,6 +503,15 @@
<attr name="listPopupWindowStyle" format="reference" />
<!-- Default PopupMenu style. -->
<attr name="popupMenuStyle" format="reference" />
+ <!-- NumberPicker up button style -->
+ <attr name="numberPickerUpButtonStyle" format="reference" />
+ <!-- NumberPicker down button style -->
+ <attr name="numberPickerDownButtonStyle" format="reference" />
+ <!-- NumberPicker input text style -->
+ <attr name="numberPickerInputTextStyle" format="reference" />
+
+ <!-- NumberPicker the fading edge length of the selector wheel -->
+ <attr name="numberPickerStyle" format="reference" />
<!-- =================== -->
<!-- Action bar styles -->
@@ -2856,6 +2865,12 @@
<attr name="minorWeightMax" format="float" />
</declare-styleable>
+ <!-- @hide -->
+ <declare-styleable name="NumberPicker">
+ <attr name="orientation" />
+ <attr name="solidColor" format="color|reference" />
+ </declare-styleable>
+
<!-- ========================= -->
<!-- Drawable class attributes -->
<!-- ========================= -->
diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml
index d6d5dbb..bdcab39 100644
--- a/core/res/res/values/donottranslate.xml
+++ b/core/res/res/values/donottranslate.xml
@@ -24,4 +24,6 @@
<bool name="lockscreen_isPortrait">true</bool>
<!-- @hide DO NOT TRANSLATE. Control aspect ratio of lock pattern -->
<string name="lock_pattern_view_aspect">square</string>
+ <!-- @hide DO NOT TRANSLATE. Separator between the hour and minute elements in a TimePicker widget -->
+ <string name="time_picker_separator">:</string>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0c3361d..eafa9b6 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2291,6 +2291,10 @@
<!-- See SMS_DIALOG. This is a button choice to disallow sending the SMSes.. -->
<string name="sms_control_no">Cancel</string>
+ <!-- Date/Time picker dialogs strings -->
+
+ <!-- The title of the time picker dialog. [CHAR LIMIT=NONE] -->
+ <string name="time_picker_dialog_title">Set time</string>
<!-- Name of the button in the date/time picker to accept the date/time change -->
<string name="date_time_set">Set</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 2754e73..60150a1 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -460,6 +460,28 @@
<item name="android:background">@android:drawable/btn_default</item>
</style>
+ <style name="Widget.NumberPicker">
+ <item name="android:orientation">vertical</item>
+ <item name="android:fadingEdge">vertical</item>
+ <item name="android:fadingEdgeLength">50dip</item>
+ <item name="android:solidColor">?android:attr/colorBackground</item>
+ </style>
+
+ <style name="Widget.ImageButton.NumberPickerUpButton">
+ <item name="android:background">@android:drawable/timepicker_up_btn</item>
+ </style>
+
+ <style name="Widget.ImageButton.NumberPickerDownButton">
+ <item name="android:background">@android:drawable/timepicker_down_btn</item>
+ </style>
+
+ <style name="Widget.EditText.NumberPickerInputText">
+ <item name="android:textAppearance">@style/TextAppearance.Large.Inverse.NumberPickerInputText</item>
+ <item name="android:gravity">center</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:background">@drawable/timepicker_input</item>
+ </style>
+
<style name="Widget.AutoCompleteTextView" parent="Widget.EditText">
<item name="android:completionHintView">@android:layout/simple_dropdown_hint</item>
<item name="android:completionThreshold">2</item>
@@ -761,7 +783,7 @@
<style name="TextAppearance.Widget.TextView.SpinnerItem">
<item name="android:textColor">@android:color/primary_text_light_disable_only</item>
</style>
-
+
<!-- @hide -->
<style name="TextAppearance.SlidingTabNormal"
parent="@android:attr/textAppearanceMedium">
@@ -804,6 +826,11 @@
<item name="android:textStyle">bold</item>
</style>
+ <style name="TextAppearance.Large.Inverse.NumberPickerInputText">
+ <item name="android:textColor">@android:color/primary_text_light</item>
+ <item name="android:textSize">30sp</item>
+ </style>
+
<!-- Preference Styles -->
<style name="Preference">
@@ -1353,6 +1380,27 @@
<style name="Widget.Holo.ImageButton" parent="Widget.ImageButton">
</style>
+ <style name="Widget.Holo.ImageButton.NumberPickerUpButton">
+ <item name="android:background">@null</item>
+ <item name="android:paddingBottom">26sp</item>
+ <item name="android:src">@android:drawable/timepicker_up_btn_holo_dark</item>
+ </style>
+
+ <style name="Widget.Holo.ImageButton.NumberPickerDownButton">
+ <item name="android:background">@null</item>
+ <item name="android:paddingTop">26sp</item>
+ <item name="android:src">@android:drawable/timepicker_down_btn_holo_dark</item>
+ </style>
+
+ <style name="Widget.Holo.EditText.NumberPickerInputText">
+ <item name="android:paddingTop">13sp</item>
+ <item name="android:paddingBottom">13sp</item>
+ <item name="android:gravity">center</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:background">@null</item>
+ </style>
+
<style name="Widget.Holo.ImageWell" parent="Widget.ImageWell">
</style>
@@ -1655,6 +1703,17 @@
<style name="Widget.Holo.Light.ImageButton" parent="Widget.ImageButton">
</style>
+ <style name="Widget.Holo.Light.ImageButton.NumberPickerUpButton" parent="Widget.Holo.Light.ImageButton.NumberPickerUpButton">
+ <item name="android:background">@android:drawable/timepicker_up_btn_holo_light</item>
+ </style>
+
+ <style name="Widget.Holo.Light.ImageButton.NumberPickerDownButton" parent="Widget.Holo.ImageButton.NumberPickerDownButton">
+ <item name="android:background">@android:drawable/timepicker_down_btn_holo_light</item>
+ </style>
+
+ <style name="Widget.Holo.Light.EditText.NumberPickerInputText" parent="Widget.Holo.EditText.NumberPickerInputText">
+ </style>
+
<style name="Widget.Holo.Light.ImageWell" parent="Widget.ImageWell">
</style>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 0eec0df..97df48a 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -267,8 +267,16 @@
<item name="searchViewGoIcon">@android:drawable/ic_go</item>
<item name="searchViewVoiceIcon">@android:drawable/ic_voice_search</item>
+
<!-- PreferenceFrameLayout attributes -->
<item name="preferenceFrameLayoutStyle">@android:style/Widget.PreferenceFrameLayout</item>
+
+ <!-- NumberPicker styles-->
+ <item name="numberPickerUpButtonStyle">@style/Widget.ImageButton.NumberPickerUpButton</item>
+ <item name="numberPickerDownButtonStyle">@style/Widget.ImageButton.NumberPickerDownButton</item>
+ <item name="numberPickerInputTextStyle">@style/Widget.EditText.NumberPickerInputText</item>
+ <item name="numberPickerStyle">@style/Widget.NumberPicker</item>
+
</style>
<!-- Variant of the default (dark) theme with no title bar -->
@@ -881,6 +889,12 @@
<!-- PreferenceFrameLayout attributes -->
<item name="preferenceFrameLayoutStyle">@android:style/Widget.Holo.PreferenceFrameLayout</item>
+
+ <!-- NumberPicker styles-->
+ <item name="numberPickerUpButtonStyle">@style/Widget.Holo.ImageButton.NumberPickerUpButton</item>
+ <item name="numberPickerDownButtonStyle">@style/Widget.Holo.ImageButton.NumberPickerDownButton</item>
+ <item name="numberPickerInputTextStyle">@style/Widget.Holo.EditText.NumberPickerInputText</item>
+
</style>
<!-- New Honeycomb holographic theme. Light version. The widgets in the
@@ -1117,6 +1131,12 @@
<!-- SearchView attributes -->
<item name="searchDropdownBackground">@android:drawable/search_dropdown_light</item>
+
+ <!-- NumberPicker attributes and styles-->
+ <item name="numberPickerUpButtonStyle">@style/Widget.Holo.Light.ImageButton.NumberPickerUpButton</item>
+ <item name="numberPickerDownButtonStyle">@style/Widget.Holo.Light.ImageButton.NumberPickerDownButton</item>
+ <item name="numberPickerInputTextStyle">@style/Widget.Holo.Light.EditText.NumberPickerInputText</item>
+
</style>
<!-- Development legacy name; if you're using this, switch. -->
diff --git a/data/keyboards/Apple_Wireless_Keyboard.kl b/data/keyboards/Apple_Wireless_Keyboard.kl
new file mode 100644
index 0000000..9262a03
--- /dev/null
+++ b/data/keyboards/Apple_Wireless_Keyboard.kl
@@ -0,0 +1,119 @@
+# Copyright (C) 2010 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.
+
+#
+# Apple Wireless Keyboard
+#
+# Notes:
+# - Keys such as PAGE_UP and FORWARD_DEL are produced using the
+# function key.
+# - Special function keys for brightness control, etc. are not
+# implemented here.
+
+key 1 ESCAPE
+key 2 1
+key 3 2
+key 4 3
+key 5 4
+key 6 5
+key 7 6
+key 8 7
+key 9 8
+key 10 9
+key 11 0
+key 12 MINUS
+key 13 EQUALS
+key 14 DEL
+key 15 TAB
+key 16 Q
+key 17 W
+key 18 E
+key 19 R
+key 20 T
+key 21 Y
+key 22 U
+key 23 I
+key 24 O
+key 25 P
+key 26 LEFT_BRACKET
+key 27 RIGHT_BRACKET
+key 28 ENTER
+key 29 CTRL_LEFT
+key 30 A
+key 31 S
+key 32 D
+key 33 F
+key 34 G
+key 35 H
+key 36 J
+key 37 K
+key 38 L
+key 39 SEMICOLON
+key 40 APOSTROPHE
+key 41 GRAVE
+key 42 SHIFT_LEFT
+key 43 BACKSLASH
+key 44 Z
+key 45 X
+key 46 C
+key 47 V
+key 48 B
+key 49 N
+key 50 M
+key 51 COMMA
+key 52 PERIOD
+key 53 SLASH
+key 54 SHIFT_RIGHT
+key 56 ALT_LEFT
+key 57 SPACE
+key 58 CAPS_LOCK
+key 59 F1
+key 60 F2
+key 61 F3
+key 62 F4
+key 63 F5
+key 64 F6
+key 65 F7
+key 66 F8
+key 67 F9
+key 68 F10
+key 87 F11
+key 88 F12
+key 100 ALT_RIGHT
+key 102 MOVE_HOME
+key 103 DPAD_UP
+key 104 PAGE_UP
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 107 MOVE_END
+key 108 DPAD_DOWN
+key 109 PAGE_DOWN
+key 110 NUMPAD_ENTER
+key 111 FORWARD_DEL
+key 113 VALUME_MUTE
+key 114 VOLUME_DOWN
+key 115 VOLUME_UP
+# key 120 switch applications
+key 125 META_LEFT
+key 126 META_RIGHT
+key 161 MEDIA_EJECT
+key 163 MEDIA_NEXT
+key 164 MEDIA_PLAY_PAUSE
+key 165 MEDIA_PREVIOUS
+# key 204 show gadgets
+# key 224 reduce brightness
+# key 225 increase brightness
+# key 229 blank special function on F5
+# key 230 blank special function on F6
+key 464 FUNCTION
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index e98000d..1ee121e 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -175,7 +175,7 @@ key 150 EXPLORER
# key 153 "KEY_DIRECTION"
# key 154 "KEY_CYCLEWINDOWS"
key 155 ENVELOPE
-# key 156 "KEY_BOOKMARKS"
+key 156 BOOKMARK
# key 157 "KEY_COMPUTER"
key 158 BACK WAKE_DROPPED
key 159 FORWARD
@@ -289,11 +289,11 @@ key 318 BUTTON_THUMBR
# key 359 "KEY_TIME"
# key 360 "KEY_VENDOR"
# key 361 "KEY_ARCHIVE"
-# key 362 "KEY_PROGRAM"
+key 362 GUIDE
# key 363 "KEY_CHANNEL"
# key 364 "KEY_FAVORITES"
# key 365 "KEY_EPG"
-# key 366 "KEY_PVR"
+key 366 DVR
# key 367 "KEY_MHP"
# key 368 "KEY_LANGUAGE"
# key 369 "KEY_TITLE"
@@ -304,7 +304,7 @@ key 318 BUTTON_THUMBR
# key 374 "KEY_KEYBOARD"
# key 375 "KEY_SCREEN"
# key 376 "KEY_PC"
-# key 377 "KEY_TV"
+key 377 TV
# key 378 "KEY_TV2"
# key 379 "KEY_VCR"
# key 380 "KEY_VCR2"
@@ -329,8 +329,8 @@ key 318 BUTTON_THUMBR
# key 399 "KEY_GREEN"
# key 400 "KEY_YELLOW"
# key 401 "KEY_BLUE"
-# key 402 "KEY_CHANNELUP"
-# key 403 "KEY_CHANNELDOWN"
+key 402 CHANNEL_UP
+key 403 CHANNEL_DOWN
# key 404 "KEY_FIRST"
# key 405 "KEY_LAST"
# key 406 "KEY_AB"
diff --git a/data/keyboards/Logitech_USB_Receiver.kl b/data/keyboards/Logitech_USB_Receiver.kl
new file mode 100644
index 0000000..23a8f54
--- /dev/null
+++ b/data/keyboards/Logitech_USB_Receiver.kl
@@ -0,0 +1,133 @@
+# Copyright (C) 2010 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.
+
+#
+# Logitech Revue keyboard
+#
+# Notes:
+# - The GRAVE key is emulated by the keyboard.
+# ALT + LEFT_BRACKET produces GRAVE.
+# ALT + RIGHT_BRACKET produces SHIFT + GRAVE.
+# - FORWARD_DEL is produced by fn + BACKSPACE
+# - PAGE_UP / PAGE_DOWN is produced by fn + CHANNEL_UP / CHANNEL_DOWN
+# - The AVR / STB / TV power and input buttons seem to be non-functional
+# as well as several of the other fn buttons and the PIP button?
+
+key 1 ESCAPE
+key 2 1
+key 3 2
+key 4 3
+key 5 4
+key 6 5
+key 7 6
+key 8 7
+key 9 8
+key 10 9
+key 11 0
+key 12 MINUS
+key 13 EQUALS
+key 14 DEL
+key 15 TAB
+key 16 Q
+key 17 W
+key 18 E
+key 19 R
+key 20 T
+key 21 Y
+key 22 U
+key 23 I
+key 24 O
+key 25 P
+key 26 LEFT_BRACKET
+key 27 RIGHT_BRACKET
+key 28 ENTER
+key 29 CTRL_LEFT
+key 30 A
+key 31 S
+key 32 D
+key 33 F
+key 34 G
+key 35 H
+key 36 J
+key 37 K
+key 38 L
+key 39 SEMICOLON
+key 40 APOSTROPHE
+key 41 GRAVE
+key 42 SHIFT_LEFT
+key 43 BACKSLASH
+key 44 Z
+key 45 X
+key 46 C
+key 47 V
+key 48 B
+key 49 N
+key 50 M
+key 51 COMMA
+key 52 PERIOD
+key 53 SLASH
+key 54 SHIFT_RIGHT
+key 56 ALT_RIGHT
+key 57 SPACE
+key 58 CAPS_LOCK
+key 59 F1
+key 60 F2
+key 61 F3
+key 62 F4
+key 63 F5
+key 64 F6
+key 65 F7
+key 66 F8
+key 67 F9
+key 68 F10
+key 87 F11
+key 88 F12
+key 96 DPAD_CENTER
+key 97 CTRL_RIGHT
+key 102 MOVE_HOME
+key 103 DPAD_UP
+key 104 PAGE_UP
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 107 MOVE_END
+key 108 DPAD_DOWN
+key 109 PAGE_DOWN
+key 110 NUMPAD_ENTER
+key 111 FORWARD_DEL
+key 113 VALUME_MUTE
+key 114 VOLUME_DOWN
+key 115 VOLUME_UP
+key 119 MEDIA_PAUSE
+key 125 SEARCH
+key 127 MENU
+key 156 BOOKMARK
+key 158 BACK
+key 163 MEDIA_NEXT
+key 165 MEDIA_PREVIOUS
+key 166 MEDIA_STOP
+key 167 MEDIA_RECORD
+key 168 MEDIA_REWIND
+key 172 HOME
+key 207 MEDIA_PLAY
+key 208 MEDIA_FAST_FORWARD
+# key 288 left mouse button
+# key 289 right mouse button (fn + button)
+key 362 GUIDE
+key 366 DVR
+key 377 TV
+key 402 CHANNEL_UP
+key 403 CHANNEL_DOWN
+key 418 ZOOM_IN
+key 419 ZOOM_OUT
+
diff --git a/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kcm b/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kcm
deleted file mode 100644
index 73a04d0..0000000
--- a/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kcm
+++ /dev/null
@@ -1,370 +0,0 @@
-# Copyright (C) 2010 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.
-
-#
-# Generic key character map for full alphabetic US English PC style external keyboards.
-#
-# This file is intentionally very generic and is intended to support a broad rang of keyboards.
-# Do not edit the generic key character map to support a specific keyboard; instead, create
-# a new key character map file with the required keyboard configuration.
-#
-
-type FULL
-
-key A {
- label: 'A'
- base: 'a'
- shift, capslock: 'A'
- ctrl, alt, meta: none
-}
-
-key B {
- label: 'B'
- base: 'b'
- shift, capslock: 'B'
- ctrl, alt, meta: none
-}
-
-key C {
- label: 'C'
- base: 'c'
- shift, capslock: 'C'
- ctrl, alt, meta: none
-}
-
-key D {
- label: 'D'
- base: 'd'
- shift, capslock: 'D'
- ctrl, alt, meta: none
-}
-
-key E {
- label: 'E'
- base: 'e'
- shift, capslock: 'E'
- ctrl, alt, meta: none
-}
-
-key F {
- label: 'F'
- base: 'f'
- shift, capslock: 'F'
- ctrl, alt, meta: none
-}
-
-key G {
- label: 'G'
- base: 'g'
- shift, capslock: 'G'
- ctrl, alt, meta: none
-}
-
-key H {
- label: 'H'
- base: 'h'
- shift, capslock: 'H'
- ctrl, alt, meta: none
-}
-
-key I {
- label: 'I'
- base: 'i'
- shift, capslock: 'I'
- ctrl, alt, meta: none
-}
-
-key J {
- label: 'J'
- base: 'j'
- shift, capslock: 'J'
- ctrl, alt, meta: none
-}
-
-key K {
- label: 'K'
- base: 'k'
- shift, capslock: 'K'
- ctrl, alt, meta: none
-}
-
-key L {
- label: 'L'
- base: 'l'
- shift, capslock: 'L'
- ctrl, alt, meta: none
-}
-
-key M {
- label: 'M'
- base: 'm'
- shift, capslock: 'M'
- ctrl, alt, meta: none
-}
-
-key N {
- label: 'N'
- base: 'n'
- shift, capslock: 'N'
- ctrl, alt, meta: none
-}
-
-key O {
- label: 'O'
- base: 'o'
- shift, capslock: 'O'
- ctrl, alt, meta: none
-}
-
-key P {
- label: 'P'
- base: 'p'
- shift, capslock: 'P'
- ctrl, alt, meta: none
-}
-
-key Q {
- label: 'Q'
- base: 'q'
- shift, capslock: 'Q'
- ctrl, alt, meta: none
-}
-
-key R {
- label: 'R'
- base: 'r'
- shift, capslock: 'R'
- ctrl, alt, meta: none
-}
-
-key S {
- label: 'S'
- base: 's'
- shift, capslock: 'S'
- ctrl, alt, meta: none
-}
-
-key T {
- label: 'T'
- base: 't'
- shift, capslock: 'T'
- ctrl, alt, meta: none
-}
-
-key U {
- label: 'U'
- base: 'u'
- shift, capslock: 'U'
- ctrl, alt, meta: none
-}
-
-key V {
- label: 'V'
- base: 'v'
- shift, capslock: 'V'
- ctrl, alt, meta: none
-}
-
-key W {
- label: 'W'
- base: 'w'
- shift, capslock: 'W'
- ctrl, alt, meta: none
-}
-
-key X {
- label: 'X'
- base: 'x'
- shift, capslock: 'X'
- ctrl, alt, meta: none
-}
-
-key Y {
- label: 'Y'
- base: 'y'
- shift, capslock: 'Y'
- ctrl, alt, meta: none
-}
-
-key Z {
- label: 'Z'
- base: 'z'
- shift, capslock: 'Z'
- ctrl, alt, meta: none
-}
-
-key 0 {
- label, number: '0'
- base: '0'
- shift: ')'
- ctrl, alt, meta: none
-}
-
-key 1 {
- label, number: '1'
- base: '1'
- shift: '!'
- ctrl, alt, meta: none
-}
-
-key 2 {
- label, number: '2'
- base: '2'
- shift: '@'
- ctrl, alt, meta: none
-}
-
-key 3 {
- label, number: '3'
- base: '3'
- shift: '#'
- ctrl, alt, meta: none
-}
-
-key 4 {
- label, number: '4'
- base: '4'
- shift: '$'
- ctrl, alt, meta: none
-}
-
-key 5 {
- label, number: '5'
- base: '5'
- shift: '%'
- ctrl, alt, meta: none
-}
-
-key 6 {
- label, number: '6'
- base: '6'
- shift: '^'
- ctrl, alt, meta: none
-}
-
-key 7 {
- label, number: '7'
- base: '7'
- shift: '&'
- ctrl, alt, meta: none
-}
-
-key 8 {
- label, number: '8'
- base: '8'
- shift: '*'
- ctrl, alt, meta: none
-}
-
-key 9 {
- label, number: '9'
- base: '9'
- shift: '('
- ctrl, alt, meta: none
-}
-
-key SPACE {
- label: ' '
- base: ' '
- ctrl, alt, meta: none
-}
-
-key ENTER {
- label: '\n'
- base: '\n'
- ctrl, alt, meta: none
-}
-
-key TAB {
- label: '\t'
- base: '\t'
- ctrl, alt, meta: none
-}
-
-key COMMA {
- label, number: ','
- base: ','
- shift: '<'
- ctrl, alt, meta: none
-}
-
-key PERIOD {
- label, number: '.'
- base: '.'
- shift: '>'
- ctrl, alt, meta: none
-}
-
-key SLASH {
- label, number: '/'
- base: '/'
- shift: '?'
- ctrl, alt, meta: none
-}
-
-key GRAVE {
- label, number: '`'
- base: '`'
- shift: '~'
- ctrl, alt, meta: none
-}
-
-key MINUS {
- label, number: '-'
- base: '-'
- shift: '_'
- ctrl, alt, meta: none
-}
-
-key EQUALS {
- label, number: '='
- base: '='
- shift: '+'
- ctrl, alt, meta: none
-}
-
-key LEFT_BRACKET {
- label, number: '['
- base: '['
- shift: '{'
- ctrl, alt, meta: none
-}
-
-key RIGHT_BRACKET {
- label, number: ']'
- base: ']'
- shift: '}'
- ctrl, alt, meta: none
-}
-
-key BACKSLASH {
- label, number: '\\'
- base: '\\'
- shift: '|'
- ctrl, alt, meta: none
-}
-
-key SEMICOLON {
- label, number: ';'
- base: ';'
- shift: ':'
- ctrl, alt, meta: none
-}
-
-key APOSTROPHE {
- label, number: '\''
- base: '\''
- shift: '"'
- ctrl, alt, meta: none
-}
diff --git a/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kl b/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kl
index eab78a0..87b3c32 100644
--- a/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kl
+++ b/data/keyboards/Motorola_Bluetooth_Wireless_Keyboard.kl
@@ -66,6 +66,7 @@ key 51 COMMA
key 52 PERIOD
key 53 SLASH
key 54 SHIFT_RIGHT
+key 56 ALT_LEFT
key 57 SPACE
key 58 CAPS_LOCK
key 59 F1
@@ -77,6 +78,9 @@ key 64 F6
key 65 F7
key 66 F8
key 67 F9
+key 68 F10
+key 87 F11
+key 88 F12
key 97 CTRL_RIGHT
key 102 HOME
key 103 DPAD_UP
@@ -84,7 +88,6 @@ key 105 DPAD_LEFT
key 106 DPAD_RIGHT
key 107 MOVE_END
key 108 DPAD_DOWN
-key 110 INSERT
key 111 FORWARD_DEL
key 113 VOLUME_MUTE
key 114 VOLUME_DOWN
@@ -95,3 +98,4 @@ key 163 MEDIA_NEXT
key 164 MEDIA_PLAY_PAUSE
key 165 MEDIA_PREVIOUS
key 166 MEDIA_STOP
+# key 226 tbd reserved key
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
index 3f05edb..5c2a75d 100644
--- a/data/keyboards/common.mk
+++ b/data/keyboards/common.mk
@@ -16,15 +16,16 @@
# Used by Android.mk and keyboards.mk.
keylayouts := \
+ Apple_Wireless_Keyboard.kl \
AVRCP.kl \
Generic.kl \
+ Logitech_USB_Receiver.kl \
Motorola_Bluetooth_Wireless_Keyboard.kl \
qwerty.kl \
qwerty2.kl
keycharmaps := \
Generic.kcm \
- Virtual.kcm \
- Motorola_Bluetooth_Wireless_Keyboard.kcm \
qwerty.kcm \
- qwerty2.kcm
+ qwerty2.kcm \
+ Virtual.kcm
diff --git a/docs/html/images/preview_hc/actionbar.png b/docs/html/images/preview_hc/actionbar.png
new file mode 100644
index 0000000..31df2b2
--- /dev/null
+++ b/docs/html/images/preview_hc/actionbar.png
Binary files differ
diff --git a/docs/html/images/preview_hc/fragments_layout.png b/docs/html/images/preview_hc/fragments_layout.png
new file mode 100644
index 0000000..91c8929
--- /dev/null
+++ b/docs/html/images/preview_hc/fragments_layout.png
Binary files differ
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index 8b77303..4153951 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -19,6 +19,7 @@ sdk.linux_checksum=TODO
@jd:body
+<div class="non-preview">
<p>Here's an overview of the steps you must follow to set up the Android SDK:</p>
<ol>
@@ -32,3 +33,4 @@ installer for help with the initial setup.)</li>
<p>To get started, download the appropriate package from the table above, then read the guide to <a
href="installing.html">Installing the SDK</a>.</p>
+</div>
diff --git a/docs/html/sdk/preview/features.jd b/docs/html/sdk/preview/features.jd
index 81b4ff6..55d0f8d 100644
--- a/docs/html/sdk/preview/features.jd
+++ b/docs/html/sdk/preview/features.jd
@@ -1,4 +1,185 @@
-sdk.redirect=true
-
+page.title=Introduction to Honeycomb
@jd:body
+<p>Welcome to the Honeycomb preview SDK. Honeycomb is the next major release of the Android
+platform and is optimized for tablet devices. This document provides an introduction to the new
+platform features and APIs available in Honeycomb.</p>
+
+
+<h2>Fragments</h2>
+
+<div class="figure" style="width:400px">
+ <img src="{@docRoot}images/preview_hc/fragments_layout.png" alt="" />
+ <p class="img-caption"><strong>Fragment Layout.</strong> An activity with two
+fragments: one with a list view, on the left, and one that displays selected content on the
+right. This demo is available in the samples package.</p>
+</div>
+
+
+<p>A new framework component that allows you to separate distinct elements of an activity into
+self-contained modules that define their own UI and lifecycle&mdash;defining what may be
+considered "sub-activities".</p>
+<ul>
+ <li>Multiple fragments can be combined in a single activity to build a multi-pane UI in which
+each pane manages its own lifecycle and user inputs</li>
+ <li>Fragments are self-contained and can be reused in multiple activities</li>
+ <li>Fragments can be added, removed, replaced and animated inside the activity</li>
+ <li>Fragment can be added to a back stack managed by the activity, preserving the state of
+fragments as they are changed and allowing the user to navigate backward through the different
+states</li>
+ <li>By <a
+href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">providing
+alternative resources</a>, you can mix and match fragments, based
+on the screen size and orientation</li>
+ <li>Fragments have direct access to their container activity and can contribute items to the
+activity's Options Menu</li>
+</ul>
+
+<p>For more information, see the <a
+href="{@docRoot}guide/topics/fragments/index.html">Fragments</a> developer guide.</p>
+
+
+<h2>Action Bar</h2>
+
+<p>A replacement for the traditional title bar, which provides users quick access to global
+actions and different navigation modes.</p>
+<ul>
+ <li>Provides quick access to items from the Options Menu ("action items") and interactive
+widgets ("action views")</li>
+ <li>Includes the application logo in the left corner, which can perform actions when tapped
+and can be replaced with a custom logo</li>
+ <li>Provides breadcumbs for navigating backward through fragments</li>
+ <li>Offers built in navigation modes, including tabs and a drop-down list</li>
+ <li>Can be customized with themes and custom backgrounds</li>
+ <li>And more</li>
+</ul>
+
+<img src="{@docRoot}images/preview_hc/actionbar.png" alt="" />
+<p class="img-caption"><strong>Action Bar.</strong> An action bar with a custom logo,
+tabs, and Options Menu. This demo is available in the samples package.</p>
+
+<p>For more information, see the <a
+href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a> developer guide.</p>
+
+
+<h2>System Clipboard</h2>
+
+<p>Applications can copy and paste data (beyond mere text) to and from the system-wide
+clipboard.</p>
+
+<ul>
+ <li>Clipped data can be plain text, a URI, or an intent</li>
+ <li>The new {@link android.content.ClipData} class represents a complex data type for the
+clipboard</li>
+ <li>The new {@link android.content.ClipboardManager} class allows apps to add {@link
+android.content.ClipData} to the clipboard (copy) and read {@link
+android.content.ClipData} from the clipboard (paste)</li>
+ <li>The {@link android.content.ContentProvider} class has been extended to generate byte
+streams based on data types added to the clipboard and point to data hosted in a
+content provider</li>
+</ul>
+
+<p>See {@link android.content.ClipData} and {@link android.content.ClipboardManager}
+for more information. You can also see an example implementation of copy/paste in an updated
+version of the NotePad application (available in the samples package).</p>
+
+
+<h2>Drag and Drop</h2>
+
+<p>New APIs to perform drag and drop operations, leveraging the system clipboard APIs to
+transport data.</p>
+
+<ul>
+ <li>Any {@link android.view.View} can be used for a drag and drop event and a thumbnail of that
+view is generated and used during the drag</li>
+ <li>{@link android.view.ViewGroup}s that can receive the object are notified during hover and drop
+events</li>
+ <li>The new {@link android.view.DragEvent} class describes a drag event relating to a view,
+including the item's current coordinates, the type of action (whether the drag has entered the
+view, exited the view, started, dropped, etc.), and provides access to the {@link
+android.content.ClipData} being carried</li>
+ <li>The new {@link android.view.View.OnDragListener} interface defines a callback that views
+can register in order to be notified of drag events being dispatched to the view; view's can
+register a drag listener with {@link android.view.View#setOnDragListener setOnDragListener()}</li>
+</ul>
+
+<p>See {@link android.view.DragEvent} and {@link android.view.View.OnDragListener} for more
+information.</p>
+
+
+<h2>New Animations</h2>
+
+<p>An all new animation framework.</p>
+
+<ul>
+ <li>A flexible animation system that allows you to animate the properties of any object (View,
+Drawable, Fragment, Object, anything)</li>
+</ul>
+
+<p>See the {@link android.animation} package.</p>
+
+
+<h2>Extended App Widgets</h2>
+
+<p>App widgets can now be more interactive and accept finger gestures.</p>
+
+<ul>
+ <li>The complete list of supported widgets for an app widget is now: {@link
+android.widget.AnalogClock}, {@link android.widget.Button}, {@link android.widget.Chronometer},
+{@link android.widget.ImageButton}, {@link android.widget.ImageView}, {@link
+android.widget.ProgressBar}, {@link android.widget.TextView}, {@link
+android.widget.ViewFlipper}, {@link android.widget.AdapterViewFlipper}, {@link
+android.widget.StackView}, {@link android.widget.ListView}, and {@link
+android.widget.GridView}.</li>
+</ul>
+
+
+<h2>Extended Status Bar Notifications</h2>
+
+<p>The {@link android.app.Notification} class has been extended to support more content-rich
+status bar notifications when on xlarge screens.</p>
+
+<ul>
+ <li>New {@link android.app.Notification.Builder} class helps you easily create new {@link
+android.app.Notification} objects</li>
+ <li>Support for a title in the status bar ticker (in addition to the normal ticker text)</li>
+ <li>Support for a large "sender" icon in the notification&mdash;a second icon intended for
+social applications to show the contact photo of the person who is the source of the
+notification</li>
+ <li>Support for custom layouts in the status bar ticker</li>
+ <li>Support for buttons in the expanded notification that deliver custom intents
+(such as to control ongoing music in the background)</li>
+</ul>
+
+
+<h2>Plus Android 2.3</h2>
+
+<p>Honeycomb includes all platform changes introduced for Android 2.3.</p>
+
+<p>To take full advantage of Honeycomb, you should also be aware of the new features
+and APIs introduced for Android 2.3. To learn more, read the <a
+href="{@docRoot}sdk/android-2.3.html">Android 2.3 release notes</a>.</p>
+
+<div class="special">
+<p>To set up your preview SDK and start developing apps for Honeycomb, see the <a
+href="{@docRoot}sdk/preview/installing.html">Getting Started</a> guide.</p>
+</div>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/html/sdk/preview/installing.jd b/docs/html/sdk/preview/installing.jd
index 1e6b26b..c835c49 100644
--- a/docs/html/sdk/preview/installing.jd
+++ b/docs/html/sdk/preview/installing.jd
@@ -1,5 +1,62 @@
-sdk.redirect=true
-
+page.title=Getting Started with Honeycomb
@jd:body
+<p>First, you need to set up your development environment with the new SDK Tools and preview
+platform:</p>
+
+<ol>
+ <li>Unpack the SDK Tools r8 package you've received.
+ <p>If you have an existing Android SDK directory, simply replace your existing {@code
+tools/} directory with the one from the new package and add the {@code platform-tools/}
+directory along side it (at the root of the SDK directory).</p></li>
+ <li>Unpack the platform package ({@code android-Froyo}) and place it in your SDK's {@code
+platforms/} directory.</li>
+ <li>If you're using Eclipse, also update your Eclipse plugin using the provided archive file.
+ <ol>
+ <li>Select <strong>Help > Install new software</strong>.</li>
+ <li>Click <strong>Add</strong>.</li>
+ <li>Click <strong>Archive</strong>.</li>
+ <li>Locate and select the archive file. Click <strong>OK</strong>.
+ <p>Developer Tools now appear in the Available Software window and you can proceed
+to install the plugin.</p>
+ </li>
+ </ol>
+ </li>
+</ol>
+
+<p class="note"><strong>Note:</strong> Beginning with SDK Tools r8 (the version you've received),
+the {@code adb} tool is now located in the {@code &lt;sdk&gt;/platform-tools/} directory (instead
+of in {@code &lt;sdk&gt;/tools/}). Be sure to update your {@code PATH} environment variable and any
+build/debugging scripts you have.</p>
+
+
+
+<h2 id="Setup">Set Up Your AVD and Application</h2>
+
+<p>With your SDK now set up, follow these steps to start developing an application for
+Honeycomb.</p>
+
+<ol>
+
+ <li>Create a new AVD targeted to "Android Froyo (Preview)" and with a custom skin resolution of
+1280 x 800.</li>
+
+ <li>Set the build target of your application to "Android Froyo (Preview)".</li>
+ <li>Set your manifest file's {@code &lt;uses-sdk&gt;} element to use {@code
+android:minSdkVersion="Froyo"}. For example:
+<pre>
+&lt;manifest&gt;
+ &lt;uses-sdk android:minSdkVersion="Froyo" /&gt;
+ ...
+&lt;/manifest&gt;
+</pre>
+<p>"Froyo" is a provisional API Level for the Honeycomb release, used only during the preview
+period. When the APIs are
+finalized and the SDK is released publicly, you must update this with the appropriate API Level
+integer.</p>
+<p class="note"><strong>Note:</strong> By providing your {@code &lt;uses-sdk&gt;} element in the
+manifest file <em>before</em> the {@code &lt;application&gt;} element, your application will
+automatically apply the new Holographic theme.</p>
+</li>
+</ol>
diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs
index 057d9e0..0b74bd6 100644
--- a/docs/html/sdk/sdk_toc.cs
+++ b/docs/html/sdk/sdk_toc.cs
@@ -37,6 +37,17 @@
</ul>
</li><?cs
/if ?>
+ <?cs
+ if:sdk.preview ?>
+ <li><h2>Android Preview SDK</h2></li>
+ <ul>
+ <li><a href="<?cs var:toroot ?>sdk/preview/features.html">Introduction
+to Honeycomb</a></li>
+ <li><a href="<?cs var:toroot ?>sdk/preview/installing.html">Getting
+Started</a></li>
+ </ul>
+ </li><?cs
+ /if ?>
<li>
<h2>
<span class="en">Downloadable SDK Components</span>
diff --git a/libs/binder/CursorWindow.cpp b/libs/binder/CursorWindow.cpp
index fbba281..47bbd04 100644
--- a/libs/binder/CursorWindow.cpp
+++ b/libs/binder/CursorWindow.cpp
@@ -219,7 +219,8 @@ LOG_WINDOW("follwing 'pointer' to next chunk, offset of next pointer is %d", chu
field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column)
{
if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
- LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+ LOGE("Failed to read row# %d, column# from a CursorWindow which has %d rows, %d columns.",
+ row, column, mHeader->numRows, mHeader->numColumns);
return NULL;
}
row_slot_t * rowSlot = getRowSlot(row);
@@ -238,7 +239,8 @@ field_slot_t * CursorWindow::getFieldSlotWithCheck(int row, int column)
uint32_t CursorWindow::read_field_slot(int row, int column, field_slot_t * slotOut)
{
if (row < 0 || row >= mHeader->numRows || column < 0 || column >= mHeader->numColumns) {
- LOGE("Bad request for field slot %d,%d. numRows = %d, numColumns = %d", row, column, mHeader->numRows, mHeader->numColumns);
+ LOGE("Can't read row# %d, col# %d from CursorWindow. Make sure your Cursor is initialized correctly.",
+ row, column);
return -1;
}
row_slot_t * rowSlot = getRowSlot(row);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
index 7174e2b..484f6e7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java
@@ -25,11 +25,6 @@ import com.android.mediaframeworktest.MediaNames;
import com.android.mediaframeworktest.MediaProfileReader;
import android.test.suitebuilder.annotation.*;
-/**
- * WARNING:
- * Currently, captureFrame() does not work, due to hardware access permission problem.
- * We are currently only testing the metadata/album art retrieval features.
- */
public class MediaMetadataRetrieverTest extends AndroidTestCase {
private static final String TAG = "MediaMetadataRetrieverTest";
@@ -101,6 +96,7 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
} catch (Exception e) {
Log.e(TAG, "Fails to convert the bitmap to a JPEG file for " + MediaNames.THUMBNAIL_CAPTURE_TEST_FILES[i]);
hasFailed = true;
+ Log.e(TAG, e.toString());
}
} catch(Exception e) {
Log.e(TAG, "Fails to setDataSource for file " + MediaNames.THUMBNAIL_CAPTURE_TEST_FILES[i]);
@@ -148,11 +144,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
public static void testBasicNormalMethodCallSequence() throws Exception {
boolean hasFailed = false;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
try {
retriever.setDataSource(MediaNames.TEST_PATH_1);
- /*
- * captureFrame() fails due to lack of permission to access hardware decoder devices
Bitmap bitmap = retriever.captureFrame();
assertTrue(bitmap != null);
try {
@@ -162,7 +155,6 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
} catch (Exception e) {
throw new Exception("Fails to convert the bitmap to a JPEG file for " + MediaNames.TEST_PATH_1, e);
}
- */
extractAllSupportedMetadataValues(retriever);
} catch(Exception e) {
Log.e(TAG, "Fails to setDataSource for " + MediaNames.TEST_PATH_1, e);
@@ -251,17 +243,14 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
assertTrue(!hasFailed);
}
- // Due to the lack of permission to access hardware decoder, any calls
- // attempting to capture a frame will fail. These are commented out for now
- // until we find a solution to this access permission problem.
@MediumTest
public static void testIntendedUsage() {
// By default, capture frame and retrieve metadata
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
boolean hasFailed = false;
- // retriever.setDataSource(MediaNames.TEST_PATH_1);
- // assertTrue(retriever.captureFrame() != null);
- // assertTrue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS) != null);
+ retriever.setDataSource(MediaNames.TEST_PATH_1);
+ assertTrue(retriever.captureFrame() != null);
+ assertTrue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS) != null);
// Do not capture frame or retrieve metadata
retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY & MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
@@ -276,9 +265,9 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
}
// Capture frame only
- // retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
- // retriever.setDataSource(MediaNames.TEST_PATH_1);
- // assertTrue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS) == null);
+ retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
+ retriever.setDataSource(MediaNames.TEST_PATH_1);
+ assertTrue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS) == null);
// Retriever metadata only
retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
@@ -289,10 +278,10 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {
}
// Capture frame and retrieve metadata
- // retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY | MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
- // retriever.setDataSource(MediaNames.TEST_PATH_1);
- // assertTrue(retriever.captureFrame() != null);
- // assertTrue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS) != null);
+ retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY | MediaMetadataRetriever.MODE_GET_METADATA_ONLY);
+ retriever.setDataSource(MediaNames.TEST_PATH_1);
+ assertTrue(retriever.captureFrame() != null);
+ assertTrue(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS) != null);
retriever.release();
assertTrue(!hasFailed);
}
diff --git a/opengl/tests/hwc/Android.mk b/opengl/tests/hwc/Android.mk
new file mode 100644
index 0000000..743dbf1
--- /dev/null
+++ b/opengl/tests/hwc/Android.mk
@@ -0,0 +1,27 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES:= hwc_stress.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libEGL \
+ libGLESv2 \
+ libui \
+ libhardware \
+
+LOCAL_STATIC_LIBRARIES := \
+ libtestUtil \
+
+LOCAL_C_INCLUDES += \
+ system/extras/tests/include \
+ hardware/libhardware/include \
+
+LOCAL_MODULE:= hwc_stress
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativestresstest
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+
+include $(BUILD_NATIVE_TEST)
diff --git a/opengl/tests/hwc/hwc_stress.cpp b/opengl/tests/hwc/hwc_stress.cpp
new file mode 100644
index 0000000..d119734
--- /dev/null
+++ b/opengl/tests/hwc/hwc_stress.cpp
@@ -0,0 +1,1193 @@
+/*
+ * Copyright (C) 2010 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.
+ *
+ */
+
+/*
+ * Hardware Composer stress test
+ *
+ * Performs a pseudo-random (prandom) sequence of operations to the
+ * Hardware Composer (HWC), for a specified number of passes or for
+ * a specified period of time. By default the period of time is FLT_MAX,
+ * so that the number of passes will take precedence.
+ *
+ * The passes are grouped together, where (pass / passesPerGroup) specifies
+ * which group a particular pass is in. This causes every passesPerGroup
+ * worth of sequential passes to be within the same group. Computationally
+ * intensive operations are performed just once at the beginning of a group
+ * of passes and then used by all the passes in that group. This is done
+ * so as to increase both the average and peak rate of graphic operations,
+ * by moving computationally intensive operations to the beginning of a group.
+ * In particular, at the start of each group of passes a set of
+ * graphic buffers are created, then used by the first and remaining
+ * passes of that group of passes.
+ *
+ * The per-group initialization of the graphic buffers is performed
+ * by a function called initFrames. This function creates an array
+ * of smart pointers to the graphic buffers, in the form of a vector
+ * of vectors. The array is accessed in row major order, so each
+ * row is a vector of smart pointers. All the pointers of a single
+ * row point to graphic buffers which use the same pixel format and
+ * have the same dimension, although it is likely that each one is
+ * filled with a different color. This is done so that after doing
+ * the first HWC prepare then set call, subsequent set calls can
+ * be made with each of the layer handles changed to a different
+ * graphic buffer within the same row. Since the graphic buffers
+ * in a particular row have the same pixel format and dimension,
+ * additional HWC set calls can be made, without having to perform
+ * an HWC prepare call.
+ *
+ * This test supports the following command-line options:
+ *
+ * -v Verbose
+ * -s num Starting pass
+ * -e num Ending pass
+ * -p num Execute the single pass specified by num
+ * -n num Number of set operations to perform after each prepare operation
+ * -t float Maximum time in seconds to execute the test
+ * -d float Delay in seconds performed after each set operation
+ * -D float Delay in seconds performed after the last pass is executed
+ *
+ * Typically the test is executed for a large range of passes. By default
+ * passes 0 through 99999 (100,000 passes) are executed. Although this test
+ * does not validate the generated image, at times it is useful to reexecute
+ * a particular pass and leave the displayed image on the screen for an
+ * extended period of time. This can be done either by setting the -s
+ * and -e options to the desired pass, along with a large value for -D.
+ * This can also be done via the -p option, again with a large value for
+ * the -D options.
+ *
+ * So far this test only contains code to create graphic buffers with
+ * a continuous solid color. Although this test is unable to validate the
+ * image produced, any image that contains other than rectangles of a solid
+ * color are incorrect. Note that the rectangles may use a transparent
+ * color and have a blending operation that causes the color in overlapping
+ * rectangles to be mixed. In such cases the overlapping portions may have
+ * a different color from the rest of the rectangle.
+ */
+
+#include <algorithm>
+#include <assert.h>
+#include <cerrno>
+#include <cmath>
+#include <cstdlib>
+#include <ctime>
+#include <libgen.h>
+#include <sched.h>
+#include <sstream>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <vector>
+
+#include <arpa/inet.h> // For ntohl() and htonl()
+
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <ui/FramebufferNativeWindow.h>
+#include <ui/GraphicBuffer.h>
+#include <ui/EGLUtils.h>
+
+#define LOG_TAG "hwcStressTest"
+#include <utils/Log.h>
+#include <testUtil.h>
+
+#include <hardware/hwcomposer.h>
+
+using namespace std;
+using namespace android;
+
+const float maxSizeRatio = 1.3; // Graphic buffers can be upto this munch
+ // larger than the default screen size
+const unsigned int passesPerGroup = 10; // A group of passes all use the same
+ // graphic buffers
+const float rareRatio = 0.1; // Ratio at which rare conditions are produced.
+
+// Defaults for command-line options
+const bool defaultVerbose = false;
+const unsigned int defaultStartPass = 0;
+const unsigned int defaultEndPass = 99999;
+const unsigned int defaultPerPassNumSet = 10;
+const float defaultPerPassDelay = 0.1;
+const float defaultEndDelay = 2.0; // Default delay between completion of
+ // final pass and restart of framework
+const float defaultDuration = FLT_MAX; // A fairly long time, so that
+ // range of passes will have
+ // precedence
+
+// Command-line option settings
+static bool verbose = defaultVerbose;
+static unsigned int startPass = defaultStartPass;
+static unsigned int endPass = defaultEndPass;
+static unsigned int numSet = defaultPerPassNumSet;
+static float perSetDelay = defaultPerPassDelay;
+static float endDelay = defaultEndDelay;
+static float duration = defaultDuration;
+
+// Command-line mutual exclusion detection flags.
+// Corresponding flag set true once an option is used.
+bool eFlag, sFlag, pFlag;
+
+#define MAXSTR 100
+#define MAXCMD 200
+#define BITSPERBYTE 8 // TODO: Obtain from <values.h>, once
+ // it has been added
+
+#define CMD_STOP_FRAMEWORK "stop 2>&1"
+#define CMD_START_FRAMEWORK "start 2>&1"
+
+#define NUMA(a) (sizeof(a) / sizeof(a [0]))
+#define MEMCLR(addr, size) do { \
+ memset((addr), 0, (size)); \
+ } while (0)
+
+// Represent RGB color as fraction of color components.
+// Each of the color components are expected in the range [0.0, 1.0]
+class RGBColor {
+ public:
+ RGBColor(): _r(0.0), _g(0.0), _b(0.0) {};
+ RGBColor(float f): _r(f), _g(f), _b(f) {}; // Gray
+ RGBColor(float r, float g, float b): _r(r), _g(g), _b(b) {};
+ float r(void) const { return _r; }
+ float g(void) const { return _g; }
+ float b(void) const { return _b; }
+
+ private:
+ float _r;
+ float _g;
+ float _b;
+};
+
+// Represent YUV color as fraction of color components.
+// Each of the color components are expected in the range [0.0, 1.0]
+class YUVColor {
+ public:
+ YUVColor(): _y(0.0), _u(0.0), _v(0.0) {};
+ YUVColor(float f): _y(f), _u(0.0), _v(0.0) {}; // Gray
+ YUVColor(float y, float u, float v): _y(y), _u(u), _v(v) {};
+ float y(void) const { return _y; }
+ float u(void) const { return _u; }
+ float v(void) const { return _v; }
+
+ private:
+ float _y;
+ float _u;
+ float _v;
+};
+
+// File scope constants
+static const struct {
+ unsigned int format;
+ const char *desc;
+} graphicFormat[] = {
+ {HAL_PIXEL_FORMAT_RGBA_8888, "RGBA8888"},
+ {HAL_PIXEL_FORMAT_RGBX_8888, "RGBX8888"},
+// {HAL_PIXEL_FORMAT_RGB_888, "RGB888"}, // Known issue: 3198458
+ {HAL_PIXEL_FORMAT_RGB_565, "RGB565"},
+ {HAL_PIXEL_FORMAT_BGRA_8888, "BGRA8888"},
+ {HAL_PIXEL_FORMAT_RGBA_5551, "RGBA5551"},
+ {HAL_PIXEL_FORMAT_RGBA_4444, "RGBA4444"},
+// {HAL_PIXEL_FORMAT_YV12, "YV12"}, // Currently not supported by HWC
+};
+const unsigned int blendingOps[] = {
+ HWC_BLENDING_NONE,
+ HWC_BLENDING_PREMULT,
+ HWC_BLENDING_COVERAGE,
+};
+const unsigned int layerFlags[] = {
+ HWC_SKIP_LAYER,
+};
+const vector<unsigned int> vecLayerFlags(layerFlags,
+ layerFlags + NUMA(layerFlags));
+
+const unsigned int transformFlags[] = {
+ HWC_TRANSFORM_FLIP_H,
+ HWC_TRANSFORM_FLIP_V,
+ HWC_TRANSFORM_ROT_90,
+ // ROT_180 & ROT_270 intentionally not listed, because they
+ // they are formed from combinations of the flags already listed.
+};
+const vector<unsigned int> vecTransformFlags(transformFlags,
+ transformFlags + NUMA(transformFlags));
+
+// File scope globals
+static const int texUsage = GraphicBuffer::USAGE_HW_TEXTURE |
+ GraphicBuffer::USAGE_SW_WRITE_RARELY;
+static hw_module_t const *hwcModule;
+static hwc_composer_device_t *hwcDevice;
+static vector <vector <sp<GraphicBuffer> > > frames;
+static EGLDisplay dpy;
+static EGLContext context;
+static EGLSurface surface;
+static EGLint width, height;
+
+// File scope prototypes
+static void execCmd(const char *cmd);
+static void checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE);
+static void checkGlError(const char* op);
+static void printEGLConfiguration(EGLDisplay dpy, EGLConfig config);
+static void printGLString(const char *name, GLenum s);
+static hwc_layer_list_t *createLayerList(size_t numLayers);
+static void freeLayerList(hwc_layer_list_t *list);
+static void fillColor(GraphicBuffer *gBuf, RGBColor color, float trans);
+static void fillColor(GraphicBuffer *gBuf, YUVColor color, float trans);
+void init(void);
+void initFrames(unsigned int seed);
+void displayList(hwc_layer_list_t *list);
+void displayListPrepareModifiable(hwc_layer_list_t *list);
+void displayListHandles(hwc_layer_list_t *list);
+const char *graphicFormat2str(unsigned int format);
+template <class T> vector<T> vectorRandSelect(const vector<T>& vec, size_t num);
+template <class T> T vectorOr(const vector<T>& vec);
+
+/*
+ * Main
+ *
+ * Performs the following high-level sequence of operations:
+ *
+ * 1. Command-line parsing
+ *
+ * 2. Initialization
+ *
+ * 3. For each pass:
+ *
+ * a. If pass is first pass or in a different group from the
+ * previous pass, initialize the array of graphic buffers.
+ *
+ * b. Create a HWC list with room to specify a prandomly
+ * selected number of layers.
+ *
+ * c. Select a subset of the rows from the graphic buffer array,
+ * such that there is a unique row to be used for each
+ * of the layers in the HWC list.
+ *
+ * d. Prandomly fill in the HWC list with handles
+ * selected from any of the columns of the selected row.
+ *
+ * e. Pass the populated list to the HWC prepare call.
+ *
+ * f. Pass the populated list to the HWC set call.
+ *
+ * g. If additional set calls are to be made, then for each
+ * additional set call, select a new set of handles and
+ * perform the set call.
+ */
+int
+main(int argc, char *argv[])
+{
+ int rv, opt;
+ char *chptr;
+ unsigned int pass;
+ char cmd[MAXCMD];
+ struct timeval startTime, currentTime, delta;
+
+ testSetLogCatTag(LOG_TAG);
+
+ // Parse command line arguments
+ while ((opt = getopt(argc, argv, "vp:d:D:n:s:e:t:?h")) != -1) {
+ switch (opt) {
+ case 'd': // Delay after each set operation
+ perSetDelay = strtod(optarg, &chptr);
+ if ((*chptr != '\0') || (perSetDelay < 0.0)) {
+ testPrintE("Invalid command-line specified per pass delay of: "
+ "%s", optarg);
+ exit(1);
+ }
+ break;
+
+ case 'D': // End of test delay
+ // Delay between completion of final pass and restart
+ // of framework
+ endDelay = strtod(optarg, &chptr);
+ if ((*chptr != '\0') || (endDelay < 0.0)) {
+ testPrintE("Invalid command-line specified end of test delay "
+ "of: %s", optarg);
+ exit(2);
+ }
+ break;
+
+ case 't': // Duration
+ duration = strtod(optarg, &chptr);
+ if ((*chptr != '\0') || (duration < 0.0)) {
+ testPrintE("Invalid command-line specified duration of: %s",
+ optarg);
+ exit(3);
+ }
+ break;
+
+ case 'n': // Num set operations per pass
+ numSet = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ testPrintE("Invalid command-line specified num set per pass "
+ "of: %s", optarg);
+ exit(4);
+ }
+ break;
+
+ case 's': // Starting Pass
+ sFlag = true;
+ if (pFlag) {
+ testPrintE("Invalid combination of command-line options.");
+ testPrintE(" The -p option is mutually exclusive from the");
+ testPrintE(" -s and -e options.");
+ exit(5);
+ }
+ startPass = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ testPrintE("Invalid command-line specified starting pass "
+ "of: %s", optarg);
+ exit(6);
+ }
+ break;
+
+ case 'e': // Ending Pass
+ eFlag = true;
+ if (pFlag) {
+ testPrintE("Invalid combination of command-line options.");
+ testPrintE(" The -p option is mutually exclusive from the");
+ testPrintE(" -s and -e options.");
+ exit(7);
+ }
+ endPass = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ testPrintE("Invalid command-line specified ending pass "
+ "of: %s", optarg);
+ exit(8);
+ }
+ break;
+
+ case 'p': // Run a single specified pass
+ pFlag = true;
+ if (sFlag || eFlag) {
+ testPrintE("Invalid combination of command-line options.");
+ testPrintE(" The -p option is mutually exclusive from the");
+ testPrintE(" -s and -e options.");
+ exit(9);
+ }
+ startPass = endPass = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ testPrintE("Invalid command-line specified pass of: %s",
+ optarg);
+ exit(10);
+ }
+ break;
+
+ case 'v': // Verbose
+ verbose = true;
+ break;
+
+ case 'h': // Help
+ case '?':
+ default:
+ testPrintE(" %s [options]", basename(argv[0]));
+ testPrintE(" options:");
+ testPrintE(" -p Execute specified pass");
+ testPrintE(" -s Starting pass");
+ testPrintE(" -e Ending pass");
+ testPrintE(" -t Duration");
+ testPrintE(" -d Delay after each set operation");
+ testPrintE(" -D End of test delay");
+ testPrintE(" -n Num set operations per pass");
+ testPrintE(" -v Verbose");
+ exit(((optopt == 0) || (optopt == '?')) ? 0 : 11);
+ }
+ }
+ if (endPass < startPass) {
+ testPrintE("Unexpected ending pass before starting pass");
+ testPrintE(" startPass: %u endPass: %u", startPass, endPass);
+ exit(12);
+ }
+ if (argc != optind) {
+ testPrintE("Unexpected command-line postional argument");
+ testPrintE(" %s [-s start_pass] [-e end_pass] [-t duration]",
+ basename(argv[0]));
+ exit(13);
+ }
+ testPrintI("duration: %g", duration);
+ testPrintI("startPass: %u", startPass);
+ testPrintI("endPass: %u", endPass);
+ testPrintI("numSet: %u", numSet);
+
+ // Stop framework
+ rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STOP_FRAMEWORK);
+ if (rv >= (signed) sizeof(cmd) - 1) {
+ testPrintE("Command too long for: %s", CMD_STOP_FRAMEWORK);
+ exit(14);
+ }
+ execCmd(cmd);
+ testDelay(1.0); // TODO - needs means to query whether asyncronous stop
+ // framework operation has completed. For now, just wait
+ // a long time.
+
+ init();
+
+ // For each pass
+ gettimeofday(&startTime, NULL);
+ for (pass = startPass; pass <= endPass; pass++) {
+ // Stop if duration of work has already been performed
+ gettimeofday(&currentTime, NULL);
+ delta = tvDelta(&startTime, &currentTime);
+ if (tv2double(&delta) > duration) { break; }
+
+ // Regenerate a new set of test frames when this pass is
+ // either the first pass or is in a different group then
+ // the previous pass. A group of passes are passes that
+ // all have the same quotient when their pass number is
+ // divided by passesPerGroup.
+ if ((pass == startPass)
+ || ((pass / passesPerGroup) != ((pass - 1) / passesPerGroup))) {
+ initFrames(pass / passesPerGroup);
+ }
+
+ testPrintI("==== Starting pass: %u", pass);
+
+ // Cause deterministic sequence of prandom numbers to be
+ // generated for this pass.
+ srand48(pass);
+
+ hwc_layer_list_t *list;
+ list = createLayerList(testRandMod(frames.size()) + 1);
+ if (list == NULL) {
+ testPrintE("createLayerList failed");
+ exit(20);
+ }
+
+ // Prandomly select a subset of frames to be used by this pass.
+ vector <vector <sp<GraphicBuffer> > > selectedFrames;
+ selectedFrames = vectorRandSelect(frames, list->numHwLayers);
+
+ // Any transform tends to create a layer that the hardware
+ // composer is unable to support and thus has to leave for
+ // SurfaceFlinger. Place heavy bias on specifying no transforms.
+ bool noTransform = testRandFract() > rareRatio;
+
+ for (unsigned int n1 = 0; n1 < list->numHwLayers; n1++) {
+ unsigned int idx = testRandMod(selectedFrames[n1].size());
+ sp<GraphicBuffer> gBuf = selectedFrames[n1][idx];
+ hwc_layer_t *layer = &list->hwLayers[n1];
+ layer->handle = gBuf->handle;
+
+ layer->blending = blendingOps[testRandMod(NUMA(blendingOps))];
+ layer->flags = (testRandFract() > rareRatio) ? 0
+ : vectorOr(vectorRandSelect(vecLayerFlags,
+ testRandMod(vecLayerFlags.size() + 1)));
+ layer->transform = (noTransform || testRandFract() > rareRatio) ? 0
+ : vectorOr(vectorRandSelect(vecTransformFlags,
+ testRandMod(vecTransformFlags.size() + 1)));
+ layer->sourceCrop.left = testRandMod(gBuf->getWidth());
+ layer->sourceCrop.top = testRandMod(gBuf->getHeight());
+ layer->sourceCrop.right = layer->sourceCrop.left
+ + testRandMod(gBuf->getWidth() - layer->sourceCrop.left) + 1;
+ layer->sourceCrop.bottom = layer->sourceCrop.top
+ + testRandMod(gBuf->getHeight() - layer->sourceCrop.top) + 1;
+ layer->displayFrame.left = testRandMod(width);
+ layer->displayFrame.top = testRandMod(height);
+ layer->displayFrame.right = layer->displayFrame.left
+ + testRandMod(width - layer->displayFrame.left) + 1;
+ layer->displayFrame.bottom = layer->displayFrame.top
+ + testRandMod(height - layer->displayFrame.top) + 1;
+ layer->visibleRegionScreen.numRects = 1;
+ layer->visibleRegionScreen.rects = &layer->displayFrame;
+ }
+
+ // Perform prepare operation
+ if (verbose) { testPrintI("Prepare:"); displayList(list); }
+ hwcDevice->prepare(hwcDevice, list);
+ if (verbose) {
+ testPrintI("Post Prepare:");
+ displayListPrepareModifiable(list);
+ }
+
+ // Turn off the geometry changed flag
+ list->flags &= ~HWC_GEOMETRY_CHANGED;
+
+ // Perform the set operation(s)
+ if (verbose) {testPrintI("Set:"); }
+ for (unsigned int n1 = 0; n1 < numSet; n1++) {
+ if (verbose) {displayListHandles(list); }
+ hwcDevice->set(hwcDevice, dpy, surface, list);
+
+ // Prandomly select a new set of handles
+ for (unsigned int n1 = 0; n1 < list->numHwLayers; n1++) {
+ unsigned int idx = testRandMod(selectedFrames[n1].size());
+ sp<GraphicBuffer> gBuf = selectedFrames[n1][idx];
+ hwc_layer_t *layer = &list->hwLayers[n1];
+ layer->handle = (native_handle_t *) gBuf->handle;
+ }
+
+ testDelay(perSetDelay);
+ }
+
+
+ freeLayerList(list);
+ testPrintI("==== Completed pass: %u", pass);
+ }
+
+ testDelay(endDelay);
+
+ // Start framework
+ rv = snprintf(cmd, sizeof(cmd), "%s", CMD_START_FRAMEWORK);
+ if (rv >= (signed) sizeof(cmd) - 1) {
+ testPrintE("Command too long for: %s", CMD_START_FRAMEWORK);
+ exit(21);
+ }
+ execCmd(cmd);
+
+ testPrintI("Successfully completed %u passes", pass - startPass);
+
+ return 0;
+}
+
+/*
+ * Execute Command
+ *
+ * Executes the command pointed to by cmd. Output from the
+ * executed command is captured and sent to LogCat Info. Once
+ * the command has finished execution, it's exit status is captured
+ * and checked for an exit status of zero. Any other exit status
+ * causes diagnostic information to be printed and an immediate
+ * testcase failure.
+ */
+static void execCmd(const char *cmd)
+{
+ FILE *fp;
+ int rv;
+ int status;
+ char str[MAXSTR];
+
+ // Display command to be executed
+ testPrintI("cmd: %s", cmd);
+
+ // Execute the command
+ fflush(stdout);
+ if ((fp = popen(cmd, "r")) == NULL) {
+ testPrintE("execCmd popen failed, errno: %i", errno);
+ exit(30);
+ }
+
+ // Obtain and display each line of output from the executed command
+ while (fgets(str, sizeof(str), fp) != NULL) {
+ if ((strlen(str) > 1) && (str[strlen(str) - 1] == '\n')) {
+ str[strlen(str) - 1] = '\0';
+ }
+ testPrintI(" out: %s", str);
+ }
+
+ // Obtain and check return status of executed command.
+ // Fail on non-zero exit status
+ status = pclose(fp);
+ if (!(WIFEXITED(status) && (WEXITSTATUS(status) == 0))) {
+ testPrintE("Unexpected command failure");
+ testPrintE(" status: %#x", status);
+ if (WIFEXITED(status)) {
+ testPrintE("WEXITSTATUS: %i", WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ testPrintE("WTERMSIG: %i", WTERMSIG(status));
+ }
+ exit(31);
+ }
+}
+
+static void checkEglError(const char* op, EGLBoolean returnVal) {
+ if (returnVal != EGL_TRUE) {
+ testPrintE("%s() returned %d", op, returnVal);
+ }
+
+ for (EGLint error = eglGetError(); error != EGL_SUCCESS; error
+ = eglGetError()) {
+ testPrintE("after %s() eglError %s (0x%x)",
+ op, EGLUtils::strerror(error), error);
+ }
+}
+
+static void checkGlError(const char* op) {
+ for (GLint error = glGetError(); error; error
+ = glGetError()) {
+ testPrintE("after %s() glError (0x%x)", op, error);
+ }
+}
+
+static void printEGLConfiguration(EGLDisplay dpy, EGLConfig config) {
+
+#define X(VAL) {VAL, #VAL}
+ struct {EGLint attribute; const char* name;} names[] = {
+ X(EGL_BUFFER_SIZE),
+ X(EGL_ALPHA_SIZE),
+ X(EGL_BLUE_SIZE),
+ X(EGL_GREEN_SIZE),
+ X(EGL_RED_SIZE),
+ X(EGL_DEPTH_SIZE),
+ X(EGL_STENCIL_SIZE),
+ X(EGL_CONFIG_CAVEAT),
+ X(EGL_CONFIG_ID),
+ X(EGL_LEVEL),
+ X(EGL_MAX_PBUFFER_HEIGHT),
+ X(EGL_MAX_PBUFFER_PIXELS),
+ X(EGL_MAX_PBUFFER_WIDTH),
+ X(EGL_NATIVE_RENDERABLE),
+ X(EGL_NATIVE_VISUAL_ID),
+ X(EGL_NATIVE_VISUAL_TYPE),
+ X(EGL_SAMPLES),
+ X(EGL_SAMPLE_BUFFERS),
+ X(EGL_SURFACE_TYPE),
+ X(EGL_TRANSPARENT_TYPE),
+ X(EGL_TRANSPARENT_RED_VALUE),
+ X(EGL_TRANSPARENT_GREEN_VALUE),
+ X(EGL_TRANSPARENT_BLUE_VALUE),
+ X(EGL_BIND_TO_TEXTURE_RGB),
+ X(EGL_BIND_TO_TEXTURE_RGBA),
+ X(EGL_MIN_SWAP_INTERVAL),
+ X(EGL_MAX_SWAP_INTERVAL),
+ X(EGL_LUMINANCE_SIZE),
+ X(EGL_ALPHA_MASK_SIZE),
+ X(EGL_COLOR_BUFFER_TYPE),
+ X(EGL_RENDERABLE_TYPE),
+ X(EGL_CONFORMANT),
+ };
+#undef X
+
+ for (size_t j = 0; j < sizeof(names) / sizeof(names[0]); j++) {
+ EGLint value = -1;
+ EGLint returnVal = eglGetConfigAttrib(dpy, config, names[j].attribute, &value);
+ EGLint error = eglGetError();
+ if (returnVal && error == EGL_SUCCESS) {
+ testPrintI(" %s: %d (%#x)", names[j].name, value, value);
+ }
+ }
+ testPrintI("");
+}
+
+static void printGLString(const char *name, GLenum s)
+{
+ const char *v = (const char *) glGetString(s);
+
+ if (v == NULL) {
+ testPrintI("GL %s unknown", name);
+ } else {
+ testPrintI("GL %s = %s", name, v);
+ }
+}
+
+/*
+ * createLayerList
+ * dynamically creates layer list with numLayers worth
+ * of hwLayers entries.
+ */
+static hwc_layer_list_t *createLayerList(size_t numLayers)
+{
+ hwc_layer_list_t *list;
+
+ size_t size = sizeof(hwc_layer_list) + numLayers * sizeof(hwc_layer_t);
+ if ((list = (hwc_layer_list_t *) calloc(1, size)) == NULL) {
+ return NULL;
+ }
+ list->flags = HWC_GEOMETRY_CHANGED;
+ list->numHwLayers = numLayers;
+
+ return list;
+}
+
+/*
+ * freeLayerList
+ * Frees memory previous allocated via createLayerList().
+ */
+static void freeLayerList(hwc_layer_list_t *list)
+{
+ free(list);
+}
+
+static void fillColor(GraphicBuffer *gBuf, RGBColor color, float trans)
+{
+ unsigned char* buf = NULL;
+ status_t err;
+ unsigned int numPixels = gBuf->getWidth() * gBuf->getHeight();
+ uint32_t pixel;
+
+ // RGB 2 YUV conversion ratios
+ const struct rgb2yuvRatios {
+ int format;
+ float weightRed;
+ float weightBlu;
+ float weightGrn;
+ } rgb2yuvRatios[] = {
+ { HAL_PIXEL_FORMAT_YV12, 0.299, 0.114, 0.587 },
+ };
+
+ const struct rgbAttrib {
+ int format;
+ bool hostByteOrder;
+ size_t bytes;
+ size_t rOffset;
+ size_t rSize;
+ size_t gOffset;
+ size_t gSize;
+ size_t bOffset;
+ size_t bSize;
+ size_t aOffset;
+ size_t aSize;
+ } rgbAttributes[] = {
+ {HAL_PIXEL_FORMAT_RGBA_8888, false, 4, 0, 8, 8, 8, 16, 8, 24, 8},
+ {HAL_PIXEL_FORMAT_RGBX_8888, false, 4, 0, 8, 8, 8, 16, 8, 0, 0},
+ {HAL_PIXEL_FORMAT_RGB_888, false, 3, 0, 8, 8, 8, 16, 8, 0, 0},
+ {HAL_PIXEL_FORMAT_RGB_565, true, 2, 0, 5, 5, 6, 11, 5, 0, 0},
+ {HAL_PIXEL_FORMAT_BGRA_8888, false, 4, 16, 8, 8, 8, 0, 8, 24, 8},
+ {HAL_PIXEL_FORMAT_RGBA_5551, true , 2, 0, 5, 5, 5, 10, 5, 15, 1},
+ {HAL_PIXEL_FORMAT_RGBA_4444, false, 2, 12, 4, 0, 4, 4, 4, 8, 4},
+ };
+
+ // If YUV format, convert color and pass work to YUV color fill
+ for (unsigned int n1 = 0; n1 < NUMA(rgb2yuvRatios); n1++) {
+ if (gBuf->getPixelFormat() == rgb2yuvRatios[n1].format) {
+ float wr = rgb2yuvRatios[n1].weightRed;
+ float wb = rgb2yuvRatios[n1].weightBlu;
+ float wg = rgb2yuvRatios[n1].weightGrn;
+ float y = wr * color.r() + wb * color.b() + wg * color.g();
+ float u = 0.5 * ((color.b() - y) / (1 - wb)) + 0.5;
+ float v = 0.5 * ((color.r() - y) / (1 - wr)) + 0.5;
+ YUVColor yuvColor(y, u, v);
+ fillColor(gBuf, yuvColor, trans);
+ return;
+ }
+ }
+
+ const struct rgbAttrib *attrib;
+ for (attrib = rgbAttributes; attrib < rgbAttributes + NUMA(rgbAttributes);
+ attrib++) {
+ if (attrib->format == gBuf->getPixelFormat()) { break; }
+ }
+ if (attrib >= rgbAttributes + NUMA(rgbAttributes)) {
+ testPrintE("fillColor rgb unsupported format of: %u",
+ gBuf->getPixelFormat());
+ exit(50);
+ }
+
+ pixel = htonl((uint32_t) (((1 << attrib->rSize) - 1) * color.r())
+ << ((sizeof(pixel) * BITSPERBYTE)
+ - (attrib->rOffset + attrib->rSize)));
+ pixel |= htonl((uint32_t) (((1 << attrib->gSize) - 1) * color.g())
+ << ((sizeof(pixel) * BITSPERBYTE)
+ - (attrib->gOffset + attrib->gSize)));
+ pixel |= htonl((uint32_t) (((1 << attrib->bSize) - 1) * color.b())
+ << ((sizeof(pixel) * BITSPERBYTE)
+ - (attrib->bOffset + attrib->bSize)));
+ if (attrib->aSize) {
+ pixel |= htonl((uint32_t) (((1 << attrib->aSize) - 1) * trans)
+ << ((sizeof(pixel) * BITSPERBYTE)
+ - (attrib->aOffset + attrib->aSize)));
+ }
+ if (attrib->hostByteOrder) {
+ pixel = ntohl(pixel);
+ pixel >>= sizeof(pixel) * BITSPERBYTE - attrib->bytes * BITSPERBYTE;
+ }
+
+ err = gBuf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&buf));
+ if (err != 0) {
+ testPrintE("fillColor rgb lock failed: %d", err);
+ exit(51);
+ }
+
+ for (unsigned int n1 = 0; n1 < numPixels; n1++) {
+ memmove(buf, &pixel, attrib->bytes);
+ buf += attrib->bytes;
+ }
+
+ err = gBuf->unlock();
+ if (err != 0) {
+ testPrintE("fillColor rgb unlock failed: %d", err);
+ exit(52);
+ }
+}
+
+static void fillColor(GraphicBuffer *gBuf, YUVColor color, float trans)
+{
+ unsigned char* buf = NULL;
+ status_t err;
+ unsigned int width = gBuf->getWidth();
+ unsigned int height = gBuf->getHeight();
+
+ const struct yuvAttrib {
+ int format;
+ size_t padWidth;
+ bool planar;
+ unsigned int uSubSampX;
+ unsigned int uSubSampY;
+ unsigned int vSubSampX;
+ unsigned int vSubSampY;
+ } yuvAttributes[] = {
+ { HAL_PIXEL_FORMAT_YV12, 16, true, 2, 2, 2, 2},
+ };
+
+ const struct yuvAttrib *attrib;
+ for (attrib = yuvAttributes; attrib < yuvAttributes + NUMA(yuvAttributes);
+ attrib++) {
+ if (attrib->format == gBuf->getPixelFormat()) { break; }
+ }
+ if (attrib >= yuvAttributes + NUMA(yuvAttributes)) {
+ testPrintE("fillColor yuv unsupported format of: %u",
+ gBuf->getPixelFormat());
+ exit(60);
+ }
+
+ assert(attrib->planar == true); // So far, only know how to handle planar
+
+ // If needed round width up to pad size
+ if (width % attrib->padWidth) {
+ width += attrib->padWidth - (width % attrib->padWidth);
+ }
+ assert((width % attrib->padWidth) == 0);
+
+ err = gBuf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&buf));
+ if (err != 0) {
+ testPrintE("fillColor lock failed: %d", err);
+ exit(61);
+ }
+
+ // Fill in Y component
+ for (unsigned int x = 0; x < width; x++) {
+ for (unsigned int y = 0; y < height; y++) {
+ *buf++ = (x < gBuf->getWidth()) ? (255 * color.y()) : 0;
+ }
+ }
+
+ // Fill in U component
+ for (unsigned int x = 0; x < width; x += attrib->uSubSampX) {
+ for (unsigned int y = 0; y < height; y += attrib->uSubSampY) {
+ *buf++ = (x < gBuf->getWidth()) ? (255 * color.u()) : 0;
+ }
+ }
+
+ // Fill in V component
+ for (unsigned int x = 0; x < width; x += attrib->vSubSampX) {
+ for (unsigned int y = 0; y < height; y += attrib->vSubSampY) {
+ *buf++ = (x < gBuf->getWidth()) ? (255 * color.v()) : 0;
+ }
+ }
+
+ err = gBuf->unlock();
+ if (err != 0) {
+ testPrintE("fillColor unlock failed: %d", err);
+ exit(62);
+ }
+}
+
+void init(void)
+{
+ int rv;
+
+ EGLBoolean returnValue;
+ EGLConfig myConfig = {0};
+ EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+ EGLint sConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_NONE };
+ EGLint majorVersion, minorVersion;
+
+ checkEglError("<init>");
+ dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ checkEglError("eglGetDisplay");
+ if (dpy == EGL_NO_DISPLAY) {
+ testPrintE("eglGetDisplay returned EGL_NO_DISPLAY");
+ exit(70);
+ }
+
+ returnValue = eglInitialize(dpy, &majorVersion, &minorVersion);
+ checkEglError("eglInitialize", returnValue);
+ testPrintI("EGL version %d.%d", majorVersion, minorVersion);
+ if (returnValue != EGL_TRUE) {
+ testPrintE("eglInitialize failed");
+ exit(71);
+ }
+
+ EGLNativeWindowType window = android_createDisplaySurface();
+ if (window == NULL) {
+ testPrintE("android_createDisplaySurface failed");
+ exit(72);
+ }
+ returnValue = EGLUtils::selectConfigForNativeWindow(dpy,
+ sConfigAttribs, window, &myConfig);
+ if (returnValue) {
+ testPrintE("EGLUtils::selectConfigForNativeWindow() returned %d",
+ returnValue);
+ exit(73);
+ }
+ checkEglError("EGLUtils::selectConfigForNativeWindow");
+
+ testPrintI("Chose this configuration:");
+ printEGLConfiguration(dpy, myConfig);
+
+ surface = eglCreateWindowSurface(dpy, myConfig, window, NULL);
+ checkEglError("eglCreateWindowSurface");
+ if (surface == EGL_NO_SURFACE) {
+ testPrintE("gelCreateWindowSurface failed.");
+ exit(74);
+ }
+
+ context = eglCreateContext(dpy, myConfig, EGL_NO_CONTEXT, contextAttribs);
+ checkEglError("eglCreateContext");
+ if (context == EGL_NO_CONTEXT) {
+ testPrintE("eglCreateContext failed");
+ exit(75);
+ }
+ returnValue = eglMakeCurrent(dpy, surface, surface, context);
+ checkEglError("eglMakeCurrent", returnValue);
+ if (returnValue != EGL_TRUE) {
+ testPrintE("eglMakeCurrent failed");
+ exit(76);
+ }
+ eglQuerySurface(dpy, surface, EGL_WIDTH, &width);
+ checkEglError("eglQuerySurface");
+ eglQuerySurface(dpy, surface, EGL_HEIGHT, &height);
+ checkEglError("eglQuerySurface");
+
+ fprintf(stderr, "Window dimensions: %d x %d", width, height);
+
+ printGLString("Version", GL_VERSION);
+ printGLString("Vendor", GL_VENDOR);
+ printGLString("Renderer", GL_RENDERER);
+ printGLString("Extensions", GL_EXTENSIONS);
+
+ if ((rv = hw_get_module(HWC_HARDWARE_MODULE_ID, &hwcModule)) != 0) {
+ testPrintE("hw_get_module failed, rv: %i", rv);
+ errno = -rv;
+ perror(NULL);
+ exit(77);
+ }
+ if ((rv = hwc_open(hwcModule, &hwcDevice)) != 0) {
+ testPrintE("hwc_open failed, rv: %i", rv);
+ errno = -rv;
+ perror(NULL);
+ exit(78);
+ }
+
+ testPrintI("");
+}
+
+/*
+ * Initialize Frames
+ *
+ * Creates an array of graphic buffers, within the global variable
+ * named frames. The graphic buffers are contained within a vector of
+ * verctors. All the graphic buffers in a particular row are of the same
+ * format and dimension. Each graphic buffer is uniformly filled with a
+ * prandomly selected color. It is likely that each buffer, even
+ * in the same row, will be filled with a unique color.
+ */
+void initFrames(unsigned int seed)
+{
+ const size_t maxRows = 5;
+ const size_t minCols = 2; // Need at least double buffering
+ const size_t maxCols = 4; // One more than triple buffering
+
+ if (verbose) { testPrintI("initFrames seed: %u", seed); }
+ srand48(seed);
+ size_t rows = testRandMod(maxRows) + 1;
+
+ frames.clear();
+ frames.resize(rows);
+
+ for (unsigned int row = 0; row < rows; row++) {
+ // All frames within a row have to have the same format and
+ // dimensions. Width and height need to be >= 1.
+ int format = graphicFormat[testRandMod(NUMA(graphicFormat))].format;
+ size_t w = (width * maxSizeRatio) * testRandFract();
+ size_t h = (height * maxSizeRatio) * testRandFract();
+ w = max(1u, w);
+ h = max(1u, h);
+ if (verbose) {
+ testPrintI(" frame %u width: %u height: %u format: %u %s",
+ row, w, h, format, graphicFormat2str(format));
+ }
+
+ size_t cols = testRandMod((maxCols + 1) - minCols) + minCols;
+ frames[row].resize(cols);
+ for (unsigned int col = 0; col < cols; col++) {
+ RGBColor color(testRandFract(), testRandFract(), testRandFract());
+ float transp = testRandFract();
+
+ frames[row][col] = new GraphicBuffer(w, h, format, texUsage);
+ fillColor(frames[row][col].get(), color, transp);
+ if (verbose) {
+ testPrintI(" buf: %p handle: %p color: <%f, %f, %f> "
+ "transp: %f",
+ frames[row][col].get(), frames[row][col]->handle,
+ color.r(), color.g(), color.b(), transp);
+ }
+ }
+ }
+}
+
+void displayList(hwc_layer_list_t *list)
+{
+ testPrintI(" flags: %#x%s", list->flags,
+ (list->flags & HWC_GEOMETRY_CHANGED) ? " GEOMETRY_CHANGED" : "");
+ testPrintI(" numHwLayers: %u", list->numHwLayers);
+
+ for (unsigned int layer = 0; layer < list->numHwLayers; layer++) {
+ testPrintI(" layer %u compositionType: %#x%s%s", layer,
+ list->hwLayers[layer].compositionType,
+ (list->hwLayers[layer].compositionType == HWC_FRAMEBUFFER)
+ ? " FRAMEBUFFER" : "",
+ (list->hwLayers[layer].compositionType == HWC_OVERLAY)
+ ? " OVERLAY" : "");
+
+ testPrintI(" hints: %#x",
+ list->hwLayers[layer].hints,
+ (list->hwLayers[layer].hints & HWC_HINT_TRIPLE_BUFFER)
+ ? " TRIPLE_BUFFER" : "",
+ (list->hwLayers[layer].hints & HWC_HINT_CLEAR_FB)
+ ? " CLEAR_FB" : "");
+
+ testPrintI(" flags: %#x%s",
+ list->hwLayers[layer].flags,
+ (list->hwLayers[layer].flags & HWC_SKIP_LAYER)
+ ? " SKIP_LAYER" : "");
+
+ testPrintI(" handle: %p",
+ list->hwLayers[layer].handle);
+
+ // Intentionally skipped display of ROT_180 & ROT_270,
+ // which are formed from combinations of the other flags.
+ testPrintI(" transform: %#x%s%s%s",
+ list->hwLayers[layer].transform,
+ (list->hwLayers[layer].transform & HWC_TRANSFORM_FLIP_H)
+ ? " FLIP_H" : "",
+ (list->hwLayers[layer].transform & HWC_TRANSFORM_FLIP_V)
+ ? " FLIP_V" : "",
+ (list->hwLayers[layer].transform & HWC_TRANSFORM_ROT_90)
+ ? " ROT_90" : "");
+
+ testPrintI(" blending: %#x",
+ list->hwLayers[layer].blending,
+ (list->hwLayers[layer].blending == HWC_BLENDING_NONE)
+ ? " NONE" : "",
+ (list->hwLayers[layer].blending == HWC_BLENDING_PREMULT)
+ ? " PREMULT" : "",
+ (list->hwLayers[layer].blending == HWC_BLENDING_COVERAGE)
+ ? " COVERAGE" : "");
+
+ testPrintI(" sourceCrop: [%i, %i, %i, %i]",
+ list->hwLayers[layer].sourceCrop.left,
+ list->hwLayers[layer].sourceCrop.top,
+ list->hwLayers[layer].sourceCrop.right,
+ list->hwLayers[layer].sourceCrop.bottom);
+
+ testPrintI(" displayFrame: [%i, %i, %i, %i]",
+ list->hwLayers[layer].displayFrame.left,
+ list->hwLayers[layer].displayFrame.top,
+ list->hwLayers[layer].displayFrame.right,
+ list->hwLayers[layer].displayFrame.bottom);
+ }
+}
+
+/*
+ * Display List Prepare Modifiable
+ *
+ * Displays the portions of a list that are meant to be modified by
+ * a prepare call.
+ */
+void displayListPrepareModifiable(hwc_layer_list_t *list)
+{
+ for (unsigned int layer = 0; layer < list->numHwLayers; layer++) {
+ testPrintI(" layer %u compositionType: %#x%s%s", layer,
+ list->hwLayers[layer].compositionType,
+ (list->hwLayers[layer].compositionType == HWC_FRAMEBUFFER)
+ ? " FRAMEBUFFER" : "",
+ (list->hwLayers[layer].compositionType == HWC_OVERLAY)
+ ? " OVERLAY" : "");
+ testPrintI(" hints: %#x%s%s",
+ list->hwLayers[layer].hints,
+ (list->hwLayers[layer].hints & HWC_HINT_TRIPLE_BUFFER)
+ ? " TRIPLE_BUFFER" : "",
+ (list->hwLayers[layer].hints & HWC_HINT_CLEAR_FB)
+ ? " CLEAR_FB" : "");
+ }
+}
+
+/*
+ * Display List Handles
+ *
+ * Displays the handles of all the graphic buffers in the list.
+ */
+void displayListHandles(hwc_layer_list_t *list)
+{
+ const unsigned int maxLayersPerLine = 6;
+
+ ostringstream str(" layers:");
+ for (unsigned int layer = 0; layer < list->numHwLayers; layer++) {
+ str << ' ' << list->hwLayers[layer].handle;
+ if (((layer % maxLayersPerLine) == (maxLayersPerLine - 1))
+ && (layer != list->numHwLayers - 1)) {
+ testPrintI("%s", str.str().c_str());
+ str.str(" ");
+ }
+ }
+ testPrintI("%s", str.str().c_str());
+}
+
+const char *graphicFormat2str(unsigned int format)
+{
+ const static char *unknown = "unknown";
+
+ for (unsigned int n1 = 0; n1 < NUMA(graphicFormat); n1++) {
+ if (format == graphicFormat[n1].format) {
+ return graphicFormat[n1].desc;
+ }
+ }
+
+ return unknown;
+}
+
+/*
+ * Vector Random Select
+ *
+ * Prandomly selects and returns num elements from vec.
+ */
+template <class T>
+vector<T> vectorRandSelect(const vector<T>& vec, size_t num)
+{
+ vector<T> rv = vec;
+
+ while (rv.size() > num) {
+ rv.erase(rv.begin() + testRandMod(rv.size()));
+ }
+
+ return rv;
+}
+
+/*
+ * Vector Or
+ *
+ * Or's togethen the values of each element of vec and returns the result.
+ */
+template <class T>
+T vectorOr(const vector<T>& vec)
+{
+ T rv = 0;
+
+ for (size_t n1 = 0; n1 < vec.size(); n1++) {
+ rv |= vec[n1];
+ }
+
+ return rv;
+}
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml
index d4e9c53..758377b 100644
--- a/packages/SystemUI/res/layout-xlarge/status_bar.xml
+++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml
@@ -82,14 +82,24 @@
android:orientation="horizontal"
android:gravity="center"
>
- <ImageView
- android:id="@+id/network"
+ <FrameLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="19dp"
android:layout_marginRight="4dp"
- />
+ >
+ <ImageView
+ android:id="@+id/network_signal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ />
+ <ImageView
+ android:id="@+id/network_type"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ />
+ </FrameLayout>
<ImageView
android:id="@+id/battery"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel.xml b/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel.xml
index c0612c8..1d98458 100644
--- a/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel.xml
+++ b/packages/SystemUI/res/layout-xlarge/status_bar_notification_panel.xml
@@ -95,7 +95,17 @@
/>
<ImageView
- android:id="@+id/network"
+ android:id="@+id/network_signal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_toRightOf="@id/battery_text"
+ android:layout_alignBaseline="@id/battery"
+ android:layout_marginRight="8dp"
+ android:baseline="15dp"
+ />
+
+ <ImageView
+ android:id="@+id/network_type"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_toRightOf="@id/battery_text"
@@ -109,7 +119,7 @@
style="@style/StatusBarNotificationText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_toRightOf="@id/network"
+ android:layout_toRightOf="@id/network_signal"
android:layout_alignBaseline="@id/battery"
android:singleLine="true"
android:text="@string/status_bar_settings_settings_button"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DoNotDisturb.java b/packages/SystemUI/src/com/android/systemui/statusbar/DoNotDisturb.java
new file mode 100644
index 0000000..9e44e71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DoNotDisturb.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.app.StatusBarManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.systemui.statusbar.policy.Prefs;
+
+public class DoNotDisturb implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private Context mContext;
+ private StatusBarManager mStatusBar;
+ SharedPreferences mPrefs;
+ private boolean mDoNotDisturb;
+
+ public DoNotDisturb(Context context) {
+ mContext = context;
+ mStatusBar = (StatusBarManager)context.getSystemService(Context.STATUS_BAR_SERVICE);
+ mPrefs = Prefs.read(context);
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ mDoNotDisturb = mPrefs.getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT);
+ updateDisableRecord();
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ final boolean val = prefs.getBoolean(Prefs.DO_NOT_DISTURB_PREF,
+ Prefs.DO_NOT_DISTURB_DEFAULT);
+ if (val != mDoNotDisturb) {
+ mDoNotDisturb = val;
+ updateDisableRecord();
+ }
+ }
+
+ private void updateDisableRecord() {
+ final int disabled = StatusBarManager.DISABLE_NOTIFICATION_ICONS
+ | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
+ | StatusBarManager.DISABLE_NOTIFICATION_TICKER;
+ mStatusBar.disable(mDoNotDisturb ? disabled : 0);
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
index d7f3730..472a225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
@@ -53,6 +53,8 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac
protected abstract View makeStatusBarView();
protected abstract int getStatusBarGravity();
+ private DoNotDisturb mDoNotDisturb;
+
public void start() {
// First set up our views and stuff.
View sb = makeStatusBarView();
@@ -127,5 +129,7 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac
+ " imeButton=" + switches[3]
);
}
+
+ mDoNotDisturb = new DoNotDisturb(mContext);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DoNotDisturbController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DoNotDisturbController.java
new file mode 100644
index 0000000..94c8aa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DoNotDisturbController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.IWindowManager;
+import android.widget.CompoundButton;
+
+public class DoNotDisturbController implements CompoundButton.OnCheckedChangeListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = "StatusBar.DoNotDisturbController";
+
+ SharedPreferences mPrefs;
+ private Context mContext;
+ private CompoundButton mCheckBox;
+
+ private boolean mDoNotDisturb;
+
+ public DoNotDisturbController(Context context, CompoundButton checkbox) {
+ mContext = context;
+
+ mPrefs = Prefs.read(context);
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ mDoNotDisturb = mPrefs.getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT);
+
+ mCheckBox = checkbox;
+ checkbox.setOnCheckedChangeListener(this);
+
+ checkbox.setChecked(!mDoNotDisturb);
+ }
+
+ // The checkbox is ON for notifications coming in and OFF for Do not disturb, so we
+ // don't have a double negative.
+ public void onCheckedChanged(CompoundButton view, boolean checked) {
+ //Slog.d(TAG, "onCheckedChanged checked=" + checked + " mDoNotDisturb=" + mDoNotDisturb);
+ final boolean value = !checked;
+ if (value != mDoNotDisturb) {
+ SharedPreferences.Editor editor = Prefs.edit(mContext);
+ editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, value);
+ editor.apply();
+ }
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ final boolean val = prefs.getBoolean(Prefs.DO_NOT_DISTURB_PREF,
+ Prefs.DO_NOT_DISTURB_DEFAULT);
+ if (val != mDoNotDisturb) {
+ mDoNotDisturb = val;
+ mCheckBox.setChecked(!val);
+ }
+ }
+
+ public void release() {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
new file mode 100644
index 0000000..05eafe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class Prefs {
+ private static final String SHARED_PREFS_NAME = "status_bar";
+
+ // a boolean
+ public static final String DO_NOT_DISTURB_PREF = "do_not_disturb";
+ public static final boolean DO_NOT_DISTURB_DEFAULT = false;
+
+ public static SharedPreferences read(Context context) {
+ return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+ }
+
+ public static SharedPreferences.Editor edit(Context context) {
+ return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SettingsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SettingsView.java
index f9ba908..d1f8dd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SettingsView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SettingsView.java
@@ -31,12 +31,14 @@ import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.AirplaneModeController;
import com.android.systemui.statusbar.policy.AutoRotateController;
+import com.android.systemui.statusbar.policy.DoNotDisturbController;
public class SettingsView extends LinearLayout implements View.OnClickListener {
static final String TAG = "SettingsView";
AirplaneModeController mAirplane;
AutoRotateController mRotate;
+ DoNotDisturbController mDoNotDisturb;
public SettingsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -57,6 +59,8 @@ public class SettingsView extends LinearLayout implements View.OnClickListener {
findViewById(R.id.network).setOnClickListener(this);
mRotate = new AutoRotateController(context,
(CompoundButton)findViewById(R.id.rotate_checkbox));
+ mDoNotDisturb = new DoNotDisturbController(context,
+ (CompoundButton)findViewById(R.id.do_not_disturb_checkbox));
findViewById(R.id.settings).setOnClickListener(this);
}
@@ -64,6 +68,7 @@ public class SettingsView extends LinearLayout implements View.OnClickListener {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAirplane.release();
+ mDoNotDisturb.release();
}
public void onClick(View v) {
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 3cae088..c55c87e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -159,7 +159,9 @@ public class TabletStatusBar extends StatusBar {
mBatteryController.addLabelView(
(TextView)mNotificationPanel.findViewById(R.id.battery_text));
mNetworkController.addCombinedSignalIconView(
- (ImageView)mNotificationPanel.findViewById(R.id.network));
+ (ImageView)mNotificationPanel.findViewById(R.id.network_signal));
+ mNetworkController.addDataTypeIconView(
+ (ImageView)mNotificationPanel.findViewById(R.id.network_type));
mNetworkController.addLabelView(
(TextView)mNotificationPanel.findViewById(R.id.network_text));
@@ -283,7 +285,10 @@ public class TabletStatusBar extends StatusBar {
mBatteryController = new BatteryController(mContext);
mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
mNetworkController = new NetworkController(mContext);
- mNetworkController.addCombinedSignalIconView((ImageView)sb.findViewById(R.id.network));
+ mNetworkController.addCombinedSignalIconView(
+ (ImageView)sb.findViewById(R.id.network_signal));
+ mNetworkController.addDataTypeIconView(
+ (ImageView)sb.findViewById(R.id.network_type));
// The navigation buttons
mNavigationArea = sb.findViewById(R.id.navigationArea);
@@ -580,12 +585,12 @@ public class TabletStatusBar extends StatusBar {
if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
// synchronize with current shadow state
- mShadowController.hideElement(mNotificationArea);
+ mShadowController.hideElement(mNotificationIconArea);
mTicker.halt();
} else {
Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
// synchronize with current shadow state
- mShadowController.showElement(mNotificationArea);
+ mShadowController.showElement(mNotificationIconArea);
}
} else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 08f3c7a..b901e0d 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -366,13 +366,59 @@ public class Canvas_Delegate {
/*package*/ static void native_concat(int nCanvas, int nMatrix) {
- // FIXME
- throw new UnsupportedOperationException();
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ assert false;
+ return;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ assert false;
+ return;
+ }
+
+ // get the current top graphics2D object.
+ Graphics2D g = canvasDelegate.getGraphics2d();
+
+ // get its current matrix
+ AffineTransform currentTx = g.getTransform();
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+ // combine them so that the given matrix is applied after.
+ currentTx.preConcatenate(matrixTx);
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(currentTx);
}
/*package*/ static void native_setMatrix(int nCanvas, int nMatrix) {
- // FIXME
- throw new UnsupportedOperationException();
+ // get the delegate from the native int.
+ Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+ if (canvasDelegate == null) {
+ assert false;
+ }
+
+ Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+ if (matrixDelegate == null) {
+ assert false;
+ }
+
+ // get the current top graphics2D object.
+ Graphics2D g = canvasDelegate.getGraphics2d();
+
+ // get the AffineTransform of the given matrix
+ AffineTransform matrixTx = matrixDelegate.getAffineTransform();
+
+ // give it to the graphics2D as a new matrix replacing all previous transform
+ g.setTransform(matrixTx);
+
+ // FIXME: log
+// if (mLogger != null && matrixDelegate.hasPerspective()) {
+// mLogger.warning("android.graphics.Canvas#setMatrix(android.graphics.Matrix) only supports affine transformations in the Layout Editor.");
+// }
}
/*package*/ static boolean native_clipRect(int nCanvas,
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index 6e80268..b2333f6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -64,7 +64,7 @@ public final class Matrix_Delegate {
return null;
}
- return getAffineTransform(delegate);
+ return delegate.getAffineTransform();
}
public static boolean hasPerspective(Matrix m) {
@@ -74,7 +74,7 @@ public final class Matrix_Delegate {
return false;
}
- return (delegate.mValues[6] != 0 || delegate.mValues[7] != 0 || delegate.mValues[8] != 1);
+ return delegate.hasPerspective();
}
/**
@@ -106,6 +106,18 @@ public final class Matrix_Delegate {
return true;
}
+ /**
+ * Returns an {@link AffineTransform} matching the matrix.
+ */
+ public AffineTransform getAffineTransform() {
+ return getAffineTransform(mValues);
+ }
+
+ public boolean hasPerspective() {
+ return (mValues[6] != 0 || mValues[7] != 0 || mValues[8] != 1);
+ }
+
+
// ---- native methods ----
@@ -599,7 +611,7 @@ public final class Matrix_Delegate {
try {
- AffineTransform affineTransform = getAffineTransform(d);
+ AffineTransform affineTransform = d.getAffineTransform();
AffineTransform inverseTransform = affineTransform.createInverse();
inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
@@ -713,10 +725,6 @@ public final class Matrix_Delegate {
// ---- Private helper methods ----
- private static AffineTransform getAffineTransform(Matrix_Delegate d) {
- return getAffineTransform(d.mValues);
- }
-
/*package*/ static AffineTransform getAffineTransform(float[] matrix) {
// the AffineTransform constructor takes the value in a different order
// for a matrix [ 0 1 2 ]
diff --git a/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
new file mode 100644
index 0000000..4d4ec7f
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/Handler_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 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 android.os;
+
+
+/**
+ * Delegate overriding selected methods of android.os.Handler
+ *
+ * Through the layoutlib_create tool, selected methods of Handler have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Handler_Delegate {
+
+ // -------- Delegate methods
+
+ /*package*/ static boolean sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+ // get the callback
+ IHandlerCallback callback = sCallbacks.get();
+ if (callback != null) {
+ callback.sendMessageAtTime(handler, msg, uptimeMillis);
+ }
+ return true;
+ }
+
+ // -------- Delegate implementation
+
+ public interface IHandlerCallback {
+ void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis);
+ }
+
+ private final static ThreadLocal<IHandlerCallback> sCallbacks =
+ new ThreadLocal<IHandlerCallback>();
+
+ public static void setCallback(IHandlerCallback callback) {
+ sCallbacks.set(callback);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
new file mode 100644
index 0000000..be222fc
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 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 android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+
+/**
+ * Delegate implementing the native methods of android.os.SystemClock
+ *
+ * Through the layoutlib_create tool, the original native methods of SystemClock have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public class SystemClock_Delegate {
+ private static long sBootTime = System.currentTimeMillis();
+
+ /*package*/ static boolean setCurrentTimeMillis(long millis) {
+ return true;
+ }
+
+ /**
+ * Returns milliseconds since boot, not counting time spent in deep sleep.
+ * <b>Note:</b> This value may get reset occasionally (before it would
+ * otherwise wrap around).
+ *
+ * @return milliseconds of non-sleep uptime since boot.
+ */
+ /*package*/ static long uptimeMillis() {
+ return System.currentTimeMillis() - sBootTime;
+ }
+
+ /**
+ * Returns milliseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed milliseconds since boot.
+ */
+ /*package*/ static long elapsedRealtime() {
+ return System.currentTimeMillis() - sBootTime;
+ }
+
+ /**
+ * Returns milliseconds running in the current thread.
+ *
+ * @return elapsed milliseconds in the thread
+ */
+ /*package*/ static long currentThreadTimeMillis() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 35ba73d..2de1cbb 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -40,6 +40,7 @@ import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
/**
* Main entry point of the LayoutLib Bridge.
@@ -57,6 +58,12 @@ public final class Bridge extends LayoutBridge {
}
/**
+ * Lock to ensure only one rendering/inflating happens at a time.
+ * This is due to some singleton in the Android framework.
+ */
+ private final static ReentrantLock sLock = new ReentrantLock();
+
+ /**
* Maps from id to resource name/type. This is for android.R only.
*/
private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>();
@@ -160,7 +167,6 @@ public final class Bridge extends LayoutBridge {
BridgeAssetManager.initSystem();
-
// When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
// on static (native) methods which prints the signature on the console and
// throws an exception.
@@ -252,27 +258,6 @@ public final class Bridge extends LayoutBridge {
}
/**
- * Sets a 9 patch chunk in a project cache or in the framework cache.
- * @param value the path of the 9 patch
- * @param ninePatch the 9 patch object
- * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
- */
- public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
- if (map == null) {
- map = new HashMap<String, SoftReference<NinePatchChunk>>();
- sProject9PatchCache.put(projectKey, map);
- }
-
- map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
- } else {
- sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
- }
- }
-
- /**
* Starts a layout session by inflating and rendering it. The method returns a
* {@link ILayoutScene} on which further actions can be taken.
*
@@ -306,27 +291,25 @@ public final class Bridge extends LayoutBridge {
public BridgeLayoutScene createScene(SceneParams params) {
try {
SceneResult lastResult = SceneResult.SUCCESS;
- LayoutSceneImpl scene = null;
- synchronized (this) {
- try {
- scene = new LayoutSceneImpl(params);
-
- scene.prepare();
+ LayoutSceneImpl scene = new LayoutSceneImpl(params);
+ try {
+ scene.prepareThread();
+ lastResult = scene.init(params.getTimeout());
+ if (lastResult == SceneResult.SUCCESS) {
lastResult = scene.inflate();
if (lastResult == SceneResult.SUCCESS) {
lastResult = scene.render();
}
- } finally {
- if (scene != null) {
- scene.cleanup();
- }
}
+ } finally {
+ scene.release();
+ scene.cleanupThread();
}
- return new BridgeLayoutScene(this, scene, lastResult);
+ return new BridgeLayoutScene(scene, lastResult);
} catch (Throwable t) {
t.printStackTrace();
- return new BridgeLayoutScene(this, null, new SceneResult("error!", t));
+ return new BridgeLayoutScene(null, new SceneResult("error!", t));
}
}
@@ -343,6 +326,13 @@ public final class Bridge extends LayoutBridge {
}
/**
+ * Returns the lock for the bridge
+ */
+ public static ReentrantLock getLock() {
+ return sLock;
+ }
+
+ /**
* Returns details of a framework resource from its integer value.
* @param value the integer value
* @return an array of 2 strings containing the resource name and type, or null if the id
@@ -461,4 +451,27 @@ public final class Bridge extends LayoutBridge {
return null;
}
+
+ /**
+ * Sets a 9 patch chunk in a project cache or in the framework cache.
+ * @param value the path of the 9 patch
+ * @param ninePatch the 9 patch object
+ * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
+ */
+ public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
+ if (projectKey != null) {
+ Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
+
+ if (map == null) {
+ map = new HashMap<String, SoftReference<NinePatchChunk>>();
+ sProject9PatchCache.put(projectKey, map);
+ }
+
+ map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ } else {
+ sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
+ }
+ }
+
+
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java
index 8b67166..97bf857 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeLayoutScene.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge;
import com.android.layoutlib.api.LayoutScene;
+import com.android.layoutlib.api.SceneParams;
import com.android.layoutlib.api.SceneResult;
import com.android.layoutlib.api.ViewInfo;
import com.android.layoutlib.bridge.impl.LayoutSceneImpl;
@@ -33,7 +34,6 @@ import java.util.Map;
*/
public class BridgeLayoutScene extends LayoutScene {
- private final Bridge mBridge;
private final LayoutSceneImpl mScene;
private SceneResult mLastResult;
@@ -58,15 +58,34 @@ public class BridgeLayoutScene extends LayoutScene {
}
@Override
- public SceneResult render() {
-
- synchronized (mBridge) {
- try {
- mScene.prepare();
+ public SceneResult render(long timeout) {
+ try {
+ mScene.prepareThread();
+ mLastResult = mScene.acquire(timeout);
+ if (mLastResult == SceneResult.SUCCESS) {
mLastResult = mScene.render();
- } finally {
- mScene.cleanup();
}
+ } finally {
+ mScene.release();
+ mScene.cleanupThread();
+ }
+
+ return mLastResult;
+ }
+
+ @Override
+ public SceneResult animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ try {
+ mScene.prepareThread();
+ mLastResult = mScene.acquire(SceneParams.DEFAULT_TIMEOUT);
+ if (mLastResult == SceneResult.SUCCESS) {
+ mLastResult = mScene.animate(targetObject, animationName, isFrameworkAnimation,
+ listener);
+ }
+ } finally {
+ mScene.release();
+ mScene.cleanupThread();
}
return mLastResult;
@@ -78,8 +97,7 @@ public class BridgeLayoutScene extends LayoutScene {
}
- /*package*/ BridgeLayoutScene(Bridge bridge, LayoutSceneImpl scene, SceneResult lastResult) {
- mBridge = bridge;
+ /*package*/ BridgeLayoutScene(LayoutSceneImpl scene, SceneResult lastResult) {
mScene = scene;
mLastResult = lastResult;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
index 1011173..affd1c6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java
@@ -261,6 +261,41 @@ public final class BridgeResources extends Resources {
}
@Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ IResourceValue value = getResourceValue(id, mPlatformResourceFlag);
+
+ if (value != null) {
+ XmlPullParser parser = null;
+
+ try {
+ File xml = new File(value.getValue());
+ if (xml.isFile()) {
+ // we need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser
+ parser = new KXmlParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(new FileReader(xml));
+
+ return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ }
+ } catch (XmlPullParserException e) {
+ mContext.getLogger().error(e);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
+ }
+
+ }
+
+ // id was not found or not resolved. Throw a NotFoundException.
+ throwException(id);
+
+ // this is not used since the method above always throws
+ return null;
+ }
+
+
+ @Override
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
return mContext.obtainStyledAttributes(set, attrs);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java
new file mode 100644
index 0000000..c20bdfd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/AnimationThread.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 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.layoutlib.bridge.impl;
+
+import com.android.layoutlib.api.SceneResult;
+import com.android.layoutlib.api.LayoutScene.IAnimationListener;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.os.Handler;
+import android.os.Handler_Delegate;
+import android.os.Message;
+import android.os.Handler_Delegate.IHandlerCallback;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class AnimationThread extends Thread {
+
+ private static class MessageBundle {
+ final Handler mTarget;
+ final Message mMessage;
+ final long mUptimeMillis;
+
+ MessageBundle(Handler target, Message message, long uptimeMillis) {
+ mTarget = target;
+ mMessage = message;
+ mUptimeMillis = uptimeMillis;
+ }
+ }
+
+ private final LayoutSceneImpl mScene;
+ private final Animator mAnimator;
+
+ Queue<MessageBundle> mQueue = new LinkedList<MessageBundle>();
+ private final IAnimationListener mListener;
+
+ public AnimationThread(LayoutSceneImpl scene, Animator animator, IAnimationListener listener) {
+ mScene = scene;
+ mAnimator = animator;
+ mListener = listener;
+ }
+
+ @Override
+ public void run() {
+ mScene.prepareThread();
+ try {
+ Handler_Delegate.setCallback(new IHandlerCallback() {
+ public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+ if (msg.what == ValueAnimator.ANIMATION_START ||
+ msg.what == ValueAnimator.ANIMATION_FRAME) {
+ mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
+ } else {
+ // just ignore.
+ }
+ }
+ });
+
+ // start the animation. This will send a message to the handler right away, so
+ // mQueue is filled when this method returns.
+ mAnimator.start();
+
+ // loop the animation
+ do {
+ // get the next message.
+ MessageBundle bundle = mQueue.poll();
+ if (bundle == null) {
+ break;
+ }
+
+ // sleep enough for this bundle to be on time
+ long currentTime = System.currentTimeMillis();
+ if (currentTime < bundle.mUptimeMillis) {
+ try {
+ sleep(bundle.mUptimeMillis - currentTime);
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ // ready to do the work, acquire the scene.
+ SceneResult result = mScene.acquire(250);
+ if (result != SceneResult.SUCCESS) {
+ mListener.done(result);
+ return;
+ }
+
+ // process the bundle. If the animation is not finished, this will enqueue
+ // the next message, so mQueue will have another one.
+ try {
+ bundle.mTarget.handleMessage(bundle.mMessage);
+ if (mScene.render() == SceneResult.SUCCESS) {
+ mListener.onNewFrame(mScene.getImage());
+ }
+ } finally {
+ mScene.release();
+ }
+ } while (mQueue.size() > 0);
+
+ mListener.done(SceneResult.SUCCESS);
+ } finally {
+ Handler_Delegate.setCallback(null);
+ mScene.cleanupThread();
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
index f7d249e..0859976 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
@@ -16,6 +16,10 @@
package com.android.layoutlib.bridge.impl;
+import static com.android.layoutlib.api.SceneResult.SceneStatus.ERROR_LOCK_INTERRUPTED;
+import static com.android.layoutlib.api.SceneResult.SceneStatus.ERROR_TIMEOUT;
+import static com.android.layoutlib.api.SceneResult.SceneStatus.SUCCESS;
+
import com.android.internal.util.XmlUtils;
import com.android.layoutlib.api.IProjectCallback;
import com.android.layoutlib.api.IResourceValue;
@@ -25,7 +29,9 @@ import com.android.layoutlib.api.SceneParams;
import com.android.layoutlib.api.SceneResult;
import com.android.layoutlib.api.ViewInfo;
import com.android.layoutlib.api.IDensityBasedResourceValue.Density;
+import com.android.layoutlib.api.LayoutScene.IAnimationListener;
import com.android.layoutlib.api.SceneParams.RenderingMode;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeInflater;
@@ -33,6 +39,8 @@ import com.android.layoutlib.bridge.android.BridgeWindow;
import com.android.layoutlib.bridge.android.BridgeWindowSession;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
import android.app.Fragment_Delegate;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -59,6 +67,8 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
/**
* Class managing a layout "scene".
@@ -73,6 +83,12 @@ public class LayoutSceneImpl {
private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
+ /**
+ * The current context being rendered. This is set through {@link #acquire(long)} and
+ * {@link #init(long)}, and unset in {@link #release()}.
+ */
+ private static BridgeContext sCurrentContext = null;
+
private final SceneParams mParams;
// scene state
@@ -98,22 +114,35 @@ public class LayoutSceneImpl {
/**
* Creates a layout scene with all the information coming from the layout bridge API.
- *
- * This also calls {@link LayoutSceneImpl#prepare()}.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * This <b>must</b> be followed by a call to {@link LayoutSceneImpl#init()}, which act as a
+ * call to {@link LayoutSceneImpl#acquire(long)}
*
* @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams)
*/
public LayoutSceneImpl(SceneParams params) {
- // we need to make sure the Looper has been initialized for this thread.
- // this is required for View that creates Handler objects.
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
// copy the params.
mParams = new SceneParams(params);
+ }
+
+ /**
+ * Initializes and acquires the scene, creating various Android objects such as context,
+ * inflater, and parser.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #acquire(long)
+ * @see #release()
+ */
+ public SceneResult init(long timeout) {
+ // acquire the lock. if the result is null, lock was just acquired, otherwise, return
+ // the result.
+ SceneResult result = acquireLock(timeout);
+ if (result != null) {
+ return result;
+ }
// setup the display Metrics.
DisplayMetrics metrics = new DisplayMetrics();
@@ -138,6 +167,9 @@ public class LayoutSceneImpl {
mParams.getProjectResources(), mParams.getFrameworkResources(),
styleParentMap, mParams.getProjectCallback(), mParams.getLogger());
+ // set the current rendering context
+ sCurrentContext = mContext;
+
// make sure the Resources object references the context (and other objects) for this
// scene
mContext.initResources();
@@ -149,7 +181,8 @@ public class LayoutSceneImpl {
mWindowBackground = mContext.findItemInStyle(mCurrentTheme, "windowBackground");
mWindowBackground = mContext.resolveResValue(mWindowBackground);
- mScreenOffset = getScreenOffset(mParams.getFrameworkResources(), mCurrentTheme, mContext);
+ mScreenOffset = getScreenOffset(mParams.getFrameworkResources(), mCurrentTheme,
+ mContext);
}
// build the inflater and parser.
@@ -159,44 +192,144 @@ public class LayoutSceneImpl {
mBlockParser = new BridgeXmlBlockParser(mParams.getLayoutDescription(),
mContext, false /* platformResourceFlag */);
+
+ return SceneResult.SUCCESS;
}
/**
- * Prepares the scene for action.
- * <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * Prepares the current thread for rendering.
+ *
+ * Note that while this can be called several time, the first call to {@link #cleanupThread()}
+ * will do the clean-up, and make the thread unable to do further scene actions.
*/
- public void prepare() {
+ public void prepareThread() {
// we need to make sure the Looper has been initialized for this thread.
// this is required for View that creates Handler objects.
if (Looper.myLooper() == null) {
Looper.prepare();
}
+ }
+
+ /**
+ * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
+ * <p>
+ * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
+ * call to this will prevent the thread from doing further scene actions
+ */
+ public void cleanupThread() {
+ // clean up the looper
+ Looper.sThreadLocal.remove();
+ }
+
+ /**
+ * Prepares the scene for action.
+ * <p>
+ * This call is blocking if another rendering/inflating is currently happening, and will return
+ * whether the preparation worked.
+ *
+ * The preparation can fail if another rendering took too long and the timeout was elapsed.
+ *
+ * More than one call to this from the same thread will have no effect and will return
+ * {@link SceneResult#SUCCESS}.
+ *
+ * After scene actions have taken place, only one call to {@link #release()} must be
+ * done.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ *
+ * @return whether the scene was prepared
+ *
+ * @see #release()
+ *
+ * @throws IllegalStateException if {@link #init(long)} was never called.
+ */
+ public SceneResult acquire(long timeout) {
+ if (mContext == null) {
+ throw new IllegalStateException("After scene creation, #init() must be called");
+ }
+
+ // acquire the lock. if the result is null, lock was just acquired, otherwise, return
+ // the result.
+ SceneResult result = acquireLock(timeout);
+ if (result != null) {
+ return result;
+ }
// make sure the Resources object references the context (and other objects) for this
// scene
mContext.initResources();
+ sCurrentContext = mContext;
+
+ return SUCCESS.getResult();
}
/**
- * Cleans up the scene after an action.
+ * Acquire the lock so that the scene can be acted upon.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * This returns null if the lock was just acquired, otherwise it returns
+ * {@link SceneResult#SUCCESS} if the lock already belonged to that thread, or another
+ * instance (see {@link SceneResult#getStatus()}) if an error occurred.
+ *
+ * @param timeout the time to wait if another rendering is happening.
+ * @return null if the lock was just acquire or another result depending on the state.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene.
*/
- public void cleanup() {
- // clean up the looper
- Looper.sThreadLocal.remove();
+ private SceneResult acquireLock(long timeout) {
+ ReentrantLock lock = Bridge.getLock();
+ if (lock.isHeldByCurrentThread() == false) {
+ try {
+ boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
+
+ if (acquired == false) {
+ return ERROR_TIMEOUT.getResult();
+ }
+ } catch (InterruptedException e) {
+ return ERROR_LOCK_INTERRUPTED.getResult();
+ }
+ } else {
+ // This thread holds the lock already. Checks that this wasn't for a different context.
+ // If this is called by init, mContext will be null and so should sCurrentContext
+ // anyway
+ if (mContext != sCurrentContext) {
+ throw new IllegalStateException("Acquiring different scenes from same thread without releases");
+ }
+ return SUCCESS.getResult();
+ }
- // Make sure to remove static references, otherwise we could not unload the lib
- mContext.disposeResources();
+ return null;
+ }
+
+ /**
+ * Cleans up the scene after an action.
+ */
+ public void release() {
+ ReentrantLock lock = Bridge.getLock();
+
+ // with the use of finally blocks, it is possible to find ourself calling this
+ // without a successful call to prepareScene. This test makes sure that unlock() will
+ // not throw IllegalMonitorStateException.
+ if (lock.isHeldByCurrentThread()) {
+ // Make sure to remove static references, otherwise we could not unload the lib
+ mContext.disposeResources();
+ sCurrentContext = null;
+
+ lock.unlock();
+ }
}
/**
* Inflates the layout.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #init(long)} was not called.
*/
public SceneResult inflate() {
+ checkLock();
+
try {
mViewRoot = new FrameLayout(mContext);
@@ -247,10 +380,16 @@ public class LayoutSceneImpl {
/**
* Renders the scene.
* <p>
- * <b>THIS MUST BE INSIDE A SYNCHRONIZED BLOCK on the BRIDGE OBJECT.<b>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
*/
public SceneResult render() {
+ checkLock();
+
try {
+ long current = System.currentTimeMillis();
if (mViewRoot == null) {
return new SceneResult("Layout has not been inflated!");
}
@@ -329,6 +468,8 @@ public class LayoutSceneImpl {
mViewInfo = visit(((ViewGroup)mViewRoot).getChildAt(0), mContext);
+ System.out.println("rendering (ms): " + (System.currentTimeMillis() - current));
+
// success!
return SceneResult.SUCCESS;
} catch (Throwable e) {
@@ -346,6 +487,71 @@ public class LayoutSceneImpl {
}
/**
+ * Animate an object
+ * <p>
+ * {@link #acquire(long)} must have been called before this.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ */
+ public SceneResult animate(Object targetObject, String animationName,
+ boolean isFrameworkAnimation, IAnimationListener listener) {
+ checkLock();
+
+ // find the animation file.
+ IResourceValue animationResource = null;
+ int animationId = 0;
+ if (isFrameworkAnimation) {
+ animationResource = mContext.getFrameworkResource("anim", animationName);
+ if (animationResource != null) {
+ animationId = Bridge.getResourceValue("anim", animationName);
+ }
+ } else {
+ animationResource = mContext.getProjectResource("anim", animationName);
+ if (animationResource != null) {
+ animationId = mContext.getProjectCallback().getResourceValue("anim", animationName);
+ }
+ }
+
+ if (animationResource != null) {
+ try {
+ ObjectAnimator anim = (ObjectAnimator) AnimatorInflater.loadAnimator(
+ mContext, animationId);
+ if (anim != null) {
+ anim.setTarget(targetObject);
+
+ new AnimationThread(this, anim, listener).start();
+
+ return SceneResult.SUCCESS;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return new SceneResult("", e);
+ }
+ }
+
+ return new SceneResult("Failed to find animation");
+ }
+
+ /**
+ * Checks that the lock is owned by the current thread and that the current context is the one
+ * from this scene.
+ *
+ * @throws IllegalStateException if the current context is different than the one owned by
+ * the scene, or if {@link #acquire(long)} was not called.
+ */
+ private void checkLock() {
+ ReentrantLock lock = Bridge.getLock();
+ if (lock.isHeldByCurrentThread() == false) {
+ throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
+ }
+ if (sCurrentContext != mContext) {
+ throw new IllegalStateException("Thread acquired a scene but is rendering a different one");
+ }
+ }
+
+
+ /**
* Compute style information from the given list of style for the project and framework.
* @param themeName the name of the current theme. In order to differentiate project and
* platform themes sharing the same name, all project themes must be prepended with
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index bb2e6b3..4440685 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -95,6 +95,7 @@ public final class CreateInfo implements ICreateInfo {
*/
private final static String[] DELEGATE_METHODS = new String[] {
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
+ "android.os.Handler#sendMessageAtTime",
"android.view.View#isInEditMode",
// TODO: comment out once DelegateClass is working
// "android.content.res.Resources$Theme#obtainStyledAttributes",
@@ -118,6 +119,7 @@ public final class CreateInfo implements ICreateInfo {
"android.graphics.SweepGradient",
"android.graphics.Typeface",
"android.graphics.Xfermode",
+ "android.os.SystemClock",
"android.util.FloatMath",
};