diff options
45 files changed, 13004 insertions, 54 deletions
diff --git a/res/layout/disposition_view.xml b/res/layout/disposition_view.xml new file mode 100644 index 0000000..a26b9a0 --- /dev/null +++ b/res/layout/disposition_view.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2013 The CyanogenMod 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. +--> +<org.cyanogenmod.wallpapers.photophase.widgets.DispositionView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/disposition_frame_margin" />
\ No newline at end of file diff --git a/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java b/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java new file mode 100644 index 0000000..e878756 --- /dev/null +++ b/src/afzkl/development/mColorPicker/drawables/AlphaPatternDrawable.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 afzkl.development.mColorPicker.drawables; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +/** + * This drawable that draws a simple white and gray chessboard pattern. + * It's pattern you will often see as a background behind a + * partly transparent image in many applications. + * @author Daniel Nilsson + */ +@SuppressWarnings("all") +public class AlphaPatternDrawable extends Drawable { + + private int mRectangleSize = 10; + + private final Paint mPaint = new Paint(); + private final Paint mPaintWhite = new Paint(); + private final Paint mPaintGray = new Paint(); + + private int numRectanglesHorizontal; + private int numRectanglesVertical; + + /** + * Bitmap in which the pattern will be cahched. + */ + private Bitmap mBitmap; + + public AlphaPatternDrawable(int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawwable."); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawwable."); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + int height = bounds.height(); + int width = bounds.width(); + + numRectanglesHorizontal = (int) Math.ceil((width / mRectangleSize)); + numRectanglesVertical = (int) Math.ceil(height / mRectangleSize); + + generatePatternBitmap(); + + } + + /** + * This will generate a bitmap with the pattern + * as big as the rectangle we were allow to draw on. + * We do this to chache the bitmap so we don't need to + * recreate it each time draw() is called since it + * takes a few milliseconds. + */ + private void generatePatternBitmap() { + + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } + + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(mBitmap); + + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + + r.top = i * mRectangleSize; + r.left = j * mRectangleSize; + r.bottom = r.top + mRectangleSize; + r.right = r.left + mRectangleSize; + + canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); + + isWhite = !isWhite; + } + + verticalStartWhite = !verticalStartWhite; + + } + + } + +} diff --git a/src/afzkl/development/mColorPicker/views/ColorDialogView.java b/src/afzkl/development/mColorPicker/views/ColorDialogView.java new file mode 100644 index 0000000..ad0b9cf --- /dev/null +++ b/src/afzkl/development/mColorPicker/views/ColorDialogView.java @@ -0,0 +1,517 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 afzkl.development.mColorPicker.views; + +import afzkl.development.mColorPicker.views.ColorPickerView.OnColorChangedListener; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.text.Editable; +import android.text.InputFilter; +import android.text.InputType; +import android.text.Spanned; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A view use directly into a dialog. It contains a one {@link ColorPickerView} + * and two {@link ColorPanelView} (the current color and the new color) + */ +public class ColorDialogView extends RelativeLayout + implements OnColorChangedListener, TextWatcher { + + private static final int DEFAULT_MARGIN_DP = 16; + private static final int DEFAULT_PANEL_HEIGHT_DP = 32; + private static final int DEFAULT_TEXT_SIZE_SP = 12; + private static final int DEFAULT_LABEL_TEXT_SIZE_SP = 18; + + private ColorPickerView mPickerView; + private ColorPanelView mCurrentColorView; + private ColorPanelView mNewColorView; + private TextView tvCurrent; + private TextView tvNew; + private TextView tvColorLabel; + private EditText etColor; + + private String mCurrentLabelText = "Current:"; //$NON-NLS-1$ + private String mNewLabelText = "New:"; //$NON-NLS-1$ + + private String mColorLabelText = "Color:"; //$NON-NLS-1$ + + private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); + + /** + * Constructor of <code>ColorDialogView</code> + * + * @param context The current context + */ + public ColorDialogView(Context context) { + this(context, null); + } + + /** + * Constructor of <code>ColorDialogView</code> + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + */ + public ColorDialogView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructor of <code>ColorDialogView</code> + * + * @param context The current context + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + */ + public ColorDialogView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Method that initializes the view. This method loads all the necessary + * information and create an appropriate layout for the view + */ + private void init() { + // To fight color branding. + ((Activity)getContext()).getWindow().setFormat(PixelFormat.RGBA_8888); + + // Create the scrollview over the dialog + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + ScrollView sv = new ScrollView(getContext()); + sv.setId(internalGenerateViewId()); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, 0); + sv.setLayoutParams(lp); + sv.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); + + // Now the vertical layout + LinearLayout ll = new LinearLayout(getContext()); + ll.setId(internalGenerateViewId()); + lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT); + ll.setLayoutParams(lp); + ll.setOrientation(LinearLayout.VERTICAL); + sv.addView(ll); + + // Creates the color input field + int id = createColorInput(ll); + + // Creates the color picker + id = createColorPicker(ll, id); + + // Creates the current color and new color panels + id = createColorsPanel(ll, id); + + // Add the scrollview + addView(sv); + + // Sets the input color + this.etColor.setText(toHex(this.mNewColorView.getColor())); + } + + /** + * Method that creates the color input + * + * @param parent The parent layout + */ + private int createColorInput(ViewGroup parent) { + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT); + lp2.setMargins(0, 0, dlgMarging, 0); + this.tvColorLabel = new TextView(getContext()); + this.tvColorLabel.setText(this.mColorLabelText); + this.tvColorLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_LABEL_TEXT_SIZE_SP); + this.tvColorLabel.setGravity(Gravity.BOTTOM | Gravity.LEFT); + this.tvColorLabel.setLayoutParams(lp2); + + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + this.etColor = new EditText(getContext()); + this.etColor.setSingleLine(); + this.etColor.setGravity(Gravity.TOP | Gravity.LEFT); + this.etColor.setCursorVisible(true); + this.etColor.setImeOptions( + EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN); + this.etColor.setInputType( + InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + this.etColor.setLayoutParams(lp2); + InputFilter[] filters = new InputFilter[2]; + filters[0] = new InputFilter.LengthFilter(8); + filters[1] = new InputFilter() { + @Override + public CharSequence filter(CharSequence source, int start, + int end, Spanned dest, int dstart, int dend) { + if (start >= end) return ""; //$NON-NLS-1$ + String s = source.subSequence(start, end).toString(); + StringBuilder sb = new StringBuilder(); + int cc = s.length(); + for (int i = 0; i < cc; i++) { + char c = s.charAt(i); + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F')) { + sb.append(c); + } + } + return sb.toString().toUpperCase(Locale.getDefault()); + } + }; + this.etColor.setFilters(filters); + this.etColor.addTextChangedListener(this); + + LinearLayout ll1 = new LinearLayout(getContext()); + ll1.setId(internalGenerateViewId()); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, 0); + lp.addRule(RelativeLayout.ALIGN_PARENT_TOP); + ll1.setLayoutParams(lp); + ll1.addView(this.tvColorLabel); + ll1.addView(this.etColor); + parent.addView(ll1); + + return ll1.getId(); + } + + /** + * Method that creates the color picker + * + * @param parent The parent layout + * @param belowOf The anchor view + * @return id The layout id + */ + private int createColorPicker(ViewGroup parent, int belowOf) { + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + this.mPickerView = new ColorPickerView(getContext()); + this.mPickerView.setId(internalGenerateViewId()); + this.mPickerView.setOnColorChangedListener(this); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, 0); + lp.addRule(RelativeLayout.BELOW, belowOf); + this.mPickerView.setLayoutParams(lp); + parent.addView(this.mPickerView); + return this.mPickerView.getId(); + } + + /** + * Method that creates the colors panel (current and new) + * + * @param parent The parent layout + * @param belowOf The anchor view + * @return id The layout id + */ + private int createColorsPanel(ViewGroup parent, int belowOf) { + final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP); + final int panelHeight = (int)convertDpToPixel(DEFAULT_PANEL_HEIGHT_DP); + LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + 1); + + // Titles + this.tvCurrent = new TextView(getContext()); + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 1); + this.tvCurrent.setLayoutParams(lp2); + this.tvCurrent.setText(this.mCurrentLabelText); + this.tvCurrent.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + this.tvNew = new TextView(getContext()); + this.tvNew.setLayoutParams(lp2); + this.tvNew.setText(this.mNewLabelText); + this.tvNew.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + TextView sep1 = new TextView(getContext()); + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 0); + lp2.setMargins(dlgMarging, 0, dlgMarging, 0); + sep1.setLayoutParams(lp2); + sep1.setText(" "); //$NON-NLS-1$ + sep1.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + + LinearLayout ll1 = new LinearLayout(getContext()); + ll1.setId(internalGenerateViewId()); + RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2); + lp.addRule(RelativeLayout.BELOW, belowOf); + ll1.setLayoutParams(lp); + ll1.addView(this.tvCurrent); + ll1.addView(sep1); + ll1.addView(this.tvNew); + parent.addView(ll1); + + // Color panels + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 1); + this.mCurrentColorView = new ColorPanelView(getContext()); + this.mCurrentColorView.setLayoutParams(lp2); + this.mNewColorView = new ColorPanelView(getContext()); + this.mNewColorView.setLayoutParams(lp2); + TextView sep2 = new TextView(getContext()); + lp2 = new LinearLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + android.view.ViewGroup.LayoutParams.WRAP_CONTENT, + 0); + lp2.setMargins(dlgMarging, 0, dlgMarging, 0); + sep2.setLayoutParams(lp2); + sep2.setText("-"); //$NON-NLS-1$ + sep2.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP); + + LinearLayout ll2 = new LinearLayout(getContext()); + ll2.setId(internalGenerateViewId()); + lp = new RelativeLayout.LayoutParams( + android.view.ViewGroup.LayoutParams.MATCH_PARENT, panelHeight); + lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2); + lp.addRule(RelativeLayout.BELOW, ll1.getId()); + ll2.setLayoutParams(lp); + ll2.addView(this.mCurrentColorView); + ll2.addView(sep2); + ll2.addView(this.mNewColorView); + parent.addView(ll2); + + return ll2.getId(); + } + + /** + * Method that returns the color of the picker + * + * @return The ARGB color + */ + public int getColor() { + return this.mPickerView.getColor(); + } + + /** + * Method that set the color of the picker + * + * @param argb The ARGB color + */ + public void setColor(int argb) { + setColor(argb, false); + } + + /** + * Method that set the color of the picker + * + * @param argb The ARGB color + * @param fromEditText If the call comes from the <code>EditText</code> + */ + private void setColor(int argb, boolean fromEditText) { + this.mPickerView.setColor(argb, false); + this.mCurrentColorView.setColor(argb); + this.mNewColorView.setColor(argb); + if (!fromEditText) { + this.etColor.setText(toHex(this.mNewColorView.getColor())); + } + } + + /** + * Method that display/hide the alpha slider + * + * @param show If the alpha slider should be shown + */ + public void showAlphaSlider(boolean show) { + this.mPickerView.setAlphaSliderVisible(show); + } + + /** + * Set the text that should be shown in the alpha slider. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text) { + this.mPickerView.setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the actual color panel. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setCurrentColorText(String text) { + this.mCurrentLabelText = text; + this.tvCurrent.setText(this.mCurrentLabelText); + } + + /** + * Set the text that should be shown in the new color panel. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setNewColorText(String text) { + this.mNewLabelText = text; + this.tvNew.setText(this.mNewLabelText); + } + + /** + * Set the text that should be shown in the label of the color input. + * Set to null to disable text. + * + * @param text Text that should be shown. + */ + public void setColorLabelText(String text) { + this.mColorLabelText = text; + this.tvColorLabel.setText(this.mColorLabelText); + } + + /** + * {@inheritDoc} + */ + @Override + public void onColorChanged(int color) { + this.mNewColorView.setColor(color); + this.etColor.removeTextChangedListener(this); + this.etColor.setText(toHex(this.mNewColorView.getColor())); + this.etColor.addTextChangedListener(this); + } + + /** + * {@inheritDoc} + */ + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {/**NON BLOCK**/} + + /** + * {@inheritDoc} + */ + @Override + public void afterTextChanged(Editable s) { + if (s.length() == 8) { + try { + setColor(toARGB(s.toString()), true); + } catch (Exception e) {/**NON BLOCK**/} + } + } + + /** + * This method converts dp unit to equivalent device specific value in pixels. + * + * @param ctx The current context + * @param dp A value in dp (Device independent pixels) unit + * @return float A float value to represent Pixels equivalent to dp according to device + */ + private float convertDpToPixel(float dp) { + Resources resources = getContext().getResources(); + DisplayMetrics metrics = resources.getDisplayMetrics(); + return dp * (metrics.densityDpi / 160f); + } + + /** + * Method that converts an ARGB color to its hex string color representation + * + * @param argb The ARGB color + * @return String The hex string representation of the color + */ + private static String toHex(int argb) { + StringBuilder sb = new StringBuilder(); + sb.append(toHexString((byte)Color.alpha(argb))); + sb.append(toHexString((byte)Color.red(argb))); + sb.append(toHexString((byte)Color.green(argb))); + sb.append(toHexString((byte)Color.blue(argb))); + return sb.toString(); + } + + /** + * Method that converts an hex string color representation to an ARGB color + * + * @param hex The hex string representation of the color + * @return int The ARGB color + */ + private static int toARGB(String hex) { + return Color.parseColor("#" + hex); //$NON-NLS-1$ + } + + /** + * Method that converts a byte into its hex string representation + * + * @param v The value to convert + * @return String The hex string representation + */ + private static String toHexString(byte v) { + String hex = Integer.toHexString(v & 0xff); + if (hex.length() == 1) { + hex = "0" + hex; //$NON-NLS-1$ + } + return hex.toUpperCase(Locale.getDefault()); + } + + /** + * Generate a value suitable for use in {@link #setId(int)}. + * This value will not collide with ID values generated at build time by aapt for R.id. + * + * @return a generated ID value + */ + private static int internalGenerateViewId() { + for (;;) { + final int result = sNextGeneratedId.get(); + // aapt-generated IDs have the high byte nonzero; clamp to the range under that. + int newValue = result + 1; + if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. + if (sNextGeneratedId.compareAndSet(result, newValue)) { + return result; + } + } + } +} diff --git a/src/afzkl/development/mColorPicker/views/ColorPanelView.java b/src/afzkl/development/mColorPicker/views/ColorPanelView.java new file mode 100644 index 0000000..9764ff1 --- /dev/null +++ b/src/afzkl/development/mColorPicker/views/ColorPanelView.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 afzkl.development.mColorPicker.views; + +import afzkl.development.mColorPicker.drawables.AlphaPatternDrawable; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; + +/** + * This class draws a panel which which will be filled with a color which can be set. + * It can be used to show the currently selected color which you will get from + * the {@link ColorPickerView}. + * @author Daniel Nilsson + * + */ +@SuppressWarnings("all") +public class ColorPanelView extends View{ + + /** + * The width in pixels of the border + * surrounding the color panel. + */ + private final static float BORDER_WIDTH_PX = 1; + + private static float mDensity = 1f; + + private int mBorderColor = 0xff6E6E6E; + private int mColor = 0xff000000; + + private Paint mBorderPaint; + private Paint mColorPaint; + + private RectF mDrawingRect; + private RectF mColorRect; + + private AlphaPatternDrawable mAlphaPattern; + + + public ColorPanelView(Context context) { + this(context, null); + } + + public ColorPanelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + init(); + } + + private void init() { + mBorderPaint = new Paint(); + mColorPaint = new Paint(); + mDensity = getContext().getResources().getDisplayMetrics().density; + } + + + @Override + protected void onDraw(Canvas canvas) { + + final RectF rect = mColorRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect, mBorderPaint); + } + + if (mAlphaPattern != null) { + mAlphaPattern.draw(canvas); + } + + mColorPaint.setColor(mColor); + + canvas.drawRect(rect, mColorPaint); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = getPaddingLeft(); + mDrawingRect.right = w - getPaddingRight(); + mDrawingRect.top = getPaddingTop(); + mDrawingRect.bottom = h - getPaddingBottom(); + + setUpColorRect(); + + } + + private void setUpColorRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mColorRect = new RectF(left,top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity)); + + mAlphaPattern.setBounds(Math.round(mColorRect.left), + Math.round(mColorRect.top), + Math.round(mColorRect.right), + Math.round(mColorRect.bottom)); + + } + + /** + * Set the color that should be shown by this view. + * @param color + */ + public void setColor(int color) { + mColor = color; + invalidate(); + } + + /** + * Get the color currently show by this view. + * @return + */ + public int getColor() { + return mColor; + } + + /** + * Set the color of the border surrounding the panel. + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding the panel. + */ + public int getBorderColor() { + return mBorderColor; + } + +} diff --git a/src/afzkl/development/mColorPicker/views/ColorPickerView.java b/src/afzkl/development/mColorPicker/views/ColorPickerView.java new file mode 100644 index 0000000..fd901c5 --- /dev/null +++ b/src/afzkl/development/mColorPicker/views/ColorPickerView.java @@ -0,0 +1,998 @@ +/* + * Copyright (C) 2010 Daniel Nilsson + * + * 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 afzkl.development.mColorPicker.views; + +import afzkl.development.mColorPicker.drawables.AlphaPatternDrawable; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Shader.TileMode; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import java.lang.reflect.Method; + +/** + * Displays a color picker to the user and allow them + * to select a color. A slider for the alpha channel is + * also available. Enable it by setting + * setAlphaSliderVisible(boolean) to true. + * @author Daniel Nilsson + */ +@SuppressWarnings("all") +public class ColorPickerView extends View{ + + public interface OnColorChangedListener{ + public void onColorChanged(int color); + } + + private final static int PANEL_SAT_VAL = 0; + private final static int PANEL_HUE = 1; + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border + * surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + /** + * The distance in dp between the different + * color panels. + */ + private float PANEL_SPACING = 10f; + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + /** + * The dp which the tracker of the hue or alpha panel + * will extend outside of its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + + private static float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + private Shader mSatShader; + private Shader mHueShader; + private Shader mAlphaShader; + + private int mAlpha = 0xff; + private float mHue = 360f; + private float mSat = 0f; + private float mVal = 0f; + + private String mAlphaSliderText = "Alpha"; + private int mSliderTrackerColor = 0xff1c1c1c; + private int mBorderColor = 0xff6E6E6E; + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when + * processing hardware button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else + * the finger tracker will get clipped when + * it is drawn outside of the view. + */ + private float mDrawingOffset; + + + /* + * Distance form the edges of the view + * of where we are allowed to draw. + */ + private RectF mDrawingRect; + + private RectF mSatValRect; + private RectF mHueRect; + private RectF mAlphaRect; + + private AlphaPatternDrawable mAlphaPattern; + + private Point mStartTouchPoint = null; + + + public ColorPickerView(Context context) { + this(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + + mDrawingOffset = calculateRequiredOffset(); + + initPaintTools(); + + //Needed for receiving trackball motion events. + setFocusable(true); + setFocusableInTouchMode(true); + } + + private void initPaintTools() { + + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); + + + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); + + mHueTrackerPaint.setColor(mSliderTrackerColor); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); + + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); + + + } + + private float calculateRequiredOffset() { + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + + return offset * 1.5f; + } + + private int[] buildHueColorArray() { + + int[] hue = new int[361]; + + int count = 0; + for (int i = hue.length -1; i >= 0; i--, count++) { + hue[count] = Color.HSVToColor(new float[]{i, 1f, 1f}); + } + + return hue; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + checkHardwareAccelerationSupport(); + } + + @Override + protected void onDraw(Canvas canvas) { + + if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) return; + + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + + } + + private void drawSatValPanel(Canvas canvas) { + + final RectF rect = mSatValRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } + + int rgb = Color.HSVToColor(new float[]{mHue,1f,1f}); + + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + 0xffffffff, rgb, TileMode.CLAMP); + ComposeShader mShader = new ComposeShader(mValShader, mSatShader, PorterDuff.Mode.MULTIPLY); + mSatValPaint.setShader(mShader); + + canvas.drawRect(rect, mSatValPaint); + + Point p = satValToPoint(mSat, mVal); + + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, mSatValTrackerPaint); + + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + + } + + private void drawHuePanel(Canvas canvas) { + + final RectF rect = mHueRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } + + canvas.drawRect(rect, mHuePaint); + + float rectHeight = 4 * mDensity / 2; + + Point p = hueToPoint(mHue); + + RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; + + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private void drawAlphaPanel(Canvas canvas) { + + if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) return; + + final RectF rect = mAlphaRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + + mAlphaPattern.draw(canvas); + + float[] hsv = new float[]{mHue,mSat,mVal}; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); + + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); + + + mAlphaPaint.setShader(mAlphaShader); + + canvas.drawRect(rect, mAlphaPaint); + + if (mAlphaSliderText != null && mAlphaSliderText!= "") { + canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, mAlphaTextPaint); + } + + float rectWidth = 4 * mDensity / 2; + + Point p = alphaToPoint(mAlpha); + + RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + + private Point hueToPoint(float hue) { + + final RectF rect = mHueRect; + final float height = rect.height(); + + Point p = new Point(); + + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = (int) rect.left; + + return p; + } + + private Point satValToPoint(float sat, float val) { + + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(int alpha) { + + final RectF rect = mAlphaRect; + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = (int) rect.top; + + return p; + + } + + private float[] pointToSatVal(float x, float y) { + + final RectF rect = mSatValRect; + float[] result = new float[2]; + + float width = rect.width(); + float height = rect.height(); + + if (x < rect.left) { + x = 0f; + } + else if (x > rect.right) { + x = width; + } + else{ + x = x - rect.left; + } + + if (y < rect.top) { + y = 0f; + } + else if (y > rect.bottom) { + y = height; + } + else{ + y = y - rect.top; + } + + + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); + + return result; + } + + private float pointToHue(float y) { + + final RectF rect = mHueRect; + + float height = rect.height(); + + if (y < rect.top) { + y = 0f; + } + else if (y > rect.bottom) { + y = height; + } + else{ + y = y - rect.top; + } + + return 360f - (y * 360f / height); + } + + private int pointToAlpha(int x) { + + final RectF rect = mAlphaRect; + final int width = (int) rect.width(); + + if (x < rect.left) { + x = 0; + } + else if (x > rect.right) { + x = width; + } + else{ + x = x - (int)rect.left; + } + + return 0xff - (x * 0xff / width); + + } + + + @Override + public boolean onTrackballEvent(MotionEvent event) { + + float x = event.getX(); + float y = event.getY(); + + boolean update = false; + + + if (event.getAction() == MotionEvent.ACTION_MOVE) { + + switch(mLastTouchedPanel) { + + case PANEL_SAT_VAL: + + float sat, val; + + sat = mSat + x/50f; + val = mVal - y/50f; + + if (sat < 0f) { + sat = 0f; + } + else if (sat > 1f) { + sat = 1f; + } + + if (val < 0f) { + val = 0f; + } + else if (val > 1f) { + val = 1f; + } + + mSat = sat; + mVal = val; + + update = true; + + break; + + case PANEL_HUE: + + float hue = mHue - y * 10f; + + if (hue < 0f) { + hue = 0f; + } + else if (hue > 360f) { + hue = 360f; + } + + mHue = hue; + + update = true; + + break; + + case PANEL_ALPHA: + + if (!mShowAlphaPanel || mAlphaRect == null) { + update = false; + } + else{ + + int alpha = (int) (mAlpha - x*10); + + if (alpha < 0) { + alpha = 0; + } + else if (alpha > 0xff) { + alpha = 0xff; + } + + mAlpha = alpha; + + + update = true; + } + + break; + } + + + } + + + if (update) { + + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + return true; + } + + + return super.onTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + boolean update = false; + + switch(event.getAction()) { + + case MotionEvent.ACTION_DOWN: + + mStartTouchPoint = new Point((int)event.getX(), (int)event.getY()); + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_MOVE: + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_UP: + + mStartTouchPoint = null; + + update = moveTrackersIfNeeded(event); + + break; + + } + + if (update) { + + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + return true; + } + + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(MotionEvent event) { + + if (mStartTouchPoint == null) return false; + + boolean update = false; + + int startX = mStartTouchPoint.x; + int startY = mStartTouchPoint.y; + + + if (mHueRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_HUE; + + mHue = pointToHue(event.getY()); + + update = true; + } + else if (mSatValRect.contains(startX, startY)) { + + mLastTouchedPanel = PANEL_SAT_VAL; + + float[] result = pointToSatVal(event.getX(), event.getY()); + + mSat = result[0]; + mVal = result[1]; + + update = true; + } + else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { + + mLastTouchedPanel = PANEL_ALPHA; + + mAlpha = pointToAlpha((int)event.getX()); + + update = true; + } + + + return update; + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = 0; + int height = 0; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); + + + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); + + + if (!mShowAlphaPanel) { + height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + + //If calculated height (based on the width) is more than the allowed height. + if (height > heightAllowed) { + height = heightAllowed; + width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); + } + else{ + width = widthAllowed; + } + } + else{ + + width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + + if (width > widthAllowed) { + width = widthAllowed; + height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } + else{ + height = heightAllowed; + } + + + } + + + setMeasuredDimension(width, height); + } + + private int chooseWidth(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } + + private int chooseHeight(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPreferedHeight(); + } + } + + private int getPrefferedWidth() { + + int width = getPreferedHeight(); + + if (mShowAlphaPanel) { + width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); + } + + + return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + + } + + private int getPreferedHeight() { + + int height = (int)(200 * mDensity); + + if (mShowAlphaPanel) { + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + return height; + } + + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect() { + + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if (mShowAlphaPanel) { + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = top + panelSide; + float right = left + panelSide; + + mSatValRect = new RectF(left,top, right, bottom); + } + + private void setUpHueRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); + float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + + if (!mShowAlphaPanel) return; + + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + + + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math + .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math + .round(mAlphaRect.bottom)); + + + + } + + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * @param listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + mListener = listener; + } + + /** + * Set the color of the border surrounding all panels. + * @param color + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * Get the current color this view is showing. + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(mAlpha, new float[]{mHue,mSat,mVal}); + } + + /** + * Set the color the view should show. + * @param color The color that should be selected. + */ + public void setColor(int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * @param color The color that should be selected. + * @param callback If you want to get a callback to + * your OnColorChangedListener. + */ + public void setColor(int color, boolean callback) { + + int alpha = Color.alpha(color); + int red = Color.red(color); + int blue = Color.blue(color); + int green = Color.green(color); + + float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if (callback && mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[]{mHue, mSat, mVal})); + } + + invalidate(); + } + + /** + * Get the drawing offset of the color picker view. + * The drawing offset is the distance from the side of + * a panel to the side of the view minus the padding. + * Useful if you want to have your own panel below showing + * the currently selected color and want to align it perfectly. + * @return The offset in pixels. + */ + public float getDrawingOffset() { + return mDrawingOffset; + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * @param visible + */ + public void setAlphaSliderVisible(boolean visible) { + + if (mShowAlphaPanel != visible) { + mShowAlphaPanel = visible; + + /* + * Reset all shader to force a recreation. + * Otherwise they will not look right after + * the size of the view has changed. + */ + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null;; + + requestLayout(); + } + + } + + public void setSliderTrackerColor(int color) { + mSliderTrackerColor = color; + + mHueTrackerPaint.setColor(mSliderTrackerColor); + + invalidate(); + } + + public int getSliderTrackerColor() { + return mSliderTrackerColor; + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * @param res string resource id. + */ + public void setAlphaSliderText(int res) { + String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * @param text Text that should be shown. + */ + public void setAlphaSliderText(String text) { + mAlphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text + * that will be shown in the alpha + * slider. + * @return + */ + public String getAlphaSliderText() { + return mAlphaSliderText; + } + + /** + * Method that checks the support for HardwareAcceleration. Check AOSP notice<br/> + * <br/> + * <pre> + * 'ComposeShader can only contain shaders of different types (a BitmapShader and a + * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your + * application is affected by any of these missing features or limitations, you can turn + * off hardware acceleration for just the affected portion of your application by calling + * setLayerType(View.LAYER_TYPE_SOFTWARE, null).' + */ + private void checkHardwareAccelerationSupport() { + // HardwareAcceleration sit is only available since ICS. 14 = ICS_VERSION_CODE + if (android.os.Build.VERSION.SDK_INT >= 14) { + try{ + // We need to use reflection to get that method to avoid compilation errors + Method isHardwareAccelerated = + getClass().getMethod("isHardwareAccelerated", new Class[]{}); + Object o = isHardwareAccelerated.invoke(this, new Object[]{}); + if (null != o && o instanceof Boolean && (Boolean)o) { + // HardwareAcceleration is supported. Use SoftwareAcceleration + Method setLayerType = + getClass().getMethod( + "setLayerType", int.class, android.graphics.Paint.class); + setLayerType.invoke(this, 1, (android.graphics.Paint)null); + } + } catch (Exception e) { /** NON BLOCK **/} + } + } + +} diff --git a/src/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java b/src/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java new file mode 100644 index 0000000..a67e162 --- /dev/null +++ b/src/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2011 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.support.v4.accessibilityservice; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.pm.ResolveInfo; +import android.os.Build; +import android.view.View; + +/** + * Helper for accessing features in {@link android.accessibilityservice.AccessibilityService} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class AccessibilityServiceInfoCompat { + + static interface AccessibilityServiceInfoVersionImpl { + public String getId(AccessibilityServiceInfo info); + public ResolveInfo getResolveInfo(AccessibilityServiceInfo info); + public boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info); + public String getDescription(AccessibilityServiceInfo info); + public String getSettingsActivityName(AccessibilityServiceInfo info); + public int getCapabilities(AccessibilityServiceInfo info); + } + + static class AccessibilityServiceInfoStubImpl implements AccessibilityServiceInfoVersionImpl { + + @Override + public boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) { + return false; + } + + @Override + public String getDescription(AccessibilityServiceInfo info) { + return null; + } + + @Override + public String getId(AccessibilityServiceInfo info) { + return null; + } + + @Override + public ResolveInfo getResolveInfo(AccessibilityServiceInfo info) { + return null; + } + + @Override + public String getSettingsActivityName(AccessibilityServiceInfo info) { + return null; + } + + @Override + public int getCapabilities(AccessibilityServiceInfo info) { + return 0; + } + } + + static class AccessibilityServiceInfoIcsImpl extends AccessibilityServiceInfoStubImpl { + + @Override + public boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) { + return AccessibilityServiceInfoCompatIcs.getCanRetrieveWindowContent(info); + } + + @Override + public String getDescription(AccessibilityServiceInfo info) { + return AccessibilityServiceInfoCompatIcs.getDescription(info); + } + + @Override + public String getId(AccessibilityServiceInfo info) { + return AccessibilityServiceInfoCompatIcs.getId(info); + } + + @Override + public ResolveInfo getResolveInfo(AccessibilityServiceInfo info) { + return AccessibilityServiceInfoCompatIcs.getResolveInfo(info); + } + + @Override + public String getSettingsActivityName(AccessibilityServiceInfo info) { + return AccessibilityServiceInfoCompatIcs.getSettingsActivityName(info); + } + + @Override + public int getCapabilities(AccessibilityServiceInfo info) { + if (getCanRetrieveWindowContent(info)) { + return CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT; + } + return 0; + } + } + + static { + if (Build.VERSION.SDK_INT >= 14) { // ICS + IMPL = new AccessibilityServiceInfoIcsImpl(); + } else { + IMPL = new AccessibilityServiceInfoStubImpl(); + } + } + + // Capabilities + + private static final AccessibilityServiceInfoVersionImpl IMPL; + + /** + * Capability: This accessibility service can retrieve the active window content. + */ + public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001; + + /** + * Capability: This accessibility service can request touch exploration mode in which + * touched items are spoken aloud and the UI can be explored via gestures. + */ + public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 0x00000002; + + /** + * Capability: This accessibility service can request enhanced web accessibility + * enhancements. For example, installing scripts to make app content more accessible. + */ + public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004; + + /** + * Capability: This accessibility service can filter the key event stream. + */ + public static final int CAPABILITY_CAN_FILTER_KEY_EVENTS = 0x00000008; + + // Feedback types + + /** + * Denotes braille feedback. + */ + public static final int FEEDBACK_BRAILLE = 0x0000020; + + /** + * Mask for all feedback types. + * + * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN + * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC + * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE + * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + * @see AccessibilityServiceInfo#FEEDBACK_GENERIC + * @see FEEDBACK_BRAILLE + */ + @SuppressWarnings("javadoc") + public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF; + + // Flags + + /** + * If an {@link AccessibilityService} is the default for a given type. + * Default service is invoked only if no package specific one exists. In case of + * more than one package specific service only the earlier registered is notified. + */ + public static final int DEFAULT = 0x0000001; + + /** + * If this flag is set the system will regard views that are not important + * for accessibility in addition to the ones that are important for accessibility. + * That is, views that are marked as not important for accessibility via + * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} or + * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} and views that are + * marked as potentially important for accessibility via + * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined + * that are not important for accessibility, are both reported while querying the + * window content and also the accessibility service will receive accessibility events + * from them. + * <p> + * <strong>Note:</strong> For accessibility services targeting API version + * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly + * set for the system to regard views that are not important for accessibility. For + * accessibility services targeting API version lower than + * {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are + * regarded for accessibility purposes. + * </p> + * <p> + * Usually views not important for accessibility are layout managers that do not + * react to user actions, do not draw any content, and do not have any special + * semantics in the context of the screen content. For example, a three by three + * grid can be implemented as three horizontal linear layouts and one vertical, + * or three vertical linear layouts and one horizontal, or one grid layout, etc. + * In this context the actual layout mangers used to achieve the grid configuration + * are not important, rather it is important that there are nine evenly distributed + * elements. + * </p> + */ + @SuppressWarnings("javadoc") + public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002; + + /** + * This flag requests that the system gets into touch exploration mode. + * In this mode a single finger moving on the screen behaves as a mouse + * pointer hovering over the user interface. The system will also detect + * certain gestures performed on the touch screen and notify this service. + * The system will enable touch exploration mode if there is at least one + * accessibility service that has this flag set. Hence, clearing this + * flag does not guarantee that the device will not be in touch exploration + * mode since there may be another enabled service that requested it. + * <p> + * For accessibility services targeting API version higher than + * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} that want to set + * this flag have to declare this capability in their meta-data by setting + * the attribute canRequestTouchExplorationMode to true, otherwise this flag + * will be ignored. For how to declare the meta-data of a service refer to + * {@value AccessibilityService#SERVICE_META_DATA}. + * </p> + * <p> + * Services targeting API version equal to or lower than + * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} will work normally, i.e. + * the first time they are run, if this flag is specified, a dialog is + * shown to the user to confirm enabling explore by touch. + * </p> + */ + @SuppressWarnings("javadoc") + public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004; + + /** + * This flag requests from the system to enable web accessibility enhancing + * extensions. Such extensions aim to provide improved accessibility support + * for content presented in a {@link android.webkit.WebView}. An example of such + * an extension is injecting JavaScript from a secure source. The system will enable + * enhanced web accessibility if there is at least one accessibility service + * that has this flag set. Hence, clearing this flag does not guarantee that the + * device will not have enhanced web accessibility enabled since there may be + * another enabled service that requested it. + * <p> + * Services that want to set this flag have to declare this capability + * in their meta-data by setting the attribute canRequestEnhancedWebAccessibility + * to true, otherwise this flag will be ignored. For how to declare the meta-data + * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}. + * </p> + */ + public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008; + + /** + * This flag requests that the AccessibilityNodeInfos obtained + * by an {@link AccessibilityService} contain the id of the source view. + * The source view id will be a fully qualified resource name of the + * form "package:id/name", for example "foo.bar:id/my_list", and it is + * useful for UI test automation. This flag is not set by default. + */ + public static final int FLAG_REPORT_VIEW_IDS = 0x00000010; + + /** + * This flag requests from the system to filter key events. If this flag + * is set the accessibility service will receive the key events before + * applications allowing it implement global shortcuts. Setting this flag + * does not guarantee that this service will filter key events since only + * one service can do so at any given time. This avoids user confusion due + * to behavior change in case different key filtering services are enabled. + * If there is already another key filtering service enabled, this one will + * not receive key events. + * <p> + * Services that want to set this flag have to declare this capability + * in their meta-data by setting the attribute canRequestFilterKeyEvents + * to true, otherwise this flag will be ignored. For how to declare the meta + * -data of a service refer to {@value AccessibilityService#SERVICE_META_DATA}. + * </p> + */ + public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020; + + /* + * Hide constructor + */ + private AccessibilityServiceInfoCompat() { + + } + + /** + * The accessibility service id. + * <p> + * <strong>Generated by the system.</strong> + * </p> + * + * @return The id. + */ + public static String getId(AccessibilityServiceInfo info) { + return IMPL.getId(info); + } + + /** + * The service {@link ResolveInfo}. + * <p> + * <strong>Generated by the system.</strong> + * </p> + * + * @return The info. + */ + public static ResolveInfo getResolveInfo(AccessibilityServiceInfo info) { + return IMPL.getResolveInfo(info); + } + + /** + * The settings activity name. + * <p> + * <strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA + * meta-data}.</strong> + * </p> + * + * @return The settings activity name. + */ + public static String getSettingsActivityName(AccessibilityServiceInfo info) { + return IMPL.getSettingsActivityName(info); + } + + /** + * Whether this service can retrieve the current window's content. + * <p> + * <strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA + * meta-data}.</strong> + * </p> + * + * @return True window content can be retrieved. + */ + public static boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) { + return IMPL.getCanRetrieveWindowContent(info); + } + + /** + * Description of the accessibility service. + * <p> + * <strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA + * meta-data}.</strong> + * </p> + * + * @return The description. + */ + public static String getDescription(AccessibilityServiceInfo info) { + return IMPL.getDescription(info); + } + + /** + * Returns the string representation of a feedback type. For example, + * {@link AccessibilityServiceInfo#FEEDBACK_SPOKEN} is represented by the + * string FEEDBACK_SPOKEN. + * + * @param feedbackType The feedback type. + * @return The string representation. + */ + public static String feedbackTypeToString(int feedbackType) { + int fbt = feedbackType; + StringBuilder builder = new StringBuilder(); + builder.append("["); + while (fbt > 0) { + final int feedbackTypeFlag = 1 << Integer.numberOfTrailingZeros(fbt); + fbt &= ~feedbackTypeFlag; + if (builder.length() > 1) { + builder.append(", "); + } + switch (feedbackTypeFlag) { + case AccessibilityServiceInfo.FEEDBACK_AUDIBLE: + builder.append("FEEDBACK_AUDIBLE"); + break; + case AccessibilityServiceInfo.FEEDBACK_HAPTIC: + builder.append("FEEDBACK_HAPTIC"); + break; + case AccessibilityServiceInfo.FEEDBACK_GENERIC: + builder.append("FEEDBACK_GENERIC"); + break; + case AccessibilityServiceInfo.FEEDBACK_SPOKEN: + builder.append("FEEDBACK_SPOKEN"); + break; + case AccessibilityServiceInfo.FEEDBACK_VISUAL: + builder.append("FEEDBACK_VISUAL"); + break; + } + } + builder.append("]"); + return builder.toString(); + } + + /** + * Returns the string representation of a flag. For example, + * {@link AccessibilityServiceInfo#DEFAULT} is represented by the + * string DEFAULT. + * + * @param flag The flag. + * @return The string representation. + */ + public static String flagToString(int flag) { + switch (flag) { + case DEFAULT: + return "DEFAULT"; + case FLAG_INCLUDE_NOT_IMPORTANT_VIEWS: + return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS"; + case FLAG_REQUEST_TOUCH_EXPLORATION_MODE: + return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE"; + case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY: + return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; + case FLAG_REPORT_VIEW_IDS: + return "FLAG_REPORT_VIEW_IDS"; + case FLAG_REQUEST_FILTER_KEY_EVENTS: + return "FLAG_REQUEST_FILTER_KEY_EVENTS"; + default: + return null; + } + } + + /** + * Returns the bit mask of capabilities this accessibility service has such as + * being able to retrieve the active window content, etc. + * + * @param info The service info whose capabilities to get. + * @return The capability bit mask. + * + * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT + * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION + * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY + * @see #CAPABILITY_CAN_FILTER_KEY_EVENTS + */ + public static int getCapabilities(AccessibilityServiceInfo info) { + return IMPL.getCapabilities(info); + } + + /** + * Returns the string representation of a capability. For example, + * {@link #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT} is represented + * by the string CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT. + * + * @param capability The capability. + * @return The string representation. + */ + public static String capabilityToString(int capability) { + switch (capability) { + case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT: + return "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT"; + case CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION: + return "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION"; + case CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY: + return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; + case CAPABILITY_CAN_FILTER_KEY_EVENTS: + return "CAPABILITY_CAN_FILTER_KEY_EVENTS"; + default: + return "UNKNOWN"; + } + } +} diff --git a/src/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompatIcs.java b/src/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompatIcs.java new file mode 100644 index 0000000..b1c203f --- /dev/null +++ b/src/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompatIcs.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 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.support.v4.accessibilityservice; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.pm.ResolveInfo; + +/** + * ICS implementation of the new APIs in AccessibilityServiceInfo. + */ +class AccessibilityServiceInfoCompatIcs { + + @SuppressWarnings("deprecation") + public static boolean getCanRetrieveWindowContent(AccessibilityServiceInfo info) { + return info.getCanRetrieveWindowContent(); + } + + @SuppressWarnings("deprecation") + public static String getDescription(AccessibilityServiceInfo info) { + return info.getDescription(); + } + + public static String getId(AccessibilityServiceInfo info) { + return info.getId(); + } + + public static ResolveInfo getResolveInfo(AccessibilityServiceInfo info) { + return info.getResolveInfo(); + } + + public static String getSettingsActivityName(AccessibilityServiceInfo info) { + return info.getSettingsActivityName(); + } +} diff --git a/src/android/support/v4/os/ParcelableCompat.java b/src/android/support/v4/os/ParcelableCompat.java new file mode 100644 index 0000000..eb40359 --- /dev/null +++ b/src/android/support/v4/os/ParcelableCompat.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 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.support.v4.os; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Helper for accessing features in {@link android.os.Parcelable} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class ParcelableCompat { + + /** + * Factory method for {@link android.os.Parcelable.Creator}. + * + * @param callbacks Creator callbacks implementation. + * @return New creator. + */ + public static <T> Parcelable.Creator<T> newCreator( + ParcelableCompatCreatorCallbacks<T> callbacks) { + if (android.os.Build.VERSION.SDK_INT >= 13) { + ParcelableCompatCreatorHoneycombMR2Stub.instantiate(callbacks); + } + return new CompatCreator<T>(callbacks); + } + + static class CompatCreator<T> implements Parcelable.Creator<T> { + final ParcelableCompatCreatorCallbacks<T> mCallbacks; + + public CompatCreator(ParcelableCompatCreatorCallbacks<T> callbacks) { + mCallbacks = callbacks; + } + + @Override + public T createFromParcel(Parcel source) { + return mCallbacks.createFromParcel(source, null); + } + + @Override + public T[] newArray(int size) { + return mCallbacks.newArray(size); + } + } +} diff --git a/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java b/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java new file mode 100644 index 0000000..a577d6f --- /dev/null +++ b/src/android/support/v4/os/ParcelableCompatCreatorCallbacks.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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.support.v4.os; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Callbacks a {@link Parcelable} creator should implement. + */ +public interface ParcelableCompatCreatorCallbacks<T> { + + /** + * Create a new instance of the Parcelable class, instantiating it + * from the given Parcel whose data had previously been written by + * {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and + * using the given ClassLoader. + * + * @param in The Parcel to read the object's data from. + * @param loader The ClassLoader that this object is being created in. + * @return Returns a new instance of the Parcelable class. + */ + public T createFromParcel(Parcel in, ClassLoader loader); + + /** + * Create a new array of the Parcelable class. + * + * @param size Size of the array. + * @return Returns an array of the Parcelable class, with every entry + * initialized to null. + */ + public T[] newArray(int size); +} diff --git a/src/android/support/v4/os/ParcelableCompatHoneycombMR2.java b/src/android/support/v4/os/ParcelableCompatHoneycombMR2.java new file mode 100644 index 0000000..dd7ea81 --- /dev/null +++ b/src/android/support/v4/os/ParcelableCompatHoneycombMR2.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 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.support.v4.os; + +import android.os.Parcel; +import android.os.Parcelable; + +class ParcelableCompatCreatorHoneycombMR2Stub { + static <T> Parcelable.Creator<T> instantiate(ParcelableCompatCreatorCallbacks<T> callbacks) { + return new ParcelableCompatCreatorHoneycombMR2<T>(callbacks); + } +} + +class ParcelableCompatCreatorHoneycombMR2<T> implements Parcelable.ClassLoaderCreator<T> { + private final ParcelableCompatCreatorCallbacks<T> mCallbacks; + + public ParcelableCompatCreatorHoneycombMR2(ParcelableCompatCreatorCallbacks<T> callbacks) { + mCallbacks = callbacks; + } + + @Override + public T createFromParcel(Parcel in) { + return mCallbacks.createFromParcel(in, null); + } + + @Override + public T createFromParcel(Parcel in, ClassLoader loader) { + return mCallbacks.createFromParcel(in, loader); + } + + @Override + public T[] newArray(int size) { + return mCallbacks.newArray(size); + } +} diff --git a/src/android/support/v4/view/AccessibilityDelegateCompat.java b/src/android/support/v4/view/AccessibilityDelegateCompat.java new file mode 100644 index 0000000..59d823d --- /dev/null +++ b/src/android/support/v4/view/AccessibilityDelegateCompat.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.os.Build; +import android.os.Bundle; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; + +/** + * Helper for accessing {@link android.view.View.AccessibilityDelegate} introduced after + * API level 4 in a backwards compatible fashion. + */ +public class AccessibilityDelegateCompat { + + static interface AccessibilityDelegateImpl { + public Object newAccessiblityDelegateDefaultImpl(); + public Object newAccessiblityDelegateBridge(AccessibilityDelegateCompat listener); + public boolean dispatchPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event); + public void onInitializeAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event); + public void onInitializeAccessibilityNodeInfo(Object delegate, View host, + AccessibilityNodeInfoCompat info); + public void onPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event); + public boolean onRequestSendAccessibilityEvent(Object delegate, ViewGroup host, View child, + AccessibilityEvent event); + public void sendAccessibilityEvent(Object delegate, View host, int eventType); + public void sendAccessibilityEventUnchecked(Object delegate, View host, + AccessibilityEvent event); + public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(Object delegate, + View host); + public boolean performAccessibilityAction(Object delegate, View host, int action, + Bundle args); + } + + static class AccessibilityDelegateStubImpl implements AccessibilityDelegateImpl { + @Override + public Object newAccessiblityDelegateDefaultImpl() { + return null; + } + + @Override + public Object newAccessiblityDelegateBridge(AccessibilityDelegateCompat listener) { + return null; + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + return false; + } + + @Override + public void onInitializeAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + return; + } + + @Override + public void onInitializeAccessibilityNodeInfo(Object delegate, View host, + AccessibilityNodeInfoCompat info) { + return; + } + + @Override + public void onPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + return; + } + + @Override + public boolean onRequestSendAccessibilityEvent(Object delegate, ViewGroup host, View child, + AccessibilityEvent event) { + return true; + } + + @Override + public void sendAccessibilityEvent(Object delegate, View host, int eventType) { + return; + } + + @Override + public void sendAccessibilityEventUnchecked(Object delegate, View host, + AccessibilityEvent event) { + return; + } + + @Override + public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(Object delegate, + View host) { + return null; + } + + @Override + public boolean performAccessibilityAction(Object delegate, View host, int action, + Bundle args) { + return false; + } + } + + static class AccessibilityDelegateIcsImpl extends AccessibilityDelegateStubImpl { + @Override + public Object newAccessiblityDelegateDefaultImpl() { + return AccessibilityDelegateCompatIcs.newAccessibilityDelegateDefaultImpl(); + } + + @Override + public Object newAccessiblityDelegateBridge(final AccessibilityDelegateCompat compat) { + return AccessibilityDelegateCompatIcs.newAccessibilityDelegateBridge( + new AccessibilityDelegateCompatIcs.AccessibilityDelegateBridge() { + @Override + public boolean dispatchPopulateAccessibilityEvent(View host, + AccessibilityEvent event) { + return compat.dispatchPopulateAccessibilityEvent(host, event); + } + + @Override + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + compat.onInitializeAccessibilityEvent(host, event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, Object info) { + compat.onInitializeAccessibilityNodeInfo(host, + new AccessibilityNodeInfoCompat(info)); + } + + @Override + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + compat.onPopulateAccessibilityEvent(host, event); + } + + @Override + public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, + AccessibilityEvent event) { + return compat.onRequestSendAccessibilityEvent(host, child, event); + } + + @Override + public void sendAccessibilityEvent(View host, int eventType) { + compat.sendAccessibilityEvent(host, eventType); + } + + @Override + public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { + compat.sendAccessibilityEventUnchecked(host, event); + } + }); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + return AccessibilityDelegateCompatIcs.dispatchPopulateAccessibilityEvent(delegate, + host, event); + } + + @Override + public void onInitializeAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + AccessibilityDelegateCompatIcs.onInitializeAccessibilityEvent(delegate, host, event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(Object delegate, View host, + AccessibilityNodeInfoCompat info) { + AccessibilityDelegateCompatIcs.onInitializeAccessibilityNodeInfo(delegate, host, + info.getInfo()); + } + + @Override + public void onPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + AccessibilityDelegateCompatIcs.onPopulateAccessibilityEvent(delegate, host, event); + } + + @Override + public boolean onRequestSendAccessibilityEvent(Object delegate, ViewGroup host, View child, + AccessibilityEvent event) { + return AccessibilityDelegateCompatIcs.onRequestSendAccessibilityEvent(delegate, host, + child, event); + } + + @Override + public void sendAccessibilityEvent(Object delegate, View host, int eventType) { + AccessibilityDelegateCompatIcs.sendAccessibilityEvent(delegate, host, eventType); + } + + @Override + public void sendAccessibilityEventUnchecked(Object delegate, View host, + AccessibilityEvent event) { + AccessibilityDelegateCompatIcs.sendAccessibilityEventUnchecked(delegate, host, event); + } + } + + private static final AccessibilityDelegateImpl IMPL; + private static final Object DEFAULT_DELEGATE; + + static { + if (Build.VERSION.SDK_INT >= 14) { // ICS + IMPL = new AccessibilityDelegateIcsImpl(); + } else { + IMPL = new AccessibilityDelegateStubImpl(); + } + DEFAULT_DELEGATE = IMPL.newAccessiblityDelegateDefaultImpl(); + } + + final Object mBridge; + + /** + * Creates a new instance. + */ + public AccessibilityDelegateCompat() { + mBridge = IMPL.newAccessiblityDelegateBridge(this); + } + + /** + * @return The wrapped bridge implementation. + */ + Object getBridge() { + return mBridge; + } + + /** + * Sends an accessibility event of the given type. If accessibility is not + * enabled this method has no effect. + * <p> + * The default implementation behaves as {@link View#sendAccessibilityEvent(int) + * View#sendAccessibilityEvent(int)} for the case of no accessibility delegate + * been set. + * </p> + * + * @param host The View hosting the delegate. + * @param eventType The type of the event to send. + * + * @see View#sendAccessibilityEvent(int) View#sendAccessibilityEvent(int) + */ + @SuppressWarnings("static-method") + public void sendAccessibilityEvent(View host, int eventType) { + IMPL.sendAccessibilityEvent(DEFAULT_DELEGATE, host, eventType); + } + + /** + * Sends an accessibility event. This method behaves exactly as + * {@link #sendAccessibilityEvent(View, int)} but takes as an argument an + * empty {@link AccessibilityEvent} and does not perform a check whether + * accessibility is enabled. + * <p> + * The default implementation behaves as + * {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent) + * View#sendAccessibilityEventUnchecked(AccessibilityEvent)} for + * the case of no accessibility delegate been set. + * </p> + * + * @param host The View hosting the delegate. + * @param event The event to send. + * + * @see View#sendAccessibilityEventUnchecked(AccessibilityEvent) + * View#sendAccessibilityEventUnchecked(AccessibilityEvent) + */ + @SuppressWarnings("static-method") + public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { + IMPL.sendAccessibilityEventUnchecked(DEFAULT_DELEGATE, host, event); + } + + /** + * Dispatches an {@link AccessibilityEvent} to the host {@link View} first and then + * to its children for adding their text content to the event. + * <p> + * The default implementation behaves as + * {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) + * View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} for + * the case of no accessibility delegate been set. + * </p> + * + * @param host The View hosting the delegate. + * @param event The event. + * @return True if the event population was completed. + * + * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) + * View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) + */ + @SuppressWarnings("static-method") + public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + return IMPL.dispatchPopulateAccessibilityEvent(DEFAULT_DELEGATE, host, event); + } + + /** + * Gives a chance to the host View to populate the accessibility event with its + * text content. + * <p> + * The default implementation behaves as + * {@link ViewCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent) + * ViewCompat#onPopulateAccessibilityEvent(AccessibilityEvent)} for + * the case of no accessibility delegate been set. + * </p> + * + * @param host The View hosting the delegate. + * @param event The accessibility event which to populate. + * + * @see ViewCompat#onPopulateAccessibilityEvent(View ,AccessibilityEvent) + * ViewCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent) + */ + @SuppressWarnings("static-method") + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + IMPL.onPopulateAccessibilityEvent(DEFAULT_DELEGATE, host, event); + } + + /** + * Initializes an {@link AccessibilityEvent} with information about the + * the host View which is the event source. + * <p> + * The default implementation behaves as + * {@link ViewCompat#onInitializeAccessibilityEvent(View v, AccessibilityEvent event) + * ViewCompat#onInitalizeAccessibilityEvent(View v, AccessibilityEvent event)} for + * the case of no accessibility delegate been set. + * </p> + * + * @param host The View hosting the delegate. + * @param event The event to initialize. + * + * @see ViewCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent) + * ViewCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent) + */ + @SuppressWarnings("static-method") + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + IMPL.onInitializeAccessibilityEvent(DEFAULT_DELEGATE, host, event); + } + + /** + * Initializes an {@link AccessibilityNodeInfoCompat} with information about the host view. + * <p> + * The default implementation behaves as + * {@link ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat) + * ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)} for + * the case of no accessibility delegate been set. + * </p> + * + * @param host The View hosting the delegate. + * @param info The instance to initialize. + * + * @see ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat) + * ViewCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat) + */ + @SuppressWarnings("static-method") + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + IMPL.onInitializeAccessibilityNodeInfo(DEFAULT_DELEGATE, host, info); + } + + /** + * Called when a child of the host View has requested sending an + * {@link AccessibilityEvent} and gives an opportunity to the parent (the host) + * to augment the event. + * <p> + * The default implementation behaves as + * {@link ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent) + * ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent)} for + * the case of no accessibility delegate been set. + * </p> + * + * @param host The View hosting the delegate. + * @param child The child which requests sending the event. + * @param event The event to be sent. + * @return True if the event should be sent + * + * @see ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent) + * ViewGroupCompat#onRequestSendAccessibilityEvent(ViewGroup, View, AccessibilityEvent) + */ + @SuppressWarnings({"static-method", "javadoc"}) + public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, + AccessibilityEvent event) { + return IMPL.onRequestSendAccessibilityEvent(DEFAULT_DELEGATE, host, child, event); + } + + /** + * Gets the provider for managing a virtual view hierarchy rooted at this View + * and reported to {@link android.accessibilityservice.AccessibilityService}s + * that explore the window content. + * <p> + * The default implementation behaves as + * {@link ViewCompat#getAccessibilityNodeProvider(View) ViewCompat#getAccessibilityNodeProvider(View)} + * for the case of no accessibility delegate been set. + * </p> + * + * @return The provider. + * + * @see AccessibilityNodeProviderCompat + */ + @SuppressWarnings("static-method") + public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) { + return IMPL.getAccessibilityNodeProvider(DEFAULT_DELEGATE, host); + } + + /** + * Performs the specified accessibility action on the view. For + * possible accessibility actions look at {@link AccessibilityNodeInfoCompat}. + * <p> + * The default implementation behaves as + * {@link View#performAccessibilityAction(int, Bundle) + * View#performAccessibilityAction(int, Bundle)} for the case of + * no accessibility delegate been set. + * </p> + * + * @param action The action to perform. + * @return Whether the action was performed. + * + * @see View#performAccessibilityAction(int, Bundle) + * View#performAccessibilityAction(int, Bundle) + */ + @SuppressWarnings("static-method") + public boolean performAccessibilityAction(View host, int action, Bundle args) { + return IMPL.performAccessibilityAction(DEFAULT_DELEGATE, host, action, args); + } +} diff --git a/src/android/support/v4/view/AccessibilityDelegateCompatIcs.java b/src/android/support/v4/view/AccessibilityDelegateCompatIcs.java new file mode 100644 index 0000000..4adeb0c --- /dev/null +++ b/src/android/support/v4/view/AccessibilityDelegateCompatIcs.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +/** + * ICS specific AccessibilityDelegate API implementation. + */ +class AccessibilityDelegateCompatIcs { + + public interface AccessibilityDelegateBridge { + public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event); + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event); + public void onInitializeAccessibilityNodeInfo(View host, Object info); + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event); + public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, + AccessibilityEvent event); + public void sendAccessibilityEvent(View host, int eventType); + public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event); + } + + public static Object newAccessibilityDelegateDefaultImpl() { + return new AccessibilityDelegate(); + } + + public static Object newAccessibilityDelegateBridge(final AccessibilityDelegateBridge bridge) { + return new AccessibilityDelegate() { + @Override + public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + return bridge.dispatchPopulateAccessibilityEvent(host, event); + } + + @Override + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + bridge.onInitializeAccessibilityEvent(host, event); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + bridge.onInitializeAccessibilityNodeInfo(host, info); + } + + @Override + public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { + bridge.onPopulateAccessibilityEvent(host, event); + } + + @Override + public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, + AccessibilityEvent event) { + return bridge.onRequestSendAccessibilityEvent(host, child, event); + } + + @Override + public void sendAccessibilityEvent(View host, int eventType) { + bridge.sendAccessibilityEvent(host, eventType); + } + + @Override + public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) { + bridge.sendAccessibilityEventUnchecked(host, event); + } + }; + } + + public static boolean dispatchPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + return ((AccessibilityDelegate) delegate).dispatchPopulateAccessibilityEvent(host, event); + } + + public static void onInitializeAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + ((AccessibilityDelegate) delegate).onInitializeAccessibilityEvent(host, event); + } + + public static void onInitializeAccessibilityNodeInfo(Object delegate, View host, Object info) { + ((AccessibilityDelegate) delegate).onInitializeAccessibilityNodeInfo(host, + (AccessibilityNodeInfo) info); + } + + public static void onPopulateAccessibilityEvent(Object delegate, View host, + AccessibilityEvent event) { + ((AccessibilityDelegate) delegate).onPopulateAccessibilityEvent(host, event); + } + + public static boolean onRequestSendAccessibilityEvent(Object delegate, ViewGroup host, + View child, AccessibilityEvent event) { + return ((AccessibilityDelegate) delegate).onRequestSendAccessibilityEvent(host, child, + event); + } + + public static void sendAccessibilityEvent(Object delegate, View host, int eventType) { + ((AccessibilityDelegate) delegate).sendAccessibilityEvent(host, eventType); + } + + public static void sendAccessibilityEventUnchecked(Object delegate, View host, + AccessibilityEvent event) { + ((AccessibilityDelegate) delegate).sendAccessibilityEventUnchecked(host, event); + } +} diff --git a/src/android/support/v4/view/KeyEventCompat.java b/src/android/support/v4/view/KeyEventCompat.java new file mode 100644 index 0000000..7dd5428 --- /dev/null +++ b/src/android/support/v4/view/KeyEventCompat.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.KeyEvent; +import android.view.View; + +/** + * Helper for accessing features in {@link KeyEvent} introduced after + * API level 4 in a backwards compatible fashion. + */ +public class KeyEventCompat { + /** + * Interface for the full API. + */ + interface KeyEventVersionImpl { + public int normalizeMetaState(int metaState); + public boolean metaStateHasModifiers(int metaState, int modifiers); + public boolean metaStateHasNoModifiers(int metaState); + public void startTracking(KeyEvent event); + public boolean isTracking(KeyEvent event); + public Object getKeyDispatcherState(View view); + public boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, + Object target); + } + + /** + * Interface implementation that doesn't use anything about v4 APIs. + */ + static class BaseKeyEventVersionImpl implements KeyEventVersionImpl { + private static final int META_MODIFIER_MASK = + KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON + | KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON + | KeyEvent.META_SYM_ON; + + // Mask of all lock key meta states. + private static final int META_ALL_MASK = META_MODIFIER_MASK; + + private static int metaStateFilterDirectionalModifiers(int metaState, + int modifiers, int basic, int left, int right) { + final boolean wantBasic = (modifiers & basic) != 0; + final int directional = left | right; + final boolean wantLeftOrRight = (modifiers & directional) != 0; + + if (wantBasic) { + if (wantLeftOrRight) { + throw new IllegalArgumentException("bad arguments"); + } + return metaState & ~directional; + } else if (wantLeftOrRight) { + return metaState & ~basic; + } else { + return metaState; + } + } + + @Override + public int normalizeMetaState(int metaState) { + int ms = metaState; + if ((ms & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON)) != 0) { + ms |= KeyEvent.META_SHIFT_ON; + } + if ((ms & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON)) != 0) { + ms |= KeyEvent.META_ALT_ON; + } + return ms & META_ALL_MASK; + } + + @Override + public boolean metaStateHasModifiers(int metaState, int modifiers) { + int ms = metaState; + ms = normalizeMetaState(ms) & META_MODIFIER_MASK; + ms = metaStateFilterDirectionalModifiers(ms, modifiers, + KeyEvent.META_SHIFT_ON, KeyEvent.META_SHIFT_LEFT_ON, KeyEvent.META_SHIFT_RIGHT_ON); + ms = metaStateFilterDirectionalModifiers(ms, modifiers, + KeyEvent.META_ALT_ON, KeyEvent.META_ALT_LEFT_ON, KeyEvent.META_ALT_RIGHT_ON); + return ms == modifiers; + } + + @Override + public boolean metaStateHasNoModifiers(int metaState) { + return (normalizeMetaState(metaState) & META_MODIFIER_MASK) == 0; + } + + @Override + public void startTracking(KeyEvent event) { + return; + } + + @Override + public boolean isTracking(KeyEvent event) { + return false; + } + + @Override + public Object getKeyDispatcherState(View view) { + return null; + } + + @Override + @SuppressWarnings("deprecation") + public boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, + Object target) { + return event.dispatch(receiver); + } + } + + static class EclairKeyEventVersionImpl extends BaseKeyEventVersionImpl { + @Override + public void startTracking(KeyEvent event) { + KeyEventCompatEclair.startTracking(event); + } + + @Override + public boolean isTracking(KeyEvent event) { + return KeyEventCompatEclair.isTracking(event); + } + + @Override + public Object getKeyDispatcherState(View view) { + return KeyEventCompatEclair.getKeyDispatcherState(view); + } + + @Override + public boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, + Object target) { + return KeyEventCompatEclair.dispatch(event, receiver, state, target); + } + } + + /** + * Interface implementation for devices with at least v11 APIs. + */ + static class HoneycombKeyEventVersionImpl extends EclairKeyEventVersionImpl { + @Override + public int normalizeMetaState(int metaState) { + return KeyEventCompatHoneycomb.normalizeMetaState(metaState); + } + + @Override + public boolean metaStateHasModifiers(int metaState, int modifiers) { + return KeyEventCompatHoneycomb.metaStateHasModifiers(metaState, modifiers); + } + + @Override + public boolean metaStateHasNoModifiers(int metaState) { + return KeyEventCompatHoneycomb.metaStateHasNoModifiers(metaState); + } + } + + /** + * Select the correct implementation to use for the current platform. + */ + static final KeyEventVersionImpl IMPL; + static { + if (android.os.Build.VERSION.SDK_INT >= 11) { + IMPL = new HoneycombKeyEventVersionImpl(); + } else { + IMPL = new BaseKeyEventVersionImpl(); + } + } + + // ------------------------------------------------------------------- + + public static int normalizeMetaState(int metaState) { + return IMPL.normalizeMetaState(metaState); + } + + public static boolean metaStateHasModifiers(int metaState, int modifiers) { + return IMPL.metaStateHasModifiers(metaState, modifiers); + } + + public static boolean metaStateHasNoModifiers(int metaState) { + return IMPL.metaStateHasNoModifiers(metaState); + } + + public static boolean hasModifiers(KeyEvent event, int modifiers) { + return IMPL.metaStateHasModifiers(event.getMetaState(), modifiers); + } + + public static boolean hasNoModifiers(KeyEvent event) { + return IMPL.metaStateHasNoModifiers(event.getMetaState()); + } + + public static void startTracking(KeyEvent event) { + IMPL.startTracking(event); + } + + public static boolean isTracking(KeyEvent event) { + return IMPL.isTracking(event); + } + + public static Object getKeyDispatcherState(View view) { + return IMPL.getKeyDispatcherState(view); + } + + public static boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, + Object target) { + return IMPL.dispatch(event, receiver, state, target); + } +} diff --git a/src/android/support/v4/view/KeyEventCompatEclair.java b/src/android/support/v4/view/KeyEventCompatEclair.java new file mode 100644 index 0000000..b3b0d11 --- /dev/null +++ b/src/android/support/v4/view/KeyEventCompatEclair.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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.support.v4.view; + +import android.view.KeyEvent; +import android.view.View; + +class KeyEventCompatEclair { + public static Object getKeyDispatcherState(View view) { + return view.getKeyDispatcherState(); + } + + public static boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, + Object target) { + return event.dispatch(receiver, (KeyEvent.DispatcherState)state, target); + } + + public static void startTracking(KeyEvent event) { + event.startTracking(); + } + + public static boolean isTracking(KeyEvent event) { + return event.isTracking(); + } +} diff --git a/src/android/support/v4/view/KeyEventCompatHoneycomb.java b/src/android/support/v4/view/KeyEventCompatHoneycomb.java new file mode 100644 index 0000000..8819357 --- /dev/null +++ b/src/android/support/v4/view/KeyEventCompatHoneycomb.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.KeyEvent; + +/** + * Implementation of key event compatibility that can call Honeycomb APIs. + */ +class KeyEventCompatHoneycomb { + public static int normalizeMetaState(int metaState) { + return KeyEvent.normalizeMetaState(metaState); + } + + public static boolean metaStateHasModifiers(int metaState, int modifiers) { + return KeyEvent.metaStateHasModifiers(metaState, modifiers); + } + + public static boolean metaStateHasNoModifiers(int metaState) { + return KeyEvent.metaStateHasNoModifiers(metaState); + } +} diff --git a/src/android/support/v4/view/MotionEventCompat.java b/src/android/support/v4/view/MotionEventCompat.java new file mode 100644 index 0000000..18e9e5d --- /dev/null +++ b/src/android/support/v4/view/MotionEventCompat.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.MotionEvent; + +/** + * Helper for accessing features in {@link MotionEvent} introduced + * after API level 4 in a backwards compatible fashion. + */ +public class MotionEventCompat { + /** + * Interface for the full API. + */ + interface MotionEventVersionImpl { + public int findPointerIndex(MotionEvent event, int pointerId); + public int getPointerId(MotionEvent event, int pointerIndex); + public float getX(MotionEvent event, int pointerIndex); + public float getY(MotionEvent event, int pointerIndex); + public int getPointerCount(MotionEvent event); + } + + /** + * Interface implementation that doesn't use anything about v4 APIs. + */ + static class BaseMotionEventVersionImpl implements MotionEventVersionImpl { + @Override + public int findPointerIndex(MotionEvent event, int pointerId) { + if (pointerId == 0) { + // id 0 == index 0 and vice versa. + return 0; + } + return -1; + } + @Override + public int getPointerId(MotionEvent event, int pointerIndex) { + if (pointerIndex == 0) { + // index 0 == id 0 and vice versa. + return 0; + } + throw new IndexOutOfBoundsException("Pre-Eclair does not support multiple pointers"); + } + @Override + public float getX(MotionEvent event, int pointerIndex) { + if (pointerIndex == 0) { + return event.getX(); + } + throw new IndexOutOfBoundsException("Pre-Eclair does not support multiple pointers"); + } + @Override + public float getY(MotionEvent event, int pointerIndex) { + if (pointerIndex == 0) { + return event.getY(); + } + throw new IndexOutOfBoundsException("Pre-Eclair does not support multiple pointers"); + } + @Override + public int getPointerCount(MotionEvent event) { + return 1; + } + } + + /** + * Interface implementation for devices with at least v11 APIs. + */ + static class EclairMotionEventVersionImpl implements MotionEventVersionImpl { + @Override + public int findPointerIndex(MotionEvent event, int pointerId) { + return MotionEventCompatEclair.findPointerIndex(event, pointerId); + } + @Override + public int getPointerId(MotionEvent event, int pointerIndex) { + return MotionEventCompatEclair.getPointerId(event, pointerIndex); + } + @Override + public float getX(MotionEvent event, int pointerIndex) { + return MotionEventCompatEclair.getX(event, pointerIndex); + } + @Override + public float getY(MotionEvent event, int pointerIndex) { + return MotionEventCompatEclair.getY(event, pointerIndex); + } + @Override + public int getPointerCount(MotionEvent event) { + return MotionEventCompatEclair.getPointerCount(event); + } + } + + /** + * Select the correct implementation to use for the current platform. + */ + static final MotionEventVersionImpl IMPL; + static { + if (android.os.Build.VERSION.SDK_INT >= 5) { + IMPL = new EclairMotionEventVersionImpl(); + } else { + IMPL = new BaseMotionEventVersionImpl(); + } + } + + // ------------------------------------------------------------------- + + /** + * Synonym for {@link MotionEvent#ACTION_MASK}. + */ + public static final int ACTION_MASK = 0xff; + + /** + * Synonym for {@link MotionEvent#ACTION_POINTER_DOWN}. + */ + public static final int ACTION_POINTER_DOWN = 5; + + /** + * Synonym for {@link MotionEvent#ACTION_POINTER_UP}. + */ + public static final int ACTION_POINTER_UP = 6; + + /** + * Synonym for {@link MotionEvent#ACTION_HOVER_MOVE}. + */ + public static final int ACTION_HOVER_MOVE = 7; + + /** + * Synonym for {@link MotionEvent#ACTION_SCROLL}. + */ + public static final int ACTION_SCROLL = 8; + + /** + * Synonym for {@link MotionEvent#ACTION_POINTER_INDEX_MASK}. + */ + public static final int ACTION_POINTER_INDEX_MASK = 0xff00; + + /** + * Synonym for {@link MotionEvent#ACTION_POINTER_INDEX_SHIFT}. + */ + public static final int ACTION_POINTER_INDEX_SHIFT = 8; + + /** + * Constant for {@link #getActionMasked}: The pointer is not down but has entered the + * boundaries of a window or view. + * <p> + * This action is always delivered to the window or view under the pointer. + * </p><p> + * This action is not a touch event so it is delivered to + * {@link android.view.View#onGenericMotionEvent(MotionEvent)} rather than + * {@link android.view.View#onTouchEvent(MotionEvent)}. + * </p> + */ + public static final int ACTION_HOVER_ENTER = 9; + + /** + * Constant for {@link #getActionMasked}: The pointer is not down but has exited the + * boundaries of a window or view. + * <p> + * This action is always delivered to the window or view that was previously under the pointer. + * </p><p> + * This action is not a touch event so it is delivered to + * {@link android.view.View#onGenericMotionEvent(MotionEvent)} rather than + * {@link android.view.View#onTouchEvent(MotionEvent)}. + * </p> + */ + public static final int ACTION_HOVER_EXIT = 10; + + /** + * Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK} + * portion. + */ + public static int getActionMasked(MotionEvent event) { + return event.getAction() & ACTION_MASK; + } + + /** + * Call {@link MotionEvent#getAction}, returning only the pointer index + * portion + */ + public static int getActionIndex(MotionEvent event) { + return (event.getAction() & ACTION_POINTER_INDEX_MASK) + >> ACTION_POINTER_INDEX_SHIFT; + } + + /** + * Call {@link MotionEvent#findPointerIndex(int)}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#ECLAIR} device, + * does nothing and returns -1. + */ + public static int findPointerIndex(MotionEvent event, int pointerId) { + return IMPL.findPointerIndex(event, pointerId); + } + + /** + * Call {@link MotionEvent#getPointerId(int)}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#ECLAIR} device, + * {@link IndexOutOfBoundsException} is thrown. + */ + public static int getPointerId(MotionEvent event, int pointerIndex) { + return IMPL.getPointerId(event, pointerIndex); + } + + /** + * Call {@link MotionEvent#getX(int)}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#ECLAIR} device, + * {@link IndexOutOfBoundsException} is thrown. + */ + public static float getX(MotionEvent event, int pointerIndex) { + return IMPL.getX(event, pointerIndex); + } + + /** + * Call {@link MotionEvent#getY(int)}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#ECLAIR} device, + * {@link IndexOutOfBoundsException} is thrown. + */ + public static float getY(MotionEvent event, int pointerIndex) { + return IMPL.getY(event, pointerIndex); + } + + /** + * The number of pointers of data contained in this event. Always + * >= 1. + */ + public static int getPointerCount(MotionEvent event) { + return IMPL.getPointerCount(event); + } +} diff --git a/src/android/support/v4/view/MotionEventCompatEclair.java b/src/android/support/v4/view/MotionEventCompatEclair.java new file mode 100644 index 0000000..7ecfb5c --- /dev/null +++ b/src/android/support/v4/view/MotionEventCompatEclair.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.MotionEvent; + +/** + * Implementation of motion event compatibility that can call Eclair APIs. + */ +class MotionEventCompatEclair { + public static int findPointerIndex(MotionEvent event, int pointerId) { + return event.findPointerIndex(pointerId); + } + public static int getPointerId(MotionEvent event, int pointerIndex) { + return event.getPointerId(pointerIndex); + } + public static float getX(MotionEvent event, int pointerIndex) { + return event.getX(pointerIndex); + } + public static float getY(MotionEvent event, int pointerIndex) { + return event.getY(pointerIndex); + } + public static int getPointerCount(MotionEvent event) { + return event.getPointerCount(); + } +} diff --git a/src/android/support/v4/view/PagerAdapter.java b/src/android/support/v4/view/PagerAdapter.java new file mode 100644 index 0000000..c321292 --- /dev/null +++ b/src/android/support/v4/view/PagerAdapter.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.database.DataSetObservable; +import android.database.DataSetObserver; +import android.os.Parcelable; +import android.view.View; +import android.view.ViewGroup; + +/** + * Base class providing the adapter to populate pages inside of + * a {@link ViewPager}. You will most likely want to use a more + * specific implementation of this, such as + * {@link android.support.v4.app.FragmentPagerAdapter} or + * {@link android.support.v4.app.FragmentStatePagerAdapter}. + * + * <p>When you implement a PagerAdapter, you must override the following methods + * at minimum:</p> + * <ul> + * <li>{@link #instantiateItem(ViewGroup, int)}</li> + * <li>{@link #destroyItem(ViewGroup, int, Object)}</li> + * <li>{@link #getCount()}</li> + * <li>{@link #isViewFromObject(View, Object)}</li> + * </ul> + * + * <p>PagerAdapter is more general than the adapters used for + * {@link android.widget.AdapterView AdapterViews}. Instead of providing a + * View recycling mechanism directly ViewPager uses callbacks to indicate the + * steps taken during an update. A PagerAdapter may implement a form of View + * recycling if desired or use a more sophisticated method of managing page + * Views such as Fragment transactions where each page is represented by its + * own Fragment.</p> + * + * <p>ViewPager associates each page with a key Object instead of working with + * Views directly. This key is used to track and uniquely identify a given page + * independent of its position in the adapter. A call to the PagerAdapter method + * {@link #startUpdate(ViewGroup)} indicates that the contents of the ViewPager + * are about to change. One or more calls to {@link #instantiateItem(ViewGroup, int)} + * and/or {@link #destroyItem(ViewGroup, int, Object)} will follow, and the end + * of an update will be signaled by a call to {@link #finishUpdate(ViewGroup)}. + * By the time {@link #finishUpdate(ViewGroup) finishUpdate} returns the views + * associated with the key objects returned by + * {@link #instantiateItem(ViewGroup, int) instantiateItem} should be added to + * the parent ViewGroup passed to these methods and the views associated with + * the keys passed to {@link #destroyItem(ViewGroup, int, Object) destroyItem} + * should be removed. The method {@link #isViewFromObject(View, Object)} identifies + * whether a page View is associated with a given key object.</p> + * + * <p>A very simple PagerAdapter may choose to use the page Views themselves + * as key objects, returning them from {@link #instantiateItem(ViewGroup, int)} + * after creation and adding them to the parent ViewGroup. A matching + * {@link #destroyItem(ViewGroup, int, Object)} implementation would remove the + * View from the parent ViewGroup and {@link #isViewFromObject(View, Object)} + * could be implemented as <code>return view == object;</code>.</p> + * + * <p>PagerAdapter supports data set changes. Data set changes must occur on the + * main thread and must end with a call to {@link #notifyDataSetChanged()} similar + * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data + * set change may involve pages being added, removed, or changing position. The + * ViewPager will keep the current page active provided the adapter implements + * the method {@link #getItemPosition(Object)}.</p> + */ +@SuppressWarnings("javadoc") +public abstract class PagerAdapter { + private DataSetObservable mObservable = new DataSetObservable(); + + public static final int POSITION_UNCHANGED = -1; + public static final int POSITION_NONE = -2; + + /** + * Return the number of views available. + */ + public abstract int getCount(); + + /** + * Called when a change in the shown pages is going to start being made. + * @param container The containing View which is displaying this adapter's + * page views. + */ + public void startUpdate(ViewGroup container) { + startUpdate((View) container); + } + + /** + * Create the page for the given position. The adapter is responsible + * for adding the view to the container given here, although it only + * must ensure this is done by the time it returns from + * {@link #finishUpdate(ViewGroup)}. + * + * @param container The containing View in which the page will be shown. + * @param position The page position to be instantiated. + * @return Returns an Object representing the new page. This does not + * need to be a View, but can be some other container of the page. + */ + public Object instantiateItem(ViewGroup container, int position) { + return instantiateItem((View) container, position); + } + + /** + * Remove a page for the given position. The adapter is responsible + * for removing the view from its container, although it only must ensure + * this is done by the time it returns from {@link #finishUpdate(ViewGroup)}. + * + * @param container The containing View from which the page will be removed. + * @param position The page position to be removed. + * @param object The same object that was returned by + * {@link #instantiateItem(View, int)}. + */ + public void destroyItem(ViewGroup container, int position, Object object) { + destroyItem((View) container, position, object); + } + + /** + * Called to inform the adapter of which item is currently considered to + * be the "primary", that is the one show to the user as the current page. + * + * @param container The containing View from which the page will be removed. + * @param position The page position that is now the primary. + * @param object The same object that was returned by + * {@link #instantiateItem(View, int)}. + */ + public void setPrimaryItem(ViewGroup container, int position, Object object) { + setPrimaryItem((View) container, position, object); + } + + /** + * Called when the a change in the shown pages has been completed. At this + * point you must ensure that all of the pages have actually been added or + * removed from the container as appropriate. + * @param container The containing View which is displaying this adapter's + * page views. + */ + public void finishUpdate(ViewGroup container) { + finishUpdate((View) container); + } + + /** + * Called when a change in the shown pages is going to start being made. + * @param container The containing View which is displaying this adapter's + * page views. + * + * @deprecated Use {@link #startUpdate(ViewGroup)} + */ + @Deprecated + @SuppressWarnings("static-method") + public void startUpdate(View container) { + return; + } + + /** + * Create the page for the given position. The adapter is responsible + * for adding the view to the container given here, although it only + * must ensure this is done by the time it returns from + * {@link #finishUpdate(ViewGroup)}. + * + * @param container The containing View in which the page will be shown. + * @param position The page position to be instantiated. + * @return Returns an Object representing the new page. This does not + * need to be a View, but can be some other container of the page. + * + * @deprecated Use {@link #instantiateItem(ViewGroup, int)} + */ + @Deprecated + @SuppressWarnings("static-method") + public Object instantiateItem(View container, int position) { + throw new UnsupportedOperationException( + "Required method instantiateItem was not overridden"); + } + + /** + * Remove a page for the given position. The adapter is responsible + * for removing the view from its container, although it only must ensure + * this is done by the time it returns from {@link #finishUpdate(View)}. + * + * @param container The containing View from which the page will be removed. + * @param position The page position to be removed. + * @param object The same object that was returned by + * {@link #instantiateItem(View, int)}. + * + * @deprecated Use {@link #destroyItem(ViewGroup, int, Object)} + */ + @Deprecated + @SuppressWarnings("static-method") + public void destroyItem(View container, int position, Object object) { + throw new UnsupportedOperationException("Required method destroyItem was not overridden"); + } + + /** + * Called to inform the adapter of which item is currently considered to + * be the "primary", that is the one show to the user as the current page. + * + * @param container The containing View from which the page will be removed. + * @param position The page position that is now the primary. + * @param object The same object that was returned by + * {@link #instantiateItem(View, int)}. + * + * @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)} + */ + @Deprecated + @SuppressWarnings("static-method") + public void setPrimaryItem(View container, int position, Object object) { + return; + } + + /** + * Called when the a change in the shown pages has been completed. At this + * point you must ensure that all of the pages have actually been added or + * removed from the container as appropriate. + * @param container The containing View which is displaying this adapter's + * page views. + * + * @deprecated Use {@link #finishUpdate(ViewGroup)} + */ + @Deprecated + @SuppressWarnings("static-method") + public void finishUpdate(View container) { + return; + } + + /** + * Determines whether a page View is associated with a specific key object + * as returned by {@link #instantiateItem(ViewGroup, int)}. This method is + * required for a PagerAdapter to function properly. + * + * @param view Page View to check for association with <code>object</code> + * @param object Object to check for association with <code>view</code> + * @return true if <code>view</code> is associated with the key object <code>object</code> + */ + public abstract boolean isViewFromObject(View view, Object object); + + /** + * Save any instance state associated with this adapter and its pages that should be + * restored if the current UI state needs to be reconstructed. + * + * @return Saved state for this adapter + */ + @SuppressWarnings("static-method") + public Parcelable saveState() { + return null; + } + + /** + * Restore any instance state associated with this adapter and its pages + * that was previously saved by {@link #saveState()}. + * + * @param state State previously saved by a call to {@link #saveState()} + * @param loader A ClassLoader that should be used to instantiate any restored objects + */ + @SuppressWarnings("static-method") + public void restoreState(Parcelable state, ClassLoader loader) { + return; + } + + /** + * Called when the host view is attempting to determine if an item's position + * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given + * item has not changed or {@link #POSITION_NONE} if the item is no longer present + * in the adapter. + * + * <p>The default implementation assumes that items will never + * change position and always returns {@link #POSITION_UNCHANGED}. + * + * @param object Object representing an item, previously returned by a call to + * {@link #instantiateItem(View, int)}. + * @return object's new position index from [0, {@link #getCount()}), + * {@link #POSITION_UNCHANGED} if the object's position has not changed, + * or {@link #POSITION_NONE} if the item is no longer present. + */ + @SuppressWarnings("static-method") + public int getItemPosition(Object object) { + return POSITION_UNCHANGED; + } + + /** + * This method should be called by the application if the data backing this adapter has changed + * and associated views should update. + */ + public void notifyDataSetChanged() { + mObservable.notifyChanged(); + } + + /** + * Register an observer to receive callbacks related to the adapter's data changing. + * + * @param observer The {@link android.database.DataSetObserver} which will receive callbacks. + */ + public void registerDataSetObserver(DataSetObserver observer) { + mObservable.registerObserver(observer); + } + + /** + * Unregister an observer from callbacks related to the adapter's data changing. + * + * @param observer The {@link android.database.DataSetObserver} which will be unregistered. + */ + public void unregisterDataSetObserver(DataSetObserver observer) { + mObservable.unregisterObserver(observer); + } + + /** + * This method may be called by the ViewPager to obtain a title string + * to describe the specified page. This method may return null + * indicating no title for this page. The default implementation returns + * null. + * + * @param position The position of the title requested + * @return A title for the requested page + */ + @SuppressWarnings("static-method") + public CharSequence getPageTitle(int position) { + return null; + } + + /** + * Returns the proportional width of a given page as a percentage of the + * ViewPager's measured width from (0.f-1.f] + * + * @param position The position of the page requested + * @return Proportional width for the given page position + */ + @SuppressWarnings("static-method") + public float getPageWidth(int position) { + return 1.f; + } +} diff --git a/src/android/support/v4/view/VelocityTrackerCompat.java b/src/android/support/v4/view/VelocityTrackerCompat.java new file mode 100644 index 0000000..99286d0 --- /dev/null +++ b/src/android/support/v4/view/VelocityTrackerCompat.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.VelocityTracker; + +/** + * Helper for accessing features in {@link VelocityTracker} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class VelocityTrackerCompat { + /** + * Interface for the full API. + */ + interface VelocityTrackerVersionImpl { + public float getXVelocity(VelocityTracker tracker, int pointerId); + public float getYVelocity(VelocityTracker tracker, int pointerId); + } + + /** + * Interface implementation that doesn't use anything about v4 APIs. + */ + static class BaseVelocityTrackerVersionImpl implements VelocityTrackerVersionImpl { + @Override + public float getXVelocity(VelocityTracker tracker, int pointerId) { + return tracker.getXVelocity(); + } + @Override + public float getYVelocity(VelocityTracker tracker, int pointerId) { + return tracker.getYVelocity(); + } + } + + /** + * Interface implementation for devices with at least v11 APIs. + */ + static class HoneycombVelocityTrackerVersionImpl implements VelocityTrackerVersionImpl { + @Override + public float getXVelocity(VelocityTracker tracker, int pointerId) { + return VelocityTrackerCompatHoneycomb.getXVelocity(tracker, pointerId); + } + @Override + public float getYVelocity(VelocityTracker tracker, int pointerId) { + return VelocityTrackerCompatHoneycomb.getYVelocity(tracker, pointerId); + } + } + + /** + * Select the correct implementation to use for the current platform. + */ + static final VelocityTrackerVersionImpl IMPL; + static { + if (android.os.Build.VERSION.SDK_INT >= 11) { + IMPL = new HoneycombVelocityTrackerVersionImpl(); + } else { + IMPL = new BaseVelocityTrackerVersionImpl(); + } + } + + // ------------------------------------------------------------------- + + /** + * Call {@link VelocityTracker#getXVelocity(int)}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} device, + * returns {@link VelocityTracker#getXVelocity()}. + */ + public static float getXVelocity(VelocityTracker tracker, int pointerId) { + return IMPL.getXVelocity(tracker, pointerId); + } + + /** + * Call {@link VelocityTracker#getYVelocity(int)}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} device, + * returns {@link VelocityTracker#getYVelocity()}. + */ + public static float getYVelocity(VelocityTracker tracker, int pointerId) { + return IMPL.getYVelocity(tracker, pointerId); + } +} diff --git a/src/android/support/v4/view/VelocityTrackerCompatHoneycomb.java b/src/android/support/v4/view/VelocityTrackerCompatHoneycomb.java new file mode 100644 index 0000000..4f9d326 --- /dev/null +++ b/src/android/support/v4/view/VelocityTrackerCompatHoneycomb.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.VelocityTracker; + +/** + * Implementation of velocity tracker compatibility that can call Honeycomb APIs. + */ +class VelocityTrackerCompatHoneycomb { + public static float getXVelocity(VelocityTracker tracker, int pointerId) { + return tracker.getXVelocity(pointerId); + } + public static float getYVelocity(VelocityTracker tracker, int pointerId) { + return tracker.getYVelocity(pointerId); + } +} diff --git a/src/android/support/v4/view/ViewCompat.java b/src/android/support/v4/view/ViewCompat.java new file mode 100644 index 0000000..8dc9965 --- /dev/null +++ b/src/android/support/v4/view/ViewCompat.java @@ -0,0 +1,1112 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; +import android.view.View; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; + +/** + * Helper for accessing features in {@link View} introduced after API + * level 4 in a backwards compatible fashion. + */ +public class ViewCompat { + /** + * Always allow a user to over-scroll this view, provided it is a + * view that can scroll. + */ + public static final int OVER_SCROLL_ALWAYS = 0; + + /** + * Allow a user to over-scroll this view only if the content is large + * enough to meaningfully scroll, provided it is a view that can scroll. + */ + public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; + + /** + * Never allow a user to over-scroll this view. + */ + public static final int OVER_SCROLL_NEVER = 2; + + private static final long FAKE_FRAME_TIME = 10; + + /** + * Automatically determine whether a view is important for accessibility. + */ + public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000; + + /** + * The view is important for accessibility. + */ + public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001; + + /** + * The view is not important for accessibility. + */ + public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002; + + /** + * The view is not important for accessibility, nor are any of its + * descendant views. + */ + public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004; + + /** + * Live region mode specifying that accessibility services should not + * automatically announce changes to this view. This is the default live + * region mode for most views. + * <p> + * Use with {@link ViewCompat#setAccessibilityLiveRegion(View, int)}. + */ + public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000; + + /** + * Live region mode specifying that accessibility services should announce + * changes to this view. + * <p> + * Use with {@link ViewCompat#setAccessibilityLiveRegion(View, int)}. + */ + public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001; + + /** + * Live region mode specifying that accessibility services should interrupt + * ongoing speech to immediately announce changes to this view. + * <p> + * Use with {@link ViewCompat#setAccessibilityLiveRegion(View, int)}. + */ + public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002; + + /** + * Indicates that the view does not have a layer. + */ + public static final int LAYER_TYPE_NONE = 0; + + /** + * <p>Indicates that the view has a software layer. A software layer is backed + * by a bitmap and causes the view to be rendered using Android's software + * rendering pipeline, even if hardware acceleration is enabled.</p> + * + * <p>Software layers have various usages:</p> + * <p>When the application is not using hardware acceleration, a software layer + * is useful to apply a specific color filter and/or blending mode and/or + * translucency to a view and all its children.</p> + * <p>When the application is using hardware acceleration, a software layer + * is useful to render drawing primitives not supported by the hardware + * accelerated pipeline. It can also be used to cache a complex view tree + * into a texture and reduce the complexity of drawing operations. For instance, + * when animating a complex view tree with a translation, a software layer can + * be used to render the view tree only once.</p> + * <p>Software layers should be avoided when the affected view tree updates + * often. Every update will require to re-render the software layer, which can + * potentially be slow (particularly when hardware acceleration is turned on + * since the layer will have to be uploaded into a hardware texture after every + * update.)</p> + */ + public static final int LAYER_TYPE_SOFTWARE = 1; + + /** + * <p>Indicates that the view has a hardware layer. A hardware layer is backed + * by a hardware specific texture (generally Frame Buffer Objects or FBO on + * OpenGL hardware) and causes the view to be rendered using Android's hardware + * rendering pipeline, but only if hardware acceleration is turned on for the + * view hierarchy. When hardware acceleration is turned off, hardware layers + * behave exactly as {@link #LAYER_TYPE_SOFTWARE software layers}.</p> + * + * <p>A hardware layer is useful to apply a specific color filter and/or + * blending mode and/or translucency to a view and all its children.</p> + * <p>A hardware layer can be used to cache a complex view tree into a + * texture and reduce the complexity of drawing operations. For instance, + * when animating a complex view tree with a translation, a hardware layer can + * be used to render the view tree only once.</p> + * <p>A hardware layer can also be used to increase the rendering quality when + * rotation transformations are applied on a view. It can also be used to + * prevent potential clipping issues when applying 3D transforms on a view.</p> + */ + public static final int LAYER_TYPE_HARDWARE = 2; + + /** + * Horizontal layout direction of this view is from Left to Right. + */ + public static final int LAYOUT_DIRECTION_LTR = 0; + + /** + * Horizontal layout direction of this view is from Right to Left. + */ + public static final int LAYOUT_DIRECTION_RTL = 1; + + /** + * Horizontal layout direction of this view is inherited from its parent. + * Use with {@link #setLayoutDirection}. + */ + public static final int LAYOUT_DIRECTION_INHERIT = 2; + + /** + * Horizontal layout direction of this view is from deduced from the default language + * script for the locale. Use with {@link #setLayoutDirection}. + */ + public static final int LAYOUT_DIRECTION_LOCALE = 3; + + /** + * Bits of {@link #getMeasuredWidthAndState} and + * {@link #getMeasuredWidthAndState} that provide the actual measured size. + */ + public static final int MEASURED_SIZE_MASK = 0x00ffffff; + + /** + * Bits of {@link #getMeasuredWidthAndState} and + * {@link #getMeasuredWidthAndState} that provide the additional state bits. + */ + public static final int MEASURED_STATE_MASK = 0xff000000; + + /** + * Bit shift of {@link #MEASURED_STATE_MASK} to get to the height bits + * for functions that combine both width and height into a single int, + * such as {@link #getMeasuredState} and the childState argument of + * {@link #resolveSizeAndState(int, int, int)}. + */ + public static final int MEASURED_HEIGHT_STATE_SHIFT = 16; + + /** + * Bit of {@link #getMeasuredWidthAndState} and + * {@link #getMeasuredWidthAndState} that indicates the measured size + * is smaller that the space the view would like to have. + */ + public static final int MEASURED_STATE_TOO_SMALL = 0x01000000; + + interface ViewCompatImpl { + public boolean canScrollHorizontally(View v, int direction); + public boolean canScrollVertically(View v, int direction); + public int getOverScrollMode(View v); + public void setOverScrollMode(View v, int mode); + public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event); + public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event); + public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info); + public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate); + public boolean hasTransientState(View view); + public void setHasTransientState(View view, boolean hasTransientState); + public void postInvalidateOnAnimation(View view); + public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom); + public void postOnAnimation(View view, Runnable action); + public void postOnAnimationDelayed(View view, Runnable action, long delayMillis); + public int getImportantForAccessibility(View view); + public void setImportantForAccessibility(View view, int mode); + public boolean performAccessibilityAction(View view, int action, Bundle arguments); + public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view); + public float getAlpha(View view); + public void setLayerType(View view, int layerType, Paint paint); + public int getLayerType(View view); + public int getLabelFor(View view); + public void setLabelFor(View view, int id); + public void setLayerPaint(View view, Paint paint); + public int getLayoutDirection(View view); + public void setLayoutDirection(View view, int layoutDirection); + public ViewParent getParentForAccessibility(View view); + public boolean isOpaque(View view); + public int resolveSizeAndState(int size, int measureSpec, int childMeasuredState); + public int getMeasuredWidthAndState(View view); + public int getMeasuredHeightAndState(View view); + public int getMeasuredState(View view); + public int getAccessibilityLiveRegion(View view); + public void setAccessibilityLiveRegion(View view, int mode); + } + + static class BaseViewCompatImpl implements ViewCompatImpl { + @Override + public boolean canScrollHorizontally(View v, int direction) { + return false; + } + @Override + public boolean canScrollVertically(View v, int direction) { + return false; + } + @Override + public int getOverScrollMode(View v) { + return OVER_SCROLL_NEVER; + } + @Override + public void setOverScrollMode(View v, int mode) { + // Do nothing; API doesn't exist + } + @Override + public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) { + // Do nothing; API doesn't exist + } + @Override + public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { + // Do nothing; API doesn't exist + } + @Override + public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { + // Do nothing; API doesn't exist + } + @Override + public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) { + // Do nothing; API doesn't exist + } + @Override + public boolean hasTransientState(View view) { + // A view can't have transient state if transient state wasn't supported. + return false; + } + @Override + public void setHasTransientState(View view, boolean hasTransientState) { + // Do nothing; API doesn't exist + } + @Override + public void postInvalidateOnAnimation(View view) { + view.postInvalidateDelayed(getFrameTime()); + } + @Override + public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) { + view.postInvalidateDelayed(getFrameTime(), left, top, right, bottom); + } + @Override + public void postOnAnimation(View view, Runnable action) { + view.postDelayed(action, getFrameTime()); + } + @Override + public void postOnAnimationDelayed(View view, Runnable action, long delayMillis) { + view.postDelayed(action, getFrameTime() + delayMillis); + } + @SuppressWarnings("static-method") + long getFrameTime() { + return FAKE_FRAME_TIME; + } + @Override + public int getImportantForAccessibility(View view) { + return 0; + } + @Override + public void setImportantForAccessibility(View view, int mode) { + return; + } + @Override + public boolean performAccessibilityAction(View view, int action, Bundle arguments) { + return false; + } + @Override + public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view) { + return null; + } + @Override + public float getAlpha(View view) { + return 1.0f; + } + @Override + public void setLayerType(View view, int layerType, Paint paint) { + // No-op until layers became available (HC) + } + @Override + public int getLayerType(View view) { + return LAYER_TYPE_NONE; + } + @Override + public int getLabelFor(View view) { + return 0; + } + @Override + public void setLabelFor(View view, int id) { + return; + } + @Override + public void setLayerPaint(View view, Paint p) { + // No-op until layers became available (HC) + } + + @Override + public int getLayoutDirection(View view) { + return LAYOUT_DIRECTION_LTR; + } + + @Override + public void setLayoutDirection(View view, int layoutDirection) { + // No-op + } + + @Override + public ViewParent getParentForAccessibility(View view) { + return view.getParent(); + } + + @Override + public boolean isOpaque(View view) { + final Drawable bg = view.getBackground(); + if (bg != null) { + return bg.getOpacity() == PixelFormat.OPAQUE; + } + return false; + } + + @Override + public int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { + return View.resolveSize(size, measureSpec); + } + + @Override + public int getMeasuredWidthAndState(View view) { + return view.getMeasuredWidth(); + } + + @Override + public int getMeasuredHeightAndState(View view) { + return view.getMeasuredHeight(); + } + + @Override + public int getMeasuredState(View view) { + return 0; + } + + @Override + public int getAccessibilityLiveRegion(View view) { + return ACCESSIBILITY_LIVE_REGION_NONE; + } + + @Override + public void setAccessibilityLiveRegion(View view, int mode) { + // No-op + } + } + + static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl { + @Override + public boolean isOpaque(View view) { + return ViewCompatEclairMr1.isOpaque(view); + } + } + + static class GBViewCompatImpl extends EclairMr1ViewCompatImpl { + @Override + public int getOverScrollMode(View v) { + return ViewCompatGingerbread.getOverScrollMode(v); + } + @Override + public void setOverScrollMode(View v, int mode) { + ViewCompatGingerbread.setOverScrollMode(v, mode); + } + } + + static class HCViewCompatImpl extends GBViewCompatImpl { + @Override + long getFrameTime() { + return ViewCompatHC.getFrameTime(); + } + @Override + public float getAlpha(View view) { + return ViewCompatHC.getAlpha(view); + } + @Override + public void setLayerType(View view, int layerType, Paint paint) { + ViewCompatHC.setLayerType(view, layerType, paint); + } + @Override + public int getLayerType(View view) { + return ViewCompatHC.getLayerType(view); + } + @Override + public void setLayerPaint(View view, Paint paint) { + // Make sure the paint is correct; this will be cheap if it's the same + // instance as was used to call setLayerType earlier. + setLayerType(view, getLayerType(view), paint); + // This is expensive, but the only way to accomplish this before JB-MR1. + view.invalidate(); + } + @Override + public int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { + return ViewCompatHC.resolveSizeAndState(size, measureSpec, childMeasuredState); + } + @Override + public int getMeasuredWidthAndState(View view) { + return ViewCompatHC.getMeasuredWidthAndState(view); + } + @Override + public int getMeasuredHeightAndState(View view) { + return ViewCompatHC.getMeasuredHeightAndState(view); + } + @Override + public int getMeasuredState(View view) { + return ViewCompatHC.getMeasuredState(view); + } + } + + static class ICSViewCompatImpl extends HCViewCompatImpl { + @Override + public boolean canScrollHorizontally(View v, int direction) { + return ViewCompatICS.canScrollHorizontally(v, direction); + } + @Override + public boolean canScrollVertically(View v, int direction) { + return ViewCompatICS.canScrollVertically(v, direction); + } + @Override + public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { + ViewCompatICS.onPopulateAccessibilityEvent(v, event); + } + @Override + public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { + ViewCompatICS.onInitializeAccessibilityEvent(v, event); + } + @Override + public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) { + ViewCompatICS.onInitializeAccessibilityNodeInfo(v, info.getInfo()); + } + @Override + public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) { + ViewCompatICS.setAccessibilityDelegate(v, delegate.getBridge()); + } + } + + static final ViewCompatImpl IMPL; + static { + final int version = android.os.Build.VERSION.SDK_INT; + if (version >= 14) { + IMPL = new ICSViewCompatImpl(); + } else if (version >= 11) { + IMPL = new HCViewCompatImpl(); + } else if (version >= 9) { + IMPL = new GBViewCompatImpl(); + } else { + IMPL = new BaseViewCompatImpl(); + } + } + + /** + * Check if this view can be scrolled horizontally in a certain direction. + * + * @param v The View against which to invoke the method. + * @param direction Negative to check scrolling left, positive to check scrolling right. + * @return true if this view can be scrolled in the specified direction, false otherwise. + */ + public static boolean canScrollHorizontally(View v, int direction) { + return IMPL.canScrollHorizontally(v, direction); + } + + /** + * Check if this view can be scrolled vertically in a certain direction. + * + * @param v The View against which to invoke the method. + * @param direction Negative to check scrolling up, positive to check scrolling down. + * @return true if this view can be scrolled in the specified direction, false otherwise. + */ + public static boolean canScrollVertically(View v, int direction) { + return IMPL.canScrollVertically(v, direction); + } + + /** + * Returns the over-scroll mode for this view. The result will be + * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS} + * (allow over-scrolling only if the view content is larger than the container), + * or {@link #OVER_SCROLL_NEVER}. + * + * @param v The View against which to invoke the method. + * @return This view's over-scroll mode. + */ + public static int getOverScrollMode(View v) { + return IMPL.getOverScrollMode(v); + } + + /** + * Set the over-scroll mode for this view. Valid over-scroll modes are + * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS} + * (allow over-scrolling only if the view content is larger than the container), + * or {@link #OVER_SCROLL_NEVER}. + * + * Setting the over-scroll mode of a view will have an effect only if the + * view is capable of scrolling. + * + * @param v The View against which to invoke the method. + * @param overScrollMode The new over-scroll mode for this view. + */ + public static void setOverScrollMode(View v, int overScrollMode) { + IMPL.setOverScrollMode(v, overScrollMode); + } + + /** + * Called from {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} + * giving a chance to this View to populate the accessibility event with its + * text content. While this method is free to modify event + * attributes other than text content, doing so should normally be performed in + * {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)}. + * <p> + * Example: Adding formatted date string to an accessibility event in addition + * to the text added by the super implementation: + * <pre> public void onPopulateAccessibilityEvent(AccessibilityEvent event) { + * super.onPopulateAccessibilityEvent(event); + * final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY; + * String selectedDateUtterance = DateUtils.formatDateTime(mContext, + * mCurrentDate.getTimeInMillis(), flags); + * event.getText().add(selectedDateUtterance); + * }</pre> + * <p> + * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling + * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its + * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View, + * AccessibilityEvent)} + * is responsible for handling this call. + * </p> + * <p class="note"><strong>Note:</strong> Always call the super implementation before adding + * information to the event, in case the default implementation has basic information to add. + * </p> + * + * @param v The View against which to invoke the method. + * @param event The accessibility event which to populate. + * + * @see View#sendAccessibilityEvent(int) + * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) + */ + public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { + IMPL.onPopulateAccessibilityEvent(v, event); + } + + /** + * Initializes an {@link AccessibilityEvent} with information about + * this View which is the event source. In other words, the source of + * an accessibility event is the view whose state change triggered firing + * the event. + * <p> + * Example: Setting the password property of an event in addition + * to properties set by the super implementation: + * <pre> public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + * super.onInitializeAccessibilityEvent(event); + * event.setPassword(true); + * }</pre> + * <p> + * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling + * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its + * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View, + * AccessibilityEvent)} + * is responsible for handling this call. + * </p> + * <p class="note"><strong>Note:</strong> Always call the super implementation before adding + * information to the event, in case the default implementation has basic information to add. + * </p> + * + * @param v The View against which to invoke the method. + * @param event The event to initialize. + * + * @see View#sendAccessibilityEvent(int) + * @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) + */ + public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { + IMPL.onInitializeAccessibilityEvent(v, event); + } + + /** + * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information + * about this view. The base implementation sets: + * <ul> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li> + * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li> + * </ul> + * <p> + * Subclasses should override this method, call the super implementation, + * and set additional attributes. + * </p> + * <p> + * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling + * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its + * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View, + * android.view.accessibility.AccessibilityNodeInfo)} + * is responsible for handling this call. + * </p> + * + * @param v The View against which to invoke the method. + * @param info The instance to initialize. + */ + public static void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info) { + IMPL.onInitializeAccessibilityNodeInfo(v, info); + } + + /** + * Sets a delegate for implementing accessibility support via compositon as + * opposed to inheritance. The delegate's primary use is for implementing + * backwards compatible widgets. For more details see + * {@link android.view.View.AccessibilityDelegate}. + * + * @param v The View against which to invoke the method. + * @param delegate The delegate instance. + * + * @see android.view.View.AccessibilityDelegate + */ + public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) { + IMPL.setAccessibilityDelegate(v, delegate); + } + + /** + * Indicates whether the view is currently tracking transient state that the + * app should not need to concern itself with saving and restoring, but that + * the framework should take special note to preserve when possible. + * + * @param view View to check for transient state + * @return true if the view has transient state + */ + public static boolean hasTransientState(View view) { + return IMPL.hasTransientState(view); + } + + /** + * Set whether this view is currently tracking transient state that the + * framework should attempt to preserve when possible. + * + * @param view View tracking transient state + * @param hasTransientState true if this view has transient state + */ + public static void setHasTransientState(View view, boolean hasTransientState) { + IMPL.setHasTransientState(view, hasTransientState); + } + + /** + * <p>Cause an invalidate to happen on the next animation time step, typically the + * next display frame.</p> + * + * <p>This method can be invoked from outside of the UI thread + * only when this View is attached to a window.</p> + * + * @param view View to invalidate + */ + public static void postInvalidateOnAnimation(View view) { + IMPL.postInvalidateOnAnimation(view); + } + + /** + * <p>Cause an invalidate of the specified area to happen on the next animation + * time step, typically the next display frame.</p> + * + * <p>This method can be invoked from outside of the UI thread + * only when this View is attached to a window.</p> + * + * @param view View to invalidate + * @param left The left coordinate of the rectangle to invalidate. + * @param top The top coordinate of the rectangle to invalidate. + * @param right The right coordinate of the rectangle to invalidate. + * @param bottom The bottom coordinate of the rectangle to invalidate. + */ + public static void postInvalidateOnAnimation(View view, int left, int top, + int right, int bottom) { + IMPL.postInvalidateOnAnimation(view, left, top, right, bottom); + } + + /** + * <p>Causes the Runnable to execute on the next animation time step. + * The runnable will be run on the user interface thread.</p> + * + * <p>This method can be invoked from outside of the UI thread + * only when this View is attached to a window.</p> + * + * @param view View to post this Runnable to + * @param action The Runnable that will be executed. + */ + public static void postOnAnimation(View view, Runnable action) { + IMPL.postOnAnimation(view, action); + } + + /** + * <p>Causes the Runnable to execute on the next animation time step, + * after the specified amount of time elapses. + * The runnable will be run on the user interface thread.</p> + * + * <p>This method can be invoked from outside of the UI thread + * only when this View is attached to a window.</p> + * + * @param view The view to post this Runnable to + * @param action The Runnable that will be executed. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + */ + public static void postOnAnimationDelayed(View view, Runnable action, long delayMillis) { + IMPL.postOnAnimationDelayed(view, action, delayMillis); + } + + /** + * Gets the mode for determining whether this View is important for accessibility + * which is if it fires accessibility events and if it is reported to + * accessibility services that query the screen. + * + * @param view The view whose property to get. + * @return The mode for determining whether a View is important for accessibility. + * + * @see #IMPORTANT_FOR_ACCESSIBILITY_YES + * @see #IMPORTANT_FOR_ACCESSIBILITY_NO + * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO + */ + public static int getImportantForAccessibility(View view) { + return IMPL.getImportantForAccessibility(view); + } + + /** + * Sets how to determine whether this view is important for accessibility + * which is if it fires accessibility events and if it is reported to + * accessibility services that query the screen. + * + * @param view The view whose property to set. + * @param mode How to determine whether this view is important for accessibility. + * + * @see #IMPORTANT_FOR_ACCESSIBILITY_YES + * @see #IMPORTANT_FOR_ACCESSIBILITY_NO + * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO + */ + public static void setImportantForAccessibility(View view, int mode) { + IMPL.setImportantForAccessibility(view, mode); + } + + /** + * Performs the specified accessibility action on the view. For + * possible accessibility actions look at {@link AccessibilityNodeInfoCompat}. + * <p> + * If an {@link AccessibilityDelegateCompat} has been specified via calling + * {@link #setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its + * {@link AccessibilityDelegateCompat#performAccessibilityAction(View, int, Bundle)} + * is responsible for handling this call. + * </p> + * + * @param action The action to perform. + * @param arguments Optional action arguments. + * @return Whether the action was performed. + */ + public static boolean performAccessibilityAction(View view, int action, Bundle arguments) { + return IMPL.performAccessibilityAction(view, action, arguments); + } + + /** + * Gets the provider for managing a virtual view hierarchy rooted at this View + * and reported to {@link android.accessibilityservice.AccessibilityService}s + * that explore the window content. + * <p> + * If this method returns an instance, this instance is responsible for managing + * {@link AccessibilityNodeInfoCompat}s describing the virtual sub-tree rooted at + * this View including the one representing the View itself. Similarly the returned + * instance is responsible for performing accessibility actions on any virtual + * view or the root view itself. + * </p> + * <p> + * If an {@link AccessibilityDelegateCompat} has been specified via calling + * {@link #setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its + * {@link AccessibilityDelegateCompat#getAccessibilityNodeProvider(View)} + * is responsible for handling this call. + * </p> + * + * @param view The view whose property to get. + * @return The provider. + * + * @see AccessibilityNodeProviderCompat + */ + public static AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view) { + return IMPL.getAccessibilityNodeProvider(view); + } + + /** + * The opacity of the view. This is a value from 0 to 1, where 0 means the view is + * completely transparent and 1 means the view is completely opaque. + * + * <p>By default this is 1.0f. Prior to API 11, the returned value is always 1.0f. + * @return The opacity of the view. + */ + public static float getAlpha(View view) { + return IMPL.getAlpha(view); + } + + /** + * <p>Specifies the type of layer backing this view. The layer can be + * {@link #LAYER_TYPE_NONE disabled}, {@link #LAYER_TYPE_SOFTWARE software} or + * {@link #LAYER_TYPE_HARDWARE hardware}.</p> + * + * <p>A layer is associated with an optional {@link android.graphics.Paint} + * instance that controls how the layer is composed on screen. The following + * properties of the paint are taken into account when composing the layer:</p> + * <ul> + * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li> + * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li> + * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li> + * </ul> + * + * <p>If this view has an alpha value set to < 1.0 by calling + * setAlpha(float), the alpha value of the layer's paint is replaced by + * this view's alpha value. Calling setAlpha(float) is therefore + * equivalent to setting a hardware layer on this view and providing a paint with + * the desired alpha value.<p> + * + * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE disabled}, + * {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware} + * for more information on when and how to use layers.</p> + * + * @param layerType The ype of layer to use with this view, must be one of + * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or + * {@link #LAYER_TYPE_HARDWARE} + * @param paint The paint used to compose the layer. This argument is optional + * and can be null. It is ignored when the layer type is + * {@link #LAYER_TYPE_NONE} + */ + public static void setLayerType(View view, int layerType, Paint paint) { + IMPL.setLayerType(view, layerType, paint); + } + + /** + * Indicates what type of layer is currently associated with this view. By default + * a view does not have a layer, and the layer type is {@link #LAYER_TYPE_NONE}. + * Refer to the documentation of + * {@link #setLayerType(android.view.View, int, android.graphics.Paint)} + * for more information on the different types of layers. + * + * @param view The view to fetch the layer type from + * @return {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or + * {@link #LAYER_TYPE_HARDWARE} + * + * @see #setLayerType(android.view.View, int, android.graphics.Paint) + * @see #LAYER_TYPE_NONE + * @see #LAYER_TYPE_SOFTWARE + * @see #LAYER_TYPE_HARDWARE + */ + public static int getLayerType(View view) { + return IMPL.getLayerType(view); + } + + /** + * Gets the id of a view for which a given view serves as a label for + * accessibility purposes. + * + * @param view The view on which to invoke the corresponding method. + * @return The labeled view id. + */ + public static int getLabelFor(View view) { + return IMPL.getLabelFor(view); + } + + /** + * Sets the id of a view for which a given view serves as a label for + * accessibility purposes. + * + * @param view The view on which to invoke the corresponding method. + * @param labeledId The labeled view id. + */ + public static void setLabelFor(View view, int labeledId) { + IMPL.setLabelFor(view, labeledId); + } + + /** + * Updates the {@link Paint} object used with the current layer (used only if the current + * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint + * provided to {@link #setLayerType(android.view.View, int, android.graphics.Paint)} + * will be used the next time the View is redrawn, but + * {@link #setLayerPaint(android.view.View, android.graphics.Paint)} + * must be called to ensure that the view gets redrawn immediately. + * + * <p>A layer is associated with an optional {@link android.graphics.Paint} + * instance that controls how the layer is composed on screen. The following + * properties of the paint are taken into account when composing the layer:</p> + * <ul> + * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li> + * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li> + * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li> + * </ul> + * + * <p>If this view has an alpha value set to < 1.0 by calling + * View#setAlpha(float), the alpha value of the layer's paint is replaced by + * this view's alpha value. Calling View#setAlpha(float) is therefore + * equivalent to setting a hardware layer on this view and providing a paint with + * the desired alpha value.</p> + * + * @param view View to set a layer paint for + * @param paint The paint used to compose the layer. This argument is optional + * and can be null. It is ignored when the layer type is + * {@link #LAYER_TYPE_NONE} + * + * @see #setLayerType(View, int, android.graphics.Paint) + */ + public static void setLayerPaint(View view, Paint paint) { + IMPL.setLayerPaint(view, paint); + } + + /** + * Returns the resolved layout direction for this view. + * + * @param view View to get layout direction for + * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns + * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL. + * + * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version + * is lower than Jellybean MR1 (API 17) + */ + public static int getLayoutDirection(View view) { + return IMPL.getLayoutDirection(view); + } + + /** + * Set the layout direction for this view. This will propagate a reset of layout direction + * resolution to the view's children and resolve layout direction for this view. + * + * @param view View to set layout direction for + * @param layoutDirection the layout direction to set. Should be one of: + * + * {@link #LAYOUT_DIRECTION_LTR}, + * {@link #LAYOUT_DIRECTION_RTL}, + * {@link #LAYOUT_DIRECTION_INHERIT}, + * {@link #LAYOUT_DIRECTION_LOCALE}. + * + * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution + * proceeds up the parent chain of the view to get the value. If there is no parent, then it + * will return the default {@link #LAYOUT_DIRECTION_LTR}. + */ + public static void setLayoutDirection(View view, int layoutDirection) { + IMPL.setLayoutDirection(view, layoutDirection); + } + + /** + * Gets the parent for accessibility purposes. Note that the parent for + * accessibility is not necessary the immediate parent. It is the first + * predecessor that is important for accessibility. + * + * @param view View to retrieve parent for + * @return The parent for use in accessibility inspection + */ + public static ViewParent getParentForAccessibility(View view) { + return IMPL.getParentForAccessibility(view); + } + + /** + * Indicates whether this View is opaque. An opaque View guarantees that it will + * draw all the pixels overlapping its bounds using a fully opaque color. + * + * On API 7 and above this will call View's true isOpaque method. On previous platform + * versions it will check the opacity of the view's background drawable if present. + * + * @return True if this View is guaranteed to be fully opaque, false otherwise. + */ + public static boolean isOpaque(View view) { + return IMPL.isOpaque(view); + } + + /** + * Utility to reconcile a desired size and state, with constraints imposed + * by a MeasureSpec. Will take the desired size, unless a different size + * is imposed by the constraints. The returned value is a compound integer, + * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and + * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting + * size is smaller than the size the view wants to be. + * + * @param size How big the view wants to be + * @param measureSpec Constraints imposed by the parent + * @return Size information bit mask as defined by + * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}. + */ + public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { + return IMPL.resolveSizeAndState(size, measureSpec, childMeasuredState); + } + + /** + * Return the full width measurement information for this view as computed + * by the most recent call to {@link android.view.View#measure(int, int)}. + * This result is a bit mask as defined by {@link #MEASURED_SIZE_MASK} and + * {@link #MEASURED_STATE_TOO_SMALL}. + * This should be used during measurement and layout calculations only. Use + * {@link android.view.View#getWidth()} to see how wide a view is after layout. + * + * @return The measured width of this view as a bit mask. + */ + public static int getMeasuredWidthAndState(View view) { + return IMPL.getMeasuredWidthAndState(view); + } + + /** + * Return the full height measurement information for this view as computed + * by the most recent call to {@link android.view.View#measure(int, int)}. + * This result is a bit mask as defined by {@link #MEASURED_SIZE_MASK} and + * {@link #MEASURED_STATE_TOO_SMALL}. + * This should be used during measurement and layout calculations only. Use + * {@link android.view.View#getHeight()} to see how wide a view is after layout. + * + * @return The measured width of this view as a bit mask. + */ + public static int getMeasuredHeightAndState(View view) { + return IMPL.getMeasuredHeightAndState(view); + } + + /** + * Return only the state bits of {@link #getMeasuredWidthAndState} + * and {@link #getMeasuredHeightAndState}, combined into one integer. + * The width component is in the regular bits {@link #MEASURED_STATE_MASK} + * and the height component is at the shifted bits + * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}. + */ + public static int getMeasuredState(View view) { + return IMPL.getMeasuredState(view); + } + + /** + * Gets the live region mode for the specified View. + * + * @param view The view from which to obtain the live region mode + * @return The live region mode for the view. + * + * @see ViewCompat#setAccessibilityLiveRegion(View, int) + */ + @SuppressWarnings("static-method") + public int getAccessibilityLiveRegion(View view) { + return IMPL.getAccessibilityLiveRegion(view); + } + + /** + * Sets the live region mode for the specified view. This indicates to + * accessibility services whether they should automatically notify the user + * about changes to the view's content description or text, or to the + * content descriptions or text of the view's children (where applicable). + * <p> + * For example, in a login screen with a TextView that displays an "incorrect + * password" notification, that view should be marked as a live region with + * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. + * <p> + * To disable change notifications for this view, use + * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region + * mode for most views. + * <p> + * To indicate that the user should be notified of changes, use + * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. + * <p> + * If the view's changes should interrupt ongoing speech and notify the user + * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. + * + * @param view The view on which to set the live region mode + * @param mode The live region mode for this view, one of: + * <ul> + * <li>{@link #ACCESSIBILITY_LIVE_REGION_NONE} + * <li>{@link #ACCESSIBILITY_LIVE_REGION_POLITE} + * <li>{@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE} + * </ul> + */ + @SuppressWarnings("static-method") + public void setAccessibilityLiveRegion(View view, int mode) { + IMPL.setAccessibilityLiveRegion(view, mode); + } +} diff --git a/src/android/support/v4/view/ViewCompatEclairMr1.java b/src/android/support/v4/view/ViewCompatEclairMr1.java new file mode 100644 index 0000000..87000dd --- /dev/null +++ b/src/android/support/v4/view/ViewCompatEclairMr1.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 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.support.v4.view; + +import android.view.View; + +class ViewCompatEclairMr1 { + public static boolean isOpaque(View view) { + return view.isOpaque(); + } +} diff --git a/src/android/support/v4/view/ViewCompatGingerbread.java b/src/android/support/v4/view/ViewCompatGingerbread.java new file mode 100644 index 0000000..f410a69 --- /dev/null +++ b/src/android/support/v4/view/ViewCompatGingerbread.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.View; + +class ViewCompatGingerbread { + public static int getOverScrollMode(View v) { + return v.getOverScrollMode(); + } + + public static void setOverScrollMode(View v, int mode) { + v.setOverScrollMode(mode); + } +} diff --git a/src/android/support/v4/view/ViewCompatHC.java b/src/android/support/v4/view/ViewCompatHC.java new file mode 100644 index 0000000..a237831 --- /dev/null +++ b/src/android/support/v4/view/ViewCompatHC.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.v4.view; + +import android.animation.ValueAnimator; +import android.graphics.Paint; +import android.view.View; + +class ViewCompatHC { + static long getFrameTime() { + return ValueAnimator.getFrameDelay(); + } + + public static float getAlpha(View view) { + return view.getAlpha(); + } + + public static void setLayerType(View view, int layerType, Paint paint) { + view.setLayerType(layerType, paint); + } + + public static int getLayerType(View view) { + return view.getLayerType(); + } + + public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { + return View.resolveSizeAndState(size, measureSpec, childMeasuredState); + } + + public static int getMeasuredWidthAndState(View view) { + return view.getMeasuredWidthAndState(); + } + + public static int getMeasuredHeightAndState(View view) { + return view.getMeasuredHeightAndState(); + } + + public static int getMeasuredState(View view) { + return view.getMeasuredState(); + } +} diff --git a/src/android/support/v4/view/ViewCompatICS.java b/src/android/support/v4/view/ViewCompatICS.java new file mode 100644 index 0000000..82aeaf3 --- /dev/null +++ b/src/android/support/v4/view/ViewCompatICS.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +/** + * Helper for accessing newer features in View introduced in ICS. + */ +class ViewCompatICS { + + public static boolean canScrollHorizontally(View v, int direction) { + return v.canScrollHorizontally(direction); + } + + public static boolean canScrollVertically(View v, int direction) { + return v.canScrollVertically(direction); + } + + public static void setAccessibilityDelegate(View v, Object delegate) { + v.setAccessibilityDelegate((AccessibilityDelegate) delegate); + } + + public static void onPopulateAccessibilityEvent(View v, AccessibilityEvent event) { + v.onPopulateAccessibilityEvent(event); + } + + public static void onInitializeAccessibilityEvent(View v, AccessibilityEvent event) { + v.onInitializeAccessibilityEvent(event); + } + + public static void onInitializeAccessibilityNodeInfo(View v, Object info) { + v.onInitializeAccessibilityNodeInfo((AccessibilityNodeInfo) info); + } +} diff --git a/src/android/support/v4/view/ViewConfigurationCompat.java b/src/android/support/v4/view/ViewConfigurationCompat.java new file mode 100644 index 0000000..fd26a27 --- /dev/null +++ b/src/android/support/v4/view/ViewConfigurationCompat.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.ViewConfiguration; + +/** + * Helper for accessing features in {@link ViewConfiguration} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class ViewConfigurationCompat { + /** + * Interface for the full API. + */ + interface ViewConfigurationVersionImpl { + public int getScaledPagingTouchSlop(ViewConfiguration config); + } + + /** + * Interface implementation that doesn't use anything about v4 APIs. + */ + static class BaseViewConfigurationVersionImpl implements ViewConfigurationVersionImpl { + @Override + public int getScaledPagingTouchSlop(ViewConfiguration config) { + return config.getScaledTouchSlop(); + } + } + + /** + * Interface implementation for devices with at least v11 APIs. + */ + static class FroyoViewConfigurationVersionImpl implements ViewConfigurationVersionImpl { + @Override + public int getScaledPagingTouchSlop(ViewConfiguration config) { + return ViewConfigurationCompatFroyo.getScaledPagingTouchSlop(config); + } + } + + /** + * Select the correct implementation to use for the current platform. + */ + static final ViewConfigurationVersionImpl IMPL; + static { + if (android.os.Build.VERSION.SDK_INT >= 11) { + IMPL = new FroyoViewConfigurationVersionImpl(); + } else { + IMPL = new BaseViewConfigurationVersionImpl(); + } + } + + // ------------------------------------------------------------------- + + /** + * Call {@link ViewConfiguration#getScaledPagingTouchSlop()}. + * If running on a pre-{@link android.os.Build.VERSION_CODES#FROYO} device, + * returns {@link ViewConfiguration#getScaledTouchSlop()}. + */ + public static int getScaledPagingTouchSlop(ViewConfiguration config) { + return IMPL.getScaledPagingTouchSlop(config); + } +} diff --git a/src/android/support/v4/view/ViewConfigurationCompatFroyo.java b/src/android/support/v4/view/ViewConfigurationCompatFroyo.java new file mode 100644 index 0000000..e97a2a2 --- /dev/null +++ b/src/android/support/v4/view/ViewConfigurationCompatFroyo.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.view.ViewConfiguration; + +/** + * Implementation of menu compatibility that can call Honeycomb APIs. + */ +class ViewConfigurationCompatFroyo { + public static int getScaledPagingTouchSlop(ViewConfiguration config) { + return config.getScaledPagingTouchSlop(); + } +} diff --git a/src/android/support/v4/view/ViewPager.java b/src/android/support/v4/view/ViewPager.java new file mode 100644 index 0000000..b67545d --- /dev/null +++ b/src/android/support/v4/view/ViewPager.java @@ -0,0 +1,2908 @@ +/* + * Copyright (C) 2011 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.support.v4.view; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.support.v4.os.ParcelableCompat; +import android.support.v4.os.ParcelableCompatCreatorCallbacks; +import android.support.v4.view.accessibility.AccessibilityEventCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityRecordCompat; +import android.support.v4.widget.EdgeEffectCompat; +import android.util.AttributeSet; +import android.util.Log; +import android.view.FocusFinder; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Layout manager that allows the user to flip left and right + * through pages of data. You supply an implementation of a + * {@link PagerAdapter} to generate the pages that the view shows. + * + * <p>Note this class is currently under early design and + * development. The API will likely change in later updates of + * the compatibility library, requiring changes to the source code + * of apps when they are compiled against the newer version.</p> + * + * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment}, + * which is a convenient way to supply and manage the lifecycle of each page. + * There are standard adapters implemented for using fragments with the ViewPager, + * which cover the most common use cases. These are + * {@link android.support.v4.app.FragmentPagerAdapter} and + * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these + * classes have simple code showing how to build a full user interface + * with them. + * + * <p>Here is a more complicated example of ViewPager, using it in conjuction + * with {@link android.app.ActionBar} tabs. You can find other examples of using + * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code. + * + * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java + * complete} + */ +@SuppressWarnings("javadoc") +public class ViewPager extends ViewGroup { + private static final String TAG = "ViewPager"; + private static final boolean DEBUG = false; + + private static final boolean USE_CACHE = false; + + private static final int DEFAULT_OFFSCREEN_PAGES = 1; + private static final int MAX_SETTLE_DURATION = 600; // ms + private static final int MIN_DISTANCE_FOR_FLING = 25; // dips + + private static final int DEFAULT_GUTTER_SIZE = 16; // dips + + private static final int MIN_FLING_VELOCITY = 400; // dips + + private static final int[] LAYOUT_ATTRS = new int[] { + android.R.attr.layout_gravity + }; + + /** + * Used to track what the expected number of items in the adapter should be. + * If the app changes this when we don't expect it, we'll throw a big obnoxious exception. + */ + private int mExpectedAdapterCount; + + static class ItemInfo { + Object object; + int position; + boolean scrolling; + float widthFactor; + float offset; + } + + private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){ + @Override + public int compare(ItemInfo lhs, ItemInfo rhs) { + return lhs.position - rhs.position; + } + }; + + private static final Interpolator sInterpolator = new Interpolator() { + @Override + public float getInterpolation(float t) { + float tEx = t; + tEx -= 1.0f; + return tEx * tEx * tEx * tEx * tEx + 1.0f; + } + }; + + private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); + private final ItemInfo mTempItem = new ItemInfo(); + + private final Rect mTempRect = new Rect(); + + private PagerAdapter mAdapter; + private int mCurItem; // Index of currently displayed page. + private int mRestoredCurItem = -1; + private Parcelable mRestoredAdapterState = null; + private ClassLoader mRestoredClassLoader = null; + private Scroller mScroller; + private PagerObserver mObserver; + + private int mPageMargin; + private Drawable mMarginDrawable; + private int mTopPageBounds; + private int mBottomPageBounds; + + // Offsets of the first and last items, if known. + // Set during population, used to determine if we are at the beginning + // or end of the pager data set during touch scrolling. + private float mFirstOffset = -Float.MAX_VALUE; + private float mLastOffset = Float.MAX_VALUE; + + private int mChildWidthMeasureSpec; + private int mChildHeightMeasureSpec; + private boolean mInLayout; + + private boolean mScrollingCacheEnabled; + + private boolean mPopulatePending; + private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; + + private boolean mIsBeingDragged; + private boolean mIsUnableToDrag; + private int mDefaultGutterSize; + private int mGutterSize; + private int mTouchSlop; + /** + * Position of the last motion event. + */ + private float mLastMotionX; + private float mLastMotionY; + private float mInitialMotionX; + private float mInitialMotionY; + /** + * ID of the active pointer. This is used to retain consistency during + * drags/flings if multiple pointers are used. + */ + private int mActivePointerId = INVALID_POINTER; + /** + * Sentinel value for no current active pointer. + * Used by {@link #mActivePointerId}. + */ + private static final int INVALID_POINTER = -1; + + /** + * Determines speed during touch scrolling + */ + private VelocityTracker mVelocityTracker; + private int mMinimumVelocity; + private int mMaximumVelocity; + private int mFlingDistance; + private int mCloseEnough; + + // If the pager is at least this close to its final position, complete the scroll + // on touch down and let the user interact with the content inside instead of + // "catching" the flinging pager. + private static final int CLOSE_ENOUGH = 2; // dp + + private boolean mFakeDragging; + private long mFakeDragBeginTime; + + private EdgeEffectCompat mLeftEdge; + private EdgeEffectCompat mRightEdge; + + private boolean mFirstLayout = true; + private boolean mCalledSuper; + private int mDecorChildCount; + + private OnPageChangeListener mOnPageChangeListener; + private OnPageChangeListener mInternalPageChangeListener; + private OnAdapterChangeListener mAdapterChangeListener; + private PageTransformer mPageTransformer; + private Method mSetChildrenDrawingOrderEnabled; + + private static final int DRAW_ORDER_DEFAULT = 0; + private static final int DRAW_ORDER_FORWARD = 1; + private static final int DRAW_ORDER_REVERSE = 2; + private int mDrawingOrder; + private ArrayList<View> mDrawingOrderedChildren; + private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator(); + + /** + * Indicates that the pager is in an idle, settled state. The current page + * is fully in view and no animation is in progress. + */ + public static final int SCROLL_STATE_IDLE = 0; + + /** + * Indicates that the pager is currently being dragged by the user. + */ + public static final int SCROLL_STATE_DRAGGING = 1; + + /** + * Indicates that the pager is in the process of settling to a final position. + */ + public static final int SCROLL_STATE_SETTLING = 2; + + private final Runnable mEndScrollRunnable = new Runnable() { + @Override + @SuppressWarnings("synthetic-access") + public void run() { + setScrollState(SCROLL_STATE_IDLE); + populate(); + } + }; + + private int mScrollState = SCROLL_STATE_IDLE; + + /** + * Callback interface for responding to changing state of the selected page. + */ + public interface OnPageChangeListener { + + /** + * This method will be invoked when the current page is scrolled, either as part + * of a programmatically initiated smooth scroll or a user initiated touch scroll. + * + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is nonzero. + * @param positionOffset Value from [0, 1) indicating the offset from the page at position. + * @param positionOffsetPixels Value in pixels indicating the offset from position. + */ + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); + + /** + * This method will be invoked when a new page becomes selected. Animation is not + * necessarily complete. + * + * @param position Position index of the new selected page. + */ + public void onPageSelected(int position); + + /** + * Called when the scroll state changes. Useful for discovering when the user + * begins dragging, when the pager is automatically settling to the current page, + * or when it is fully stopped/idle. + * + * @param state The new scroll state. + * @see ViewPager#SCROLL_STATE_IDLE + * @see ViewPager#SCROLL_STATE_DRAGGING + * @see ViewPager#SCROLL_STATE_SETTLING + */ + public void onPageScrollStateChanged(int state); + } + + /** + * Simple implementation of the {@link OnPageChangeListener} interface with stub + * implementations of each method. Extend this if you do not intend to override + * every method of {@link OnPageChangeListener}. + */ + public static class SimpleOnPageChangeListener implements OnPageChangeListener { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + // This space for rent + } + + @Override + public void onPageSelected(int position) { + // This space for rent + } + + @Override + public void onPageScrollStateChanged(int state) { + // This space for rent + } + } + + /** + * A PageTransformer is invoked whenever a visible/attached page is scrolled. + * This offers an opportunity for the application to apply a custom transformation + * to the page views using animation properties. + * + * <p>As property animation is only supported as of Android 3.0 and forward, + * setting a PageTransformer on a ViewPager on earlier platform versions will + * be ignored.</p> + */ + public interface PageTransformer { + /** + * Apply a property transformation to the given page. + * + * @param page Apply the transformation to this page + * @param position Position of page relative to the current front-and-center + * position of the pager. 0 is front and center. 1 is one full + * page position to the right, and -1 is one page position to the left. + */ + public void transformPage(View page, float position); + } + + /** + * Used internally to monitor when adapters are switched. + */ + interface OnAdapterChangeListener { + public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); + } + + /** + * Used internally to tag special types of child views that should be added as + * pager decorations by default. + */ + interface Decor { + // Empty + } + + public ViewPager(Context context) { + super(context); + initViewPager(); + } + + public ViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + initViewPager(); + } + + void initViewPager() { + setWillNotDraw(false); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + setFocusable(true); + final Context context = getContext(); + mScroller = new Scroller(context, sInterpolator); + final ViewConfiguration configuration = ViewConfiguration.get(context); + final float density = context.getResources().getDisplayMetrics().density; + + mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); + mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mLeftEdge = new EdgeEffectCompat(context); + mRightEdge = new EdgeEffectCompat(context); + + mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); + mCloseEnough = (int) (CLOSE_ENOUGH * density); + mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density); + + ViewCompat.setAccessibilityDelegate(this, new MyAccessibilityDelegate()); + + if (ViewCompat.getImportantForAccessibility(this) + == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { + ViewCompat.setImportantForAccessibility(this, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); + } + } + + @Override + protected void onDetachedFromWindow() { + removeCallbacks(mEndScrollRunnable); + super.onDetachedFromWindow(); + } + + private void setScrollState(int newState) { + if (mScrollState == newState) { + return; + } + + mScrollState = newState; + if (mPageTransformer != null) { + // PageTransformers can do complex things that benefit from hardware layers. + enableLayers(newState != SCROLL_STATE_IDLE); + } + if (mOnPageChangeListener != null) { + mOnPageChangeListener.onPageScrollStateChanged(newState); + } + } + + /** + * Set a PagerAdapter that will supply views for this pager as needed. + * + * @param adapter Adapter to use + */ + @SuppressWarnings("synthetic-access") + public void setAdapter(PagerAdapter adapter) { + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mObserver); + mAdapter.startUpdate(this); + for (int i = 0; i < mItems.size(); i++) { + final ItemInfo ii = mItems.get(i); + mAdapter.destroyItem(this, ii.position, ii.object); + } + mAdapter.finishUpdate(this); + mItems.clear(); + removeNonDecorViews(); + mCurItem = 0; + scrollTo(0, 0); + } + + final PagerAdapter oldAdapter = mAdapter; + mAdapter = adapter; + mExpectedAdapterCount = 0; + + if (mAdapter != null) { + if (mObserver == null) { + mObserver = new PagerObserver(); + } + mAdapter.registerDataSetObserver(mObserver); + mPopulatePending = false; + final boolean wasFirstLayout = mFirstLayout; + mFirstLayout = true; + mExpectedAdapterCount = mAdapter.getCount(); + if (mRestoredCurItem >= 0) { + mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); + setCurrentItemInternal(mRestoredCurItem, false, true); + mRestoredCurItem = -1; + mRestoredAdapterState = null; + mRestoredClassLoader = null; + } else if (!wasFirstLayout) { + populate(); + } else { + requestLayout(); + } + } + + if (mAdapterChangeListener != null && oldAdapter != adapter) { + mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); + } + } + + private void removeNonDecorViews() { + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (!lp.isDecor) { + removeViewAt(i); + i--; + } + } + } + + /** + * Retrieve the current adapter supplying pages. + * + * @return The currently registered PagerAdapter + */ + public PagerAdapter getAdapter() { + return mAdapter; + } + + void setOnAdapterChangeListener(OnAdapterChangeListener listener) { + mAdapterChangeListener = listener; + } + + private int getClientWidth() { + return getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + } + + /** + * Set the currently selected page. If the ViewPager has already been through its first + * layout with its current adapter there will be a smooth animated transition between + * the current item and the specified item. + * + * @param item Item index to select + */ + public void setCurrentItem(int item) { + mPopulatePending = false; + setCurrentItemInternal(item, !mFirstLayout, false); + } + + /** + * Set the currently selected page. + * + * @param item Item index to select + * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately + */ + public void setCurrentItem(int item, boolean smoothScroll) { + mPopulatePending = false; + setCurrentItemInternal(item, smoothScroll, false); + } + + public int getCurrentItem() { + return mCurItem; + } + + void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { + setCurrentItemInternal(item, smoothScroll, always, 0); + } + + void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { + int itemEx = item; + if (mAdapter == null || mAdapter.getCount() <= 0) { + setScrollingCacheEnabled(false); + return; + } + if (!always && mCurItem == itemEx && mItems.size() != 0) { + setScrollingCacheEnabled(false); + return; + } + + if (itemEx < 0) { + itemEx = 0; + } else if (itemEx >= mAdapter.getCount()) { + itemEx = mAdapter.getCount() - 1; + } + final int pageLimit = mOffscreenPageLimit; + if (itemEx > (mCurItem + pageLimit) || itemEx < (mCurItem - pageLimit)) { + // We are doing a jump by more than one page. To avoid + // glitches, we want to keep all current pages in the view + // until the scroll ends. + for (int i=0; i<mItems.size(); i++) { + mItems.get(i).scrolling = true; + } + } + final boolean dispatchSelected = mCurItem != itemEx; + + if (mFirstLayout) { + // We don't have any idea how big we are yet and shouldn't have any pages either. + // Just set things up and let the pending layout handle things. + mCurItem = itemEx; + if (dispatchSelected && mOnPageChangeListener != null) { + mOnPageChangeListener.onPageSelected(itemEx); + } + if (dispatchSelected && mInternalPageChangeListener != null) { + mInternalPageChangeListener.onPageSelected(itemEx); + } + requestLayout(); + } else { + populate(itemEx); + scrollToItem(itemEx, smoothScroll, velocity, dispatchSelected); + } + } + + private void scrollToItem(int item, boolean smoothScroll, int velocity, + boolean dispatchSelected) { + final ItemInfo curInfo = infoForPosition(item); + int destX = 0; + if (curInfo != null) { + final int width = getClientWidth(); + destX = (int) (width * Math.max(mFirstOffset, + Math.min(curInfo.offset, mLastOffset))); + } + if (smoothScroll) { + smoothScrollTo(destX, 0, velocity); + if (dispatchSelected && mOnPageChangeListener != null) { + mOnPageChangeListener.onPageSelected(item); + } + if (dispatchSelected && mInternalPageChangeListener != null) { + mInternalPageChangeListener.onPageSelected(item); + } + } else { + if (dispatchSelected && mOnPageChangeListener != null) { + mOnPageChangeListener.onPageSelected(item); + } + if (dispatchSelected && mInternalPageChangeListener != null) { + mInternalPageChangeListener.onPageSelected(item); + } + completeScroll(false); + scrollTo(destX, 0); + pageScrolled(destX); + } + } + + /** + * Set a listener that will be invoked whenever the page changes or is incrementally + * scrolled. See {@link OnPageChangeListener}. + * + * @param listener Listener to set + */ + public void setOnPageChangeListener(OnPageChangeListener listener) { + mOnPageChangeListener = listener; + } + + /** + * Set a {@link PageTransformer} that will be called for each attached page whenever + * the scroll position is changed. This allows the application to apply custom property + * transformations to each page, overriding the default sliding look and feel. + * + * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist. + * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p> + * + * @param reverseDrawingOrder true if the supplied PageTransformer requires page views + * to be drawn from last to first instead of first to last. + * @param transformer PageTransformer that will modify each page's animation properties + */ + public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) { + if (Build.VERSION.SDK_INT >= 11) { + final boolean hasTransformer = transformer != null; + final boolean needsPopulate = hasTransformer != (mPageTransformer != null); + mPageTransformer = transformer; + setChildrenDrawingOrderEnabledCompat(hasTransformer); + if (hasTransformer) { + mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD; + } else { + mDrawingOrder = DRAW_ORDER_DEFAULT; + } + if (needsPopulate) populate(); + } + } + + void setChildrenDrawingOrderEnabledCompat(boolean enable) { + if (Build.VERSION.SDK_INT >= 7) { + if (mSetChildrenDrawingOrderEnabled == null) { + try { + mSetChildrenDrawingOrderEnabled = ViewGroup.class.getDeclaredMethod( + "setChildrenDrawingOrderEnabled", new Class[] { Boolean.TYPE }); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Can't find setChildrenDrawingOrderEnabled", e); + } + } + try { + mSetChildrenDrawingOrderEnabled.invoke(this, Boolean.valueOf(enable)); + } catch (Exception e) { + Log.e(TAG, "Error changing children drawing order", e); + } + } + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i; + final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex; + return result; + } + + /** + * Set a separate OnPageChangeListener for internal use by the support library. + * + * @param listener Listener to set + * @return The old listener that was set, if any. + */ + OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { + OnPageChangeListener oldListener = mInternalPageChangeListener; + mInternalPageChangeListener = listener; + return oldListener; + } + + /** + * Returns the number of pages that will be retained to either side of the + * current page in the view hierarchy in an idle state. Defaults to 1. + * + * @return How many pages will be kept offscreen on either side + * @see #setOffscreenPageLimit(int) + */ + public int getOffscreenPageLimit() { + return mOffscreenPageLimit; + } + + /** + * Set the number of pages that should be retained to either side of the + * current page in the view hierarchy in an idle state. Pages beyond this + * limit will be recreated from the adapter when needed. + * + * <p>This is offered as an optimization. If you know in advance the number + * of pages you will need to support or have lazy-loading mechanisms in place + * on your pages, tweaking this setting can have benefits in perceived smoothness + * of paging animations and interaction. If you have a small number of pages (3-4) + * that you can keep active all at once, less time will be spent in layout for + * newly created view subtrees as the user pages back and forth.</p> + * + * <p>You should keep this limit low, especially if your pages have complex layouts. + * This setting defaults to 1.</p> + * + * @param limit How many pages will be kept offscreen in an idle state. + */ + public void setOffscreenPageLimit(int limit) { + int limitEx = limit; + if (limitEx < DEFAULT_OFFSCREEN_PAGES) { + Log.w(TAG, "Requested offscreen page limit " + limitEx + " too small; defaulting to " + + DEFAULT_OFFSCREEN_PAGES); + limitEx = DEFAULT_OFFSCREEN_PAGES; + } + if (limitEx != mOffscreenPageLimit) { + mOffscreenPageLimit = limitEx; + populate(); + } + } + + /** + * Set the margin between pages. + * + * @param marginPixels Distance between adjacent pages in pixels + * @see #getPageMargin() + * @see #setPageMarginDrawable(Drawable) + * @see #setPageMarginDrawable(int) + */ + public void setPageMargin(int marginPixels) { + final int oldMargin = mPageMargin; + mPageMargin = marginPixels; + + final int width = getWidth(); + recomputeScrollPosition(width, width, marginPixels, oldMargin); + + requestLayout(); + } + + /** + * Return the margin between pages. + * + * @return The size of the margin in pixels + */ + public int getPageMargin() { + return mPageMargin; + } + + /** + * Set a drawable that will be used to fill the margin between pages. + * + * @param d Drawable to display between pages + */ + public void setPageMarginDrawable(Drawable d) { + mMarginDrawable = d; + if (d != null) refreshDrawableState(); + setWillNotDraw(d == null); + invalidate(); + } + + /** + * Set a drawable that will be used to fill the margin between pages. + * + * @param resId Resource ID of a drawable to display between pages + */ + public void setPageMarginDrawable(int resId) { + setPageMarginDrawable(getContext().getResources().getDrawable(resId)); + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mMarginDrawable; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + final Drawable d = mMarginDrawable; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + } + + // We want the duration of the page snap animation to be influenced by the distance that + // the screen has to travel, however, we don't want this duration to be effected in a + // purely linear fashion. Instead, we use this method to moderate the effect that the distance + // of travel has on the overall snap duration. + @SuppressWarnings("static-method") + float distanceInfluenceForSnapDuration(float f) { + float fEx = f; + fEx -= 0.5f; // center the values about 0. + fEx *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(fEx); + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis + */ + void smoothScrollTo(int x, int y) { + smoothScrollTo(x, y, 0); + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis + * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) + */ + void smoothScrollTo(int x, int y, int velocity) { + + if (getChildCount() == 0) { + // Nothing to do. + setScrollingCacheEnabled(false); + return; + } + int sx = getScrollX(); + int sy = getScrollY(); + int dx = x - sx; + int dy = y - sy; + if (dx == 0 && dy == 0) { + completeScroll(false); + populate(); + setScrollState(SCROLL_STATE_IDLE); + return; + } + + setScrollingCacheEnabled(true); + setScrollState(SCROLL_STATE_SETTLING); + + final int width = getClientWidth(); + final int halfWidth = width / 2; + final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); + final float distance = halfWidth + halfWidth * + distanceInfluenceForSnapDuration(distanceRatio); + + int duration = 0; + int velocityEx = Math.abs(velocity); + if (velocityEx > 0) { + duration = 4 * Math.round(1000 * Math.abs(distance / velocityEx)); + } else { + final float pageWidth = width * mAdapter.getPageWidth(mCurItem); + final float pageDelta = Math.abs(dx) / (pageWidth + mPageMargin); + duration = (int) ((pageDelta + 1) * 100); + } + duration = Math.min(duration, MAX_SETTLE_DURATION); + + mScroller.startScroll(sx, sy, dx, dy, duration); + ViewCompat.postInvalidateOnAnimation(this); + } + + ItemInfo addNewItem(int position, int index) { + ItemInfo ii = new ItemInfo(); + ii.position = position; + ii.object = mAdapter.instantiateItem(this, position); + ii.widthFactor = mAdapter.getPageWidth(position); + if (index < 0 || index >= mItems.size()) { + mItems.add(ii); + } else { + mItems.add(index, ii); + } + return ii; + } + + void dataSetChanged() { + // This method only gets called if our observer is attached, so mAdapter is non-null. + + final int adapterCount = mAdapter.getCount(); + mExpectedAdapterCount = adapterCount; + boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 && + mItems.size() < adapterCount; + int newCurrItem = mCurItem; + + boolean isUpdating = false; + for (int i = 0; i < mItems.size(); i++) { + final ItemInfo ii = mItems.get(i); + final int newPos = mAdapter.getItemPosition(ii.object); + + if (newPos == PagerAdapter.POSITION_UNCHANGED) { + continue; + } + + if (newPos == PagerAdapter.POSITION_NONE) { + mItems.remove(i); + i--; + + if (!isUpdating) { + mAdapter.startUpdate(this); + isUpdating = true; + } + + mAdapter.destroyItem(this, ii.position, ii.object); + needPopulate = true; + + if (mCurItem == ii.position) { + // Keep the current item in the valid range + newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1)); + needPopulate = true; + } + continue; + } + + if (ii.position != newPos) { + if (ii.position == mCurItem) { + // Our current item changed position. Follow it. + newCurrItem = newPos; + } + + ii.position = newPos; + needPopulate = true; + } + } + + if (isUpdating) { + mAdapter.finishUpdate(this); + } + + Collections.sort(mItems, COMPARATOR); + + if (needPopulate) { + // Reset our known page widths; populate will recompute them. + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (!lp.isDecor) { + lp.widthFactor = 0.f; + } + } + + setCurrentItemInternal(newCurrItem, false, true); + requestLayout(); + } + } + + void populate() { + populate(mCurItem); + } + + void populate(int newCurrentItem) { + ItemInfo oldCurInfo = null; + int focusDirection = View.FOCUS_FORWARD; + if (mCurItem != newCurrentItem) { + focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; + oldCurInfo = infoForPosition(mCurItem); + mCurItem = newCurrentItem; + } + + if (mAdapter == null) { + sortChildDrawingOrder(); + return; + } + + // Bail now if we are waiting to populate. This is to hold off + // on creating views from the time the user releases their finger to + // fling to a new position until we have finished the scroll to + // that position, avoiding glitches from happening at that point. + if (mPopulatePending) { + if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); + sortChildDrawingOrder(); + return; + } + + // Also, don't populate until we are attached to a window. This is to + // avoid trying to populate before we have restored our view hierarchy + // state and conflicting with what is restored. + if (getWindowToken() == null) { + return; + } + + mAdapter.startUpdate(this); + + final int pageLimit = mOffscreenPageLimit; + final int startPos = Math.max(0, mCurItem - pageLimit); + final int N = mAdapter.getCount(); + final int endPos = Math.min(N-1, mCurItem + pageLimit); + + if (N != mExpectedAdapterCount) { + String resName; + try { + resName = getResources().getResourceName(getId()); + } catch (Resources.NotFoundException e) { + resName = Integer.toHexString(getId()); + } + throw new IllegalStateException("The application's PagerAdapter changed the adapter's" + + " contents without calling PagerAdapter#notifyDataSetChanged!" + + " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N + + " Pager id: " + resName + + " Pager class: " + getClass() + + " Problematic adapter: " + mAdapter.getClass()); + } + + // Locate the currently focused item or add it if needed. + int curIndex = -1; + ItemInfo curItem = null; + for (curIndex = 0; curIndex < mItems.size(); curIndex++) { + final ItemInfo ii = mItems.get(curIndex); + if (ii.position >= mCurItem) { + if (ii.position == mCurItem) curItem = ii; + break; + } + } + + if (curItem == null && N > 0) { + curItem = addNewItem(mCurItem, curIndex); + } + + // Fill 3x the available width or up to the number of offscreen + // pages requested to either side, whichever is larger. + // If we have no current item we have no work to do. + if (curItem != null) { + float extraWidthLeft = 0.f; + int itemIndex = curIndex - 1; + ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + final int clientWidth = getClientWidth(); + final float leftWidthNeeded = clientWidth <= 0 ? 0 : + 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth; + for (int pos = mCurItem - 1; pos >= 0; pos--) { + if (extraWidthLeft >= leftWidthNeeded && pos < startPos) { + if (ii == null) { + break; + } + if (pos == ii.position && !ii.scrolling) { + mItems.remove(itemIndex); + mAdapter.destroyItem(this, pos, ii.object); + if (DEBUG) { + Log.i(TAG, "populate() - destroyItem() with pos: " + pos + + " view: " + ii.object); + } + itemIndex--; + curIndex--; + ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + } + } else if (ii != null && pos == ii.position) { + extraWidthLeft += ii.widthFactor; + itemIndex--; + ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + } else { + ii = addNewItem(pos, itemIndex + 1); + extraWidthLeft += ii.widthFactor; + curIndex++; + ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; + } + } + + float extraWidthRight = curItem.widthFactor; + itemIndex = curIndex + 1; + if (extraWidthRight < 2.f) { + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + final float rightWidthNeeded = clientWidth <= 0 ? 0 : + (float) getPaddingRight() / (float) clientWidth + 2.f; + for (int pos = mCurItem + 1; pos < N; pos++) { + if (extraWidthRight >= rightWidthNeeded && pos > endPos) { + if (ii == null) { + break; + } + if (pos == ii.position && !ii.scrolling) { + mItems.remove(itemIndex); + mAdapter.destroyItem(this, pos, ii.object); + if (DEBUG) { + Log.i(TAG, "populate() - destroyItem() with pos: " + pos + + " view: " + ii.object); + } + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + } + } else if (ii != null && pos == ii.position) { + extraWidthRight += ii.widthFactor; + itemIndex++; + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + } else { + ii = addNewItem(pos, itemIndex); + itemIndex++; + extraWidthRight += ii.widthFactor; + ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; + } + } + } + + calculatePageOffsets(curItem, curIndex, oldCurInfo); + } + + if (DEBUG) { + Log.i(TAG, "Current page list:"); + for (int i=0; i<mItems.size(); i++) { + Log.i(TAG, "#" + i + ": page " + mItems.get(i).position); + } + } + + mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); + + mAdapter.finishUpdate(this); + + // Check width measurement of current pages and drawing sort order. + // Update LayoutParams as needed. + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.childIndex = i; + if (!lp.isDecor && lp.widthFactor == 0.f) { + // 0 means requery the adapter for this, it doesn't have a valid width. + final ItemInfo ii = infoForChild(child); + if (ii != null) { + lp.widthFactor = ii.widthFactor; + lp.position = ii.position; + } + } + } + sortChildDrawingOrder(); + + if (hasFocus()) { + View currentFocused = findFocus(); + ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null; + if (ii == null || ii.position != mCurItem) { + for (int i=0; i<getChildCount(); i++) { + View child = getChildAt(i); + ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + if (child.requestFocus(focusDirection)) { + break; + } + } + } + } + } + } + + private void sortChildDrawingOrder() { + if (mDrawingOrder != DRAW_ORDER_DEFAULT) { + if (mDrawingOrderedChildren == null) { + mDrawingOrderedChildren = new ArrayList<View>(); + } else { + mDrawingOrderedChildren.clear(); + } + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + mDrawingOrderedChildren.add(child); + } + Collections.sort(mDrawingOrderedChildren, sPositionComparator); + } + } + + private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) { + final int N = mAdapter.getCount(); + final int width = getClientWidth(); + final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; + // Fix up offsets for later layout. + if (oldCurInfo != null) { + final int oldCurPosition = oldCurInfo.position; + // Base offsets off of oldCurInfo. + if (oldCurPosition < curItem.position) { + int itemIndex = 0; + ItemInfo ii = null; + float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset; + for (int pos = oldCurPosition + 1; + pos <= curItem.position && itemIndex < mItems.size(); pos++) { + ii = mItems.get(itemIndex); + while (pos > ii.position && itemIndex < mItems.size() - 1) { + itemIndex++; + ii = mItems.get(itemIndex); + } + while (pos < ii.position) { + // We don't have an item populated for this, + // ask the adapter for an offset. + offset += mAdapter.getPageWidth(pos) + marginOffset; + pos++; + } + ii.offset = offset; + offset += ii.widthFactor + marginOffset; + } + } else if (oldCurPosition > curItem.position) { + int itemIndex = mItems.size() - 1; + ItemInfo ii = null; + float offset = oldCurInfo.offset; + for (int pos = oldCurPosition - 1; + pos >= curItem.position && itemIndex >= 0; pos--) { + ii = mItems.get(itemIndex); + while (pos < ii.position && itemIndex > 0) { + itemIndex--; + ii = mItems.get(itemIndex); + } + while (pos > ii.position) { + // We don't have an item populated for this, + // ask the adapter for an offset. + offset -= mAdapter.getPageWidth(pos) + marginOffset; + pos--; + } + offset -= ii.widthFactor + marginOffset; + ii.offset = offset; + } + } + } + + // Base all offsets off of curItem. + final int itemCount = mItems.size(); + float offset = curItem.offset; + int pos = curItem.position - 1; + mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE; + mLastOffset = curItem.position == N - 1 ? + curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE; + // Previous pages + for (int i = curIndex - 1; i >= 0; i--, pos--) { + final ItemInfo ii = mItems.get(i); + while (pos > ii.position) { + offset -= mAdapter.getPageWidth(pos--) + marginOffset; + } + offset -= ii.widthFactor + marginOffset; + ii.offset = offset; + if (ii.position == 0) mFirstOffset = offset; + } + offset = curItem.offset + curItem.widthFactor + marginOffset; + pos = curItem.position + 1; + // Next pages + for (int i = curIndex + 1; i < itemCount; i++, pos++) { + final ItemInfo ii = mItems.get(i); + while (pos < ii.position) { + offset += mAdapter.getPageWidth(pos++) + marginOffset; + } + if (ii.position == N - 1) { + mLastOffset = offset + ii.widthFactor - 1; + } + ii.offset = offset; + offset += ii.widthFactor + marginOffset; + } + } + + /** + * This is the persistent state that is saved by ViewPager. Only needed + * if you are creating a sublass of ViewPager that must save its own + * state, in which case it should implement a subclass of this which + * contains that state. + */ + public static class SavedState extends BaseSavedState { + int position; + Parcelable adapterState; + ClassLoader loader; + + public SavedState(Parcelable superState) { + super(superState); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(position); + out.writeParcelable(adapterState, flags); + } + + @Override + public String toString() { + return "FragmentPager.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " position=" + position + "}"; + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator<SavedState> CREATOR + = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { + @Override + public SavedState createFromParcel(Parcel in, ClassLoader loader) { + return new SavedState(in, loader); + } + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }); + + SavedState(Parcel in, ClassLoader loader) { + super(in); + ClassLoader loaderEx = loader; + if (loaderEx == null) { + loaderEx = getClass().getClassLoader(); + } + position = in.readInt(); + adapterState = in.readParcelable(loaderEx); + this.loader = loaderEx; + } + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.position = mCurItem; + if (mAdapter != null) { + ss.adapterState = mAdapter.saveState(); + } + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + SavedState ss = (SavedState)state; + super.onRestoreInstanceState(ss.getSuperState()); + + if (mAdapter != null) { + mAdapter.restoreState(ss.adapterState, ss.loader); + setCurrentItemInternal(ss.position, false, true); + } else { + mRestoredCurItem = ss.position; + mRestoredAdapterState = ss.adapterState; + mRestoredClassLoader = ss.loader; + } + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + ViewGroup.LayoutParams paramsEx = params; + if (!checkLayoutParams(paramsEx)) { + paramsEx = generateLayoutParams(paramsEx); + } + final LayoutParams lp = (LayoutParams) paramsEx; + lp.isDecor |= child instanceof Decor; + if (mInLayout) { + if (lp.isDecor) { + throw new IllegalStateException("Cannot add pager decor view during layout"); + } + lp.needsMeasure = true; + addViewInLayout(child, index, paramsEx); + } else { + super.addView(child, index, paramsEx); + } + + if (USE_CACHE) { + if (child.getVisibility() != GONE) { + child.setDrawingCacheEnabled(mScrollingCacheEnabled); + } else { + child.setDrawingCacheEnabled(false); + } + } + } + + @Override + public void removeView(View view) { + if (mInLayout) { + removeViewInLayout(view); + } else { + super.removeView(view); + } + } + + ItemInfo infoForChild(View child) { + for (int i=0; i<mItems.size(); i++) { + ItemInfo ii = mItems.get(i); + if (mAdapter.isViewFromObject(child, ii.object)) { + return ii; + } + } + return null; + } + + ItemInfo infoForAnyChild(View child) { + View childEx = child; + ViewParent parent; + while ((parent = childEx.getParent()) != this) { + if (parent == null || !(parent instanceof View)) { + return null; + } + childEx = (View)parent; + } + return infoForChild(childEx); + } + + ItemInfo infoForPosition(int position) { + for (int i = 0; i < mItems.size(); i++) { + ItemInfo ii = mItems.get(i); + if (ii.position == position) { + return ii; + } + } + return null; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mFirstLayout = true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // For simple implementation, our internal size is always 0. + // We depend on the container to specify the layout size of + // our view. We can't really know what it is since we will be + // adding and removing different arbitrary views and do not + // want the layout to change as this happens. + setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), + getDefaultSize(0, heightMeasureSpec)); + + final int measuredWidth = getMeasuredWidth(); + final int maxGutterSize = measuredWidth / 10; + mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize); + + // Children are just made to fill our space. + int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(); + int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); + + /* + * Make sure all children have been properly measured. Decor views first. + * Right now we cheat and make this less complicated by assuming decor + * views won't intersect. We will pin to edges based on gravity. + */ + int size = getChildCount(); + for (int i = 0; i < size; ++i) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp != null && lp.isDecor) { + final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; + int widthMode = MeasureSpec.AT_MOST; + int heightMode = MeasureSpec.AT_MOST; + boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM; + boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT; + + if (consumeVertical) { + widthMode = MeasureSpec.EXACTLY; + } else if (consumeHorizontal) { + heightMode = MeasureSpec.EXACTLY; + } + + int widthSize = childWidthSize; + int heightSize = childHeightSize; + if (lp.width != ViewGroup.LayoutParams.WRAP_CONTENT) { + widthMode = MeasureSpec.EXACTLY; + if (lp.width != ViewGroup.LayoutParams.MATCH_PARENT) { + widthSize = lp.width; + } + } + if (lp.height != ViewGroup.LayoutParams.WRAP_CONTENT) { + heightMode = MeasureSpec.EXACTLY; + if (lp.height != ViewGroup.LayoutParams.MATCH_PARENT) { + heightSize = lp.height; + } + } + final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); + final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); + child.measure(widthSpec, heightSpec); + + if (consumeVertical) { + childHeightSize -= child.getMeasuredHeight(); + } else if (consumeHorizontal) { + childWidthSize -= child.getMeasuredWidth(); + } + } + } + } + + mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); + mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); + + // Make sure we have created all fragments that we need to have shown. + mInLayout = true; + populate(); + mInLayout = false; + + // Page views next. + size = getChildCount(); + for (int i = 0; i < size; ++i) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child + + ": " + mChildWidthMeasureSpec); + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp == null || !lp.isDecor) { + final int widthSpec = MeasureSpec.makeMeasureSpec( + (int) (childWidthSize * (lp == null ? 0.f : lp.widthFactor)), + MeasureSpec.EXACTLY); + child.measure(widthSpec, mChildHeightMeasureSpec); + } + } + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + // Make sure scroll position is set correctly. + if (w != oldw) { + recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin); + } + } + + private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) { + if (oldWidth > 0 && !mItems.isEmpty()) { + final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin; + final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight() + + oldMargin; + final int xpos = getScrollX(); + final float pageOffset = (float) xpos / oldWidthWithMargin; + final int newOffsetPixels = (int) (pageOffset * widthWithMargin); + + scrollTo(newOffsetPixels, getScrollY()); + if (!mScroller.isFinished()) { + // We now return to your regularly scheduled scroll, already in progress. + final int newDuration = mScroller.getDuration() - mScroller.timePassed(); + ItemInfo targetInfo = infoForPosition(mCurItem); + mScroller.startScroll(newOffsetPixels, 0, + (int) (targetInfo.offset * width), 0, newDuration); + } + } else { + final ItemInfo ii = infoForPosition(mCurItem); + final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0; + final int scrollPos = (int) (scrollOffset * + (width - getPaddingLeft() - getPaddingRight())); + if (scrollPos != getScrollX()) { + completeScroll(false); + scrollTo(scrollPos, getScrollY()); + } + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int count = getChildCount(); + int width = r - l; + int height = b - t; + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int paddingRight = getPaddingRight(); + int paddingBottom = getPaddingBottom(); + final int scrollX = getScrollX(); + + int decorCount = 0; + + // First pass - decor views. We need to do this in two passes so that + // we have the proper offsets for non-decor views later. + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + int childLeft = 0; + int childTop = 0; + if (lp.isDecor) { + final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; + switch (hgrav) { + default: + childLeft = paddingLeft; + break; + case Gravity.LEFT: + childLeft = paddingLeft; + paddingLeft += child.getMeasuredWidth(); + break; + case Gravity.CENTER_HORIZONTAL: + childLeft = Math.max((width - child.getMeasuredWidth()) / 2, + paddingLeft); + break; + case Gravity.RIGHT: + childLeft = width - paddingRight - child.getMeasuredWidth(); + paddingRight += child.getMeasuredWidth(); + break; + } + switch (vgrav) { + default: + childTop = paddingTop; + break; + case Gravity.TOP: + childTop = paddingTop; + paddingTop += child.getMeasuredHeight(); + break; + case Gravity.CENTER_VERTICAL: + childTop = Math.max((height - child.getMeasuredHeight()) / 2, + paddingTop); + break; + case Gravity.BOTTOM: + childTop = height - paddingBottom - child.getMeasuredHeight(); + paddingBottom += child.getMeasuredHeight(); + break; + } + childLeft += scrollX; + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), + childTop + child.getMeasuredHeight()); + decorCount++; + } + } + } + + final int childWidth = width - paddingLeft - paddingRight; + // Page views. Do this once we have the right padding offsets from above. + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + ItemInfo ii; + if (!lp.isDecor && (ii = infoForChild(child)) != null) { + int loff = (int) (childWidth * ii.offset); + int childLeft = paddingLeft + loff; + int childTop = paddingTop; + if (lp.needsMeasure) { + // This was added during layout and needs measurement. + // Do it now that we know what we're working with. + lp.needsMeasure = false; + final int widthSpec = MeasureSpec.makeMeasureSpec( + (int) (childWidth * lp.widthFactor), + MeasureSpec.EXACTLY); + final int heightSpec = MeasureSpec.makeMeasureSpec( + height - paddingTop - paddingBottom, + MeasureSpec.EXACTLY); + child.measure(widthSpec, heightSpec); + } + if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object + + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() + + "x" + child.getMeasuredHeight()); + child.layout(childLeft, childTop, + childLeft + child.getMeasuredWidth(), + childTop + child.getMeasuredHeight()); + } + } + } + mTopPageBounds = paddingTop; + mBottomPageBounds = height - paddingBottom; + mDecorChildCount = decorCount; + + if (mFirstLayout) { + scrollToItem(mCurItem, false, 0, false); + } + mFirstLayout = false; + } + + @Override + public void computeScroll() { + if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + + if (oldX != x || oldY != y) { + scrollTo(x, y); + if (!pageScrolled(x)) { + mScroller.abortAnimation(); + scrollTo(0, y); + } + } + + // Keep on drawing until the animation has finished. + ViewCompat.postInvalidateOnAnimation(this); + return; + } + + // Done with scroll, clean up state. + completeScroll(true); + } + + private boolean pageScrolled(int xpos) { + if (mItems.size() == 0) { + mCalledSuper = false; + onPageScrolled(0, 0, 0); + if (!mCalledSuper) { + throw new IllegalStateException( + "onPageScrolled did not call superclass implementation"); + } + return false; + } + final ItemInfo ii = infoForCurrentScrollPosition(); + final int width = getClientWidth(); + final int widthWithMargin = width + mPageMargin; + final float marginOffset = (float) mPageMargin / width; + final int currentPage = ii.position; + final float pageOffset = (((float) xpos / width) - ii.offset) / + (ii.widthFactor + marginOffset); + final int offsetPixels = (int) (pageOffset * widthWithMargin); + + mCalledSuper = false; + onPageScrolled(currentPage, pageOffset, offsetPixels); + if (!mCalledSuper) { + throw new IllegalStateException( + "onPageScrolled did not call superclass implementation"); + } + return true; + } + + /** + * This method will be invoked when the current page is scrolled, either as part + * of a programmatically initiated smooth scroll or a user initiated touch scroll. + * If you override this method you must call through to the superclass implementation + * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled + * returns. + * + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is nonzero. + * @param offset Value from [0, 1) indicating the offset from the page at position. + * @param offsetPixels Value in pixels indicating the offset from position. + */ + protected void onPageScrolled(int position, float offset, int offsetPixels) { + // Offset any decor views if needed - keep them on-screen at all times. + if (mDecorChildCount > 0) { + final int scrollX = getScrollX(); + int paddingLeft = getPaddingLeft(); + int paddingRight = getPaddingRight(); + final int width = getWidth(); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (!lp.isDecor) continue; + + final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + int childLeft = 0; + switch (hgrav) { + default: + childLeft = paddingLeft; + break; + case Gravity.LEFT: + childLeft = paddingLeft; + paddingLeft += child.getWidth(); + break; + case Gravity.CENTER_HORIZONTAL: + childLeft = Math.max((width - child.getMeasuredWidth()) / 2, + paddingLeft); + break; + case Gravity.RIGHT: + childLeft = width - paddingRight - child.getMeasuredWidth(); + paddingRight += child.getMeasuredWidth(); + break; + } + childLeft += scrollX; + + final int childOffset = childLeft - child.getLeft(); + if (childOffset != 0) { + child.offsetLeftAndRight(childOffset); + } + } + } + + if (mOnPageChangeListener != null) { + mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); + } + if (mInternalPageChangeListener != null) { + mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); + } + + if (mPageTransformer != null) { + final int scrollX = getScrollX(); + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (lp.isDecor) continue; + + final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth(); + mPageTransformer.transformPage(child, transformPos); + } + } + + mCalledSuper = true; + } + + private void completeScroll(boolean postEvents) { + boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING; + if (needPopulate) { + // Done with scroll, no longer want to cache view drawing. + setScrollingCacheEnabled(false); + mScroller.abortAnimation(); + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + if (oldX != x || oldY != y) { + scrollTo(x, y); + } + } + mPopulatePending = false; + for (int i=0; i<mItems.size(); i++) { + ItemInfo ii = mItems.get(i); + if (ii.scrolling) { + needPopulate = true; + ii.scrolling = false; + } + } + if (needPopulate) { + if (postEvents) { + ViewCompat.postOnAnimation(this, mEndScrollRunnable); + } else { + mEndScrollRunnable.run(); + } + } + } + + private boolean isGutterDrag(float x, float dx) { + return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0); + } + + private void enableLayers(boolean enable) { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final int layerType = enable ? + ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; + ViewCompat.setLayerType(getChildAt(i), layerType, null); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onMotionEvent will be called and we do the actual + * scrolling there. + */ + + final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; + + // Always take care of the touch gesture being complete. + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + // Release the drag. + if (DEBUG) Log.v(TAG, "Intercept done!"); + mIsBeingDragged = false; + mIsUnableToDrag = false; + mActivePointerId = INVALID_POINTER; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + return false; + } + + // Nothing more to do here if we have decided whether or not we + // are dragging. + if (action != MotionEvent.ACTION_DOWN) { + if (mIsBeingDragged) { + if (DEBUG) Log.v(TAG, "Intercept returning true!"); + return true; + } + if (mIsUnableToDrag) { + if (DEBUG) Log.v(TAG, "Intercept returning false!"); + return false; + } + } + + switch (action) { + case MotionEvent.ACTION_MOVE: { + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + + /* + * Locally do absolute value. mLastMotionY is set to the y value + * of the down event. + */ + final int activePointerId = mActivePointerId; + if (activePointerId == INVALID_POINTER) { + // If we don't have a valid id, the touch down wasn't on content. + break; + } + + final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); + final float x = MotionEventCompat.getX(ev, pointerIndex); + final float dx = x - mLastMotionX; + final float xDiff = Math.abs(dx); + final float y = MotionEventCompat.getY(ev, pointerIndex); + final float yDiff = Math.abs(y - mInitialMotionY); + if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); + + if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && + canScroll(this, false, (int) dx, (int) x, (int) y)) { + // Nested view has scrollable area under this point. Let it be handled there. + mLastMotionX = x; + mLastMotionY = y; + mIsUnableToDrag = true; + return false; + } + if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { + if (DEBUG) Log.v(TAG, "Starting drag!"); + mIsBeingDragged = true; + requestParentDisallowInterceptTouchEvent(true); + setScrollState(SCROLL_STATE_DRAGGING); + mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : + mInitialMotionX - mTouchSlop; + mLastMotionY = y; + setScrollingCacheEnabled(true); + } else if (yDiff > mTouchSlop) { + // The finger has moved enough in the vertical + // direction to be counted as a drag... abort + // any attempt to drag horizontally, to work correctly + // with children that have scrolling containers. + if (DEBUG) Log.v(TAG, "Starting unable to drag!"); + mIsUnableToDrag = true; + } + if (mIsBeingDragged) { + // Scroll to follow the motion event + if (performDrag(x)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + break; + } + + case MotionEvent.ACTION_DOWN: { + /* + * Remember location of down touch. + * ACTION_DOWN always refers to pointer index 0. + */ + mLastMotionX = mInitialMotionX = ev.getX(); + mLastMotionY = mInitialMotionY = ev.getY(); + mActivePointerId = MotionEventCompat.getPointerId(ev, 0); + mIsUnableToDrag = false; + + mScroller.computeScrollOffset(); + if (mScrollState == SCROLL_STATE_SETTLING && + Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { + // Let the user 'catch' the pager as it animates. + mScroller.abortAnimation(); + mPopulatePending = false; + populate(); + mIsBeingDragged = true; + requestParentDisallowInterceptTouchEvent(true); + setScrollState(SCROLL_STATE_DRAGGING); + } else { + completeScroll(false); + mIsBeingDragged = false; + } + + if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY + + " mIsBeingDragged=" + mIsBeingDragged + + "mIsUnableToDrag=" + mIsUnableToDrag); + break; + } + + case MotionEventCompat.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mIsBeingDragged; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mFakeDragging) { + // A fake drag is in progress already, ignore this real one + // but still eat the touch events. + // (It is likely that the user is multi-touching the screen.) + return true; + } + + if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { + // Don't handle edge touches immediately -- they may actually belong to one of our + // descendants. + return false; + } + + if (mAdapter == null || mAdapter.getCount() == 0) { + // Nothing to present or scroll; nothing to touch. + return false; + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + final int action = ev.getAction(); + boolean needsInvalidate = false; + + switch (action & MotionEventCompat.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + mScroller.abortAnimation(); + mPopulatePending = false; + populate(); + + // Remember where the motion event started + mLastMotionX = mInitialMotionX = ev.getX(); + mLastMotionY = mInitialMotionY = ev.getY(); + mActivePointerId = MotionEventCompat.getPointerId(ev, 0); + break; + } + case MotionEvent.ACTION_MOVE: + if (!mIsBeingDragged) { + final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); + final float x = MotionEventCompat.getX(ev, pointerIndex); + final float xDiff = Math.abs(x - mLastMotionX); + final float y = MotionEventCompat.getY(ev, pointerIndex); + final float yDiff = Math.abs(y - mLastMotionY); + if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); + if (xDiff > mTouchSlop && xDiff > yDiff) { + if (DEBUG) Log.v(TAG, "Starting drag!"); + mIsBeingDragged = true; + requestParentDisallowInterceptTouchEvent(true); + mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : + mInitialMotionX - mTouchSlop; + mLastMotionY = y; + setScrollState(SCROLL_STATE_DRAGGING); + setScrollingCacheEnabled(true); + + // Disallow Parent Intercept, just in case + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + } + // Not else! Note that mIsBeingDragged can be set above. + if (mIsBeingDragged) { + // Scroll to follow the motion event + final int activePointerIndex = MotionEventCompat.findPointerIndex( + ev, mActivePointerId); + final float x = MotionEventCompat.getX(ev, activePointerIndex); + needsInvalidate |= performDrag(x); + } + break; + case MotionEvent.ACTION_UP: + if (mIsBeingDragged) { + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( + velocityTracker, mActivePointerId); + mPopulatePending = true; + final int width = getClientWidth(); + final int scrollX = getScrollX(); + final ItemInfo ii = infoForCurrentScrollPosition(); + final int currentPage = ii.position; + final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; + final int activePointerIndex = + MotionEventCompat.findPointerIndex(ev, mActivePointerId); + final float x = MotionEventCompat.getX(ev, activePointerIndex); + final int totalDelta = (int) (x - mInitialMotionX); + int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, + totalDelta); + setCurrentItemInternal(nextPage, true, true, initialVelocity); + + mActivePointerId = INVALID_POINTER; + endDrag(); + needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); + } + break; + case MotionEvent.ACTION_CANCEL: + if (mIsBeingDragged) { + scrollToItem(mCurItem, true, 0, false); + mActivePointerId = INVALID_POINTER; + endDrag(); + needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); + } + break; + case MotionEventCompat.ACTION_POINTER_DOWN: { + final int index = MotionEventCompat.getActionIndex(ev); + final float x = MotionEventCompat.getX(ev, index); + mLastMotionX = x; + mActivePointerId = MotionEventCompat.getPointerId(ev, index); + break; + } + case MotionEventCompat.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + mLastMotionX = MotionEventCompat.getX(ev, + MotionEventCompat.findPointerIndex(ev, mActivePointerId)); + break; + } + if (needsInvalidate) { + ViewCompat.postInvalidateOnAnimation(this); + } + return true; + } + + private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) { + final ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(disallowIntercept); + } + } + + private boolean performDrag(float x) { + boolean needsInvalidate = false; + + final float deltaX = mLastMotionX - x; + mLastMotionX = x; + + float oldScrollX = getScrollX(); + float scrollX = oldScrollX + deltaX; + final int width = getClientWidth(); + + float leftBound = width * mFirstOffset; + float rightBound = width * mLastOffset; + boolean leftAbsolute = true; + boolean rightAbsolute = true; + + final ItemInfo firstItem = mItems.get(0); + final ItemInfo lastItem = mItems.get(mItems.size() - 1); + if (firstItem.position != 0) { + leftAbsolute = false; + leftBound = firstItem.offset * width; + } + if (lastItem.position != mAdapter.getCount() - 1) { + rightAbsolute = false; + rightBound = lastItem.offset * width; + } + + if (scrollX < leftBound) { + if (leftAbsolute) { + float over = leftBound - scrollX; + needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width); + } + scrollX = leftBound; + } else if (scrollX > rightBound) { + if (rightAbsolute) { + float over = scrollX - rightBound; + needsInvalidate = mRightEdge.onPull(Math.abs(over) / width); + } + scrollX = rightBound; + } + // Don't lose the rounded component + mLastMotionX += scrollX - (int) scrollX; + scrollTo((int) scrollX, getScrollY()); + pageScrolled((int) scrollX); + + return needsInvalidate; + } + + /** + * @return Info about the page at the current scroll position. + * This can be synthetic for a missing middle page; the 'object' field can be null. + */ + private ItemInfo infoForCurrentScrollPosition() { + final int width = getClientWidth(); + final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0; + final float marginOffset = width > 0 ? (float) mPageMargin / width : 0; + int lastPos = -1; + float lastOffset = 0.f; + float lastWidth = 0.f; + boolean first = true; + + ItemInfo lastItem = null; + for (int i = 0; i < mItems.size(); i++) { + ItemInfo ii = mItems.get(i); + float offset; + if (!first && ii.position != lastPos + 1) { + // Create a synthetic item for a missing page. + ii = mTempItem; + ii.offset = lastOffset + lastWidth + marginOffset; + ii.position = lastPos + 1; + ii.widthFactor = mAdapter.getPageWidth(ii.position); + i--; + } + offset = ii.offset; + + final float leftBound = offset; + final float rightBound = offset + ii.widthFactor + marginOffset; + if (first || scrollOffset >= leftBound) { + if (scrollOffset < rightBound || i == mItems.size() - 1) { + return ii; + } + } else { + return lastItem; + } + first = false; + lastPos = ii.position; + lastOffset = offset; + lastWidth = ii.widthFactor; + lastItem = ii; + } + + return lastItem; + } + + private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { + int targetPage; + if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { + targetPage = velocity > 0 ? currentPage : currentPage + 1; + } else { + final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f; + targetPage = (int) (currentPage + pageOffset + truncator); + } + + if (mItems.size() > 0) { + final ItemInfo firstItem = mItems.get(0); + final ItemInfo lastItem = mItems.get(mItems.size() - 1); + + // Only let the user target pages we have items for + targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position)); + } + + return targetPage; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + boolean needsInvalidate = false; + + final int overScrollMode = ViewCompat.getOverScrollMode(this); + if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || + (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && + mAdapter != null && mAdapter.getCount() > 1)) { + if (!mLeftEdge.isFinished()) { + final int restoreCount = canvas.save(); + final int height = getHeight() - getPaddingTop() - getPaddingBottom(); + final int width = getWidth(); + + canvas.rotate(270); + canvas.translate(-height + getPaddingTop(), mFirstOffset * width); + mLeftEdge.setSize(height, width); + needsInvalidate |= mLeftEdge.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mRightEdge.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight() - getPaddingTop() - getPaddingBottom(); + + canvas.rotate(90); + canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); + mRightEdge.setSize(height, width); + needsInvalidate |= mRightEdge.draw(canvas); + canvas.restoreToCount(restoreCount); + } + } else { + mLeftEdge.finish(); + mRightEdge.finish(); + } + + if (needsInvalidate) { + // Keep animating + ViewCompat.postInvalidateOnAnimation(this); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // Draw the margin drawable between pages if needed. + if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { + final int scrollX = getScrollX(); + final int width = getWidth(); + + final float marginOffset = (float) mPageMargin / width; + int itemIndex = 0; + ItemInfo ii = mItems.get(0); + float offset = ii.offset; + final int itemCount = mItems.size(); + final int firstPos = ii.position; + final int lastPos = mItems.get(itemCount - 1).position; + for (int pos = firstPos; pos < lastPos; pos++) { + while (pos > ii.position && itemIndex < itemCount) { + ii = mItems.get(++itemIndex); + } + + float drawAt; + if (pos == ii.position) { + drawAt = (ii.offset + ii.widthFactor) * width; + offset = ii.offset + ii.widthFactor + marginOffset; + } else { + float widthFactor = mAdapter.getPageWidth(pos); + drawAt = (offset + widthFactor) * width; + offset += widthFactor + marginOffset; + } + + if (drawAt + mPageMargin > scrollX) { + mMarginDrawable.setBounds((int) drawAt, mTopPageBounds, + (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds); + mMarginDrawable.draw(canvas); + } + + if (drawAt > scrollX + width) { + break; // No more visible, no sense in continuing + } + } + } + } + + /** + * Start a fake drag of the pager. + * + * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager + * with the touch scrolling of another view, while still letting the ViewPager + * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) + * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call + * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. + * + * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag + * is already in progress, this method will return false. + * + * @return true if the fake drag began successfully, false if it could not be started. + * + * @see #fakeDragBy(float) + * @see #endFakeDrag() + */ + public boolean beginFakeDrag() { + if (mIsBeingDragged) { + return false; + } + mFakeDragging = true; + setScrollState(SCROLL_STATE_DRAGGING); + mInitialMotionX = mLastMotionX = 0; + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } else { + mVelocityTracker.clear(); + } + final long time = SystemClock.uptimeMillis(); + final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); + mVelocityTracker.addMovement(ev); + ev.recycle(); + mFakeDragBeginTime = time; + return true; + } + + /** + * End a fake drag of the pager. + * + * @see #beginFakeDrag() + * @see #fakeDragBy(float) + */ + public void endFakeDrag() { + if (!mFakeDragging) { + throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); + } + + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( + velocityTracker, mActivePointerId); + mPopulatePending = true; + final int width = getClientWidth(); + final int scrollX = getScrollX(); + final ItemInfo ii = infoForCurrentScrollPosition(); + final int currentPage = ii.position; + final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; + final int totalDelta = (int) (mLastMotionX - mInitialMotionX); + int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, + totalDelta); + setCurrentItemInternal(nextPage, true, true, initialVelocity); + endDrag(); + + mFakeDragging = false; + } + + /** + * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. + * + * @param xOffset Offset in pixels to drag by. + * @see #beginFakeDrag() + * @see #endFakeDrag() + */ + public void fakeDragBy(float xOffset) { + if (!mFakeDragging) { + throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); + } + + mLastMotionX += xOffset; + + float oldScrollX = getScrollX(); + float scrollX = oldScrollX - xOffset; + final int width = getClientWidth(); + + float leftBound = width * mFirstOffset; + float rightBound = width * mLastOffset; + + final ItemInfo firstItem = mItems.get(0); + final ItemInfo lastItem = mItems.get(mItems.size() - 1); + if (firstItem.position != 0) { + leftBound = firstItem.offset * width; + } + if (lastItem.position != mAdapter.getCount() - 1) { + rightBound = lastItem.offset * width; + } + + if (scrollX < leftBound) { + scrollX = leftBound; + } else if (scrollX > rightBound) { + scrollX = rightBound; + } + // Don't lose the rounded component + mLastMotionX += scrollX - (int) scrollX; + scrollTo((int) scrollX, getScrollY()); + pageScrolled((int) scrollX); + + // Synthesize an event for the VelocityTracker. + final long time = SystemClock.uptimeMillis(); + final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, + mLastMotionX, 0, 0); + mVelocityTracker.addMovement(ev); + ev.recycle(); + } + + /** + * Returns true if a fake drag is in progress. + * + * @return true if currently in a fake drag, false otherwise. + * + * @see #beginFakeDrag() + * @see #fakeDragBy(float) + * @see #endFakeDrag() + */ + public boolean isFakeDragging() { + return mFakeDragging; + } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = MotionEventCompat.getActionIndex(ev); + final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); + mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private void endDrag() { + mIsBeingDragged = false; + mIsUnableToDrag = false; + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void setScrollingCacheEnabled(boolean enabled) { + if (mScrollingCacheEnabled != enabled) { + mScrollingCacheEnabled = enabled; + if (USE_CACHE) { + final int size = getChildCount(); + for (int i = 0; i < size; ++i) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + child.setDrawingCacheEnabled(enabled); + } + } + } + } + } + + @Override + public boolean canScrollHorizontally(int direction) { + if (mAdapter == null) { + return false; + } + + final int width = getClientWidth(); + final int scrollX = getScrollX(); + if (direction < 0) { + return (scrollX > (int) (width * mFirstOffset)); + } else if (direction > 0) { + return (scrollX < (int) (width * mLastOffset)); + } else { + return false; + } + } + + /** + * Tests scrollability within child views of v given a delta of dx. + * + * @param v View to test for horizontal scrollability + * @param checkV Whether the view v passed should itself be checked for scrollability (true), + * or just its children (false). + * @param dx Delta scrolled in pixels + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point + * @return true if child views of v can be scrolled by delta of dx. + */ + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (v instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) v; + final int scrollX = v.getScrollX(); + final int scrollY = v.getScrollY(); + final int count = group.getChildCount(); + // Count backwards - let topmost views consume scroll distance first. + for (int i = count - 1; i >= 0; i--) { + // TODO: Add versioned support here for transformed views. + // This will not work for transformed views in Honeycomb+ + final View child = group.getChildAt(i); + if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && + y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && + canScroll(child, true, dx, x + scrollX - child.getLeft(), + y + scrollY - child.getTop())) { + return true; + } + } + } + + return checkV && ViewCompat.canScrollHorizontally(v, -dx); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Let the focused view and/or our descendants get the key first + return super.dispatchKeyEvent(event) || executeKeyEvent(event); + } + + /** + * You can call this function yourself to have the scroll view perform + * scrolling from a key event, just as if the event had been dispatched to + * it by the view hierarchy. + * + * @param event The key event to execute. + * @return Return true if the event was handled, else false. + */ + public boolean executeKeyEvent(KeyEvent event) { + boolean handled = false; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_LEFT: + handled = arrowScroll(FOCUS_LEFT); + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + handled = arrowScroll(FOCUS_RIGHT); + break; + case KeyEvent.KEYCODE_TAB: + if (Build.VERSION.SDK_INT >= 11) { + // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD + // before Android 3.0. Ignore the tab key on those devices. + if (KeyEventCompat.hasNoModifiers(event)) { + handled = arrowScroll(FOCUS_FORWARD); + } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { + handled = arrowScroll(FOCUS_BACKWARD); + } + } + break; + } + } + return handled; + } + + public boolean arrowScroll(int direction) { + View currentFocused = findFocus(); + if (currentFocused == this) { + currentFocused = null; + } else if (currentFocused != null) { + boolean isChild = false; + for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; + parent = parent.getParent()) { + if (parent == this) { + isChild = true; + break; + } + } + if (!isChild) { + // This would cause the focus search down below to fail in fun ways. + final StringBuilder sb = new StringBuilder(); + sb.append(currentFocused.getClass().getSimpleName()); + for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; + parent = parent.getParent()) { + sb.append(" => ").append(parent.getClass().getSimpleName()); + } + Log.e(TAG, "arrowScroll tried to find focus based on non-child " + + "current focused view " + sb.toString()); + currentFocused = null; + } + } + + boolean handled = false; + + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, + direction); + if (nextFocused != null && nextFocused != currentFocused) { + if (direction == View.FOCUS_LEFT) { + // If there is nothing to the left, or this is causing us to + // jump to the right, then what we really want to do is page left. + final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; + final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; + if (currentFocused != null && nextLeft >= currLeft) { + handled = pageLeft(); + } else { + handled = nextFocused.requestFocus(); + } + } else if (direction == View.FOCUS_RIGHT) { + // If there is nothing to the right, or this is causing us to + // jump to the left, then what we really want to do is page right. + final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left; + final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left; + if (currentFocused != null && nextLeft <= currLeft) { + handled = pageRight(); + } else { + handled = nextFocused.requestFocus(); + } + } + } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { + // Trying to move left and nothing there; try to page. + handled = pageLeft(); + } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { + // Trying to move right and nothing there; try to page. + handled = pageRight(); + } + if (handled) { + playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); + } + return handled; + } + + private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { + Rect outRectEx = outRect; + if (outRectEx == null) { + outRectEx = new Rect(); + } + if (child == null) { + outRectEx.set(0, 0, 0, 0); + return outRectEx; + } + outRectEx.left = child.getLeft(); + outRectEx.right = child.getRight(); + outRectEx.top = child.getTop(); + outRectEx.bottom = child.getBottom(); + + ViewParent parent = child.getParent(); + while (parent instanceof ViewGroup && parent != this) { + final ViewGroup group = (ViewGroup) parent; + outRectEx.left += group.getLeft(); + outRectEx.right += group.getRight(); + outRectEx.top += group.getTop(); + outRectEx.bottom += group.getBottom(); + + parent = group.getParent(); + } + return outRectEx; + } + + boolean pageLeft() { + if (mCurItem > 0) { + setCurrentItem(mCurItem-1, true); + return true; + } + return false; + } + + boolean pageRight() { + if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { + setCurrentItem(mCurItem+1, true); + return true; + } + return false; + } + + /** + * We only want the current page that is being shown to be focusable. + */ + @Override + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + final int focusableCount = views.size(); + + final int descendantFocusability = getDescendantFocusability(); + + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + child.addFocusables(views, direction, focusableMode); + } + } + } + } + + // we add ourselves (if focusable) in all cases except for when we are + // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is + // to avoid the focus search finding layouts when a more precise search + // among the focusable children would be more interesting. + if ( + descendantFocusability != FOCUS_AFTER_DESCENDANTS || + // No focusable descendants + (focusableCount == views.size())) { + // Note that we can't call the superclass here, because it will + // add all views in. So we need to do the same thing View does. + if (!isFocusable()) { + return; + } + if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && + isInTouchMode() && !isFocusableInTouchMode()) { + return; + } + views.add(this); + } + } + + /** + * We only want the current page that is being shown to be touchable. + */ + @Override + public void addTouchables(ArrayList<View> views) { + // Note that we don't call super.addTouchables(), which means that + // we don't call View.addTouchables(). This is okay because a ViewPager + // is itself not touchable. + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + child.addTouchables(views); + } + } + } + } + + /** + * We only want the current page that is being shown to be focusable. + */ + @Override + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + int index; + int increment; + int end; + int count = getChildCount(); + if ((direction & FOCUS_FORWARD) != 0) { + index = 0; + increment = 1; + end = count; + } else { + index = count - 1; + increment = -1; + end = -1; + } + for (int i = index; i != end; i += increment) { + View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem) { + if (child.requestFocus(direction, previouslyFocusedRect)) { + return true; + } + } + } + } + return false; + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + // Dispatch scroll events from this ViewPager. + if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED) { + return super.dispatchPopulateAccessibilityEvent(event); + } + + // Dispatch all other accessibility events from the current page. + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == VISIBLE) { + final ItemInfo ii = infoForChild(child); + if (ii != null && ii.position == mCurItem && + child.dispatchPopulateAccessibilityEvent(event)) { + return true; + } + } + } + + return false; + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return generateDefaultLayoutParams(); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams && super.checkLayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + class MyAccessibilityDelegate extends AccessibilityDelegateCompat { + + @Override + @SuppressWarnings("synthetic-access") + public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(host, event); + event.setClassName(ViewPager.class.getName()); + final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain(); + recordCompat.setScrollable(canScroll()); + if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED + && mAdapter != null) { + recordCompat.setItemCount(mAdapter.getCount()); + recordCompat.setFromIndex(mCurItem); + recordCompat.setToIndex(mCurItem); + } + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.setClassName(ViewPager.class.getName()); + info.setScrollable(canScroll()); + if (canScrollHorizontally(1)) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); + } + if (canScrollHorizontally(-1)) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); + } + } + + @Override + @SuppressWarnings("synthetic-access") + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (super.performAccessibilityAction(host, action, args)) { + return true; + } + switch (action) { + case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { + if (canScrollHorizontally(1)) { + setCurrentItem(mCurItem + 1); + return true; + } + } return false; + case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { + if (canScrollHorizontally(-1)) { + setCurrentItem(mCurItem - 1); + return true; + } + } return false; + } + return false; + } + + @SuppressWarnings("synthetic-access") + private boolean canScroll() { + return (mAdapter != null) && (mAdapter.getCount() > 1); + } + } + + private class PagerObserver extends DataSetObserver { + @Override + public void onChanged() { + dataSetChanged(); + } + @Override + public void onInvalidated() { + dataSetChanged(); + } + } + + /** + * Layout parameters that should be supplied for views added to a + * ViewPager. + */ + public static class LayoutParams extends ViewGroup.LayoutParams { + /** + * true if this view is a decoration on the pager itself and not + * a view supplied by the adapter. + */ + public boolean isDecor; + + /** + * Gravity setting for use on decor views only: + * Where to position the view page within the overall ViewPager + * container; constants are defined in {@link android.view.Gravity}. + */ + public int gravity; + + /** + * Width as a 0-1 multiplier of the measured pager width + */ + float widthFactor = 0.f; + + /** + * true if this view was added during layout and needs to be measured + * before being positioned. + */ + boolean needsMeasure; + + /** + * Adapter position this view is for if !isDecor + */ + int position; + + /** + * Current child index within the ViewPager that this view occupies + */ + int childIndex; + + public LayoutParams() { + super(MATCH_PARENT, MATCH_PARENT); + } + + @SuppressWarnings("synthetic-access") + public LayoutParams(Context context, AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); + gravity = a.getInteger(0, Gravity.TOP); + a.recycle(); + } + } + + static class ViewPositionComparator implements Comparator<View> { + @Override + public int compare(View lhs, View rhs) { + final LayoutParams llp = (LayoutParams) lhs.getLayoutParams(); + final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams(); + if (llp.isDecor != rlp.isDecor) { + return llp.isDecor ? 1 : -1; + } + return llp.position - rlp.position; + } + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java b/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java new file mode 100644 index 0000000..c7b17ea --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityEventCompat.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.os.Build; +import android.view.accessibility.AccessibilityEvent; + +/** + * Helper for accessing features in {@link AccessibilityEvent} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class AccessibilityEventCompat { + + static interface AccessibilityEventVersionImpl { + public int getRecordCount(AccessibilityEvent event); + public void appendRecord(AccessibilityEvent event, Object record); + public Object getRecord(AccessibilityEvent event, int index); + } + + static class AccessibilityEventStubImpl implements AccessibilityEventVersionImpl { + + @Override + public void appendRecord(AccessibilityEvent event, Object record) { + return; + } + + @Override + public Object getRecord(AccessibilityEvent event, int index) { + return null; + } + + @Override + public int getRecordCount(AccessibilityEvent event) { + return 0; + } + } + + static class AccessibilityEventIcsImpl extends AccessibilityEventStubImpl { + + @Override + public void appendRecord(AccessibilityEvent event, Object record) { + AccessibilityEventCompatIcs.appendRecord(event, record); + } + + @Override + public Object getRecord(AccessibilityEvent event, int index) { + return AccessibilityEventCompatIcs.getRecord(event, index); + } + + @Override + public int getRecordCount(AccessibilityEvent event) { + return AccessibilityEventCompatIcs.getRecordCount(event); + } + } + + private final static AccessibilityEventVersionImpl IMPL; + + static { + if (Build.VERSION.SDK_INT >= 14) { // ICS + IMPL = new AccessibilityEventIcsImpl(); + } else { + IMPL = new AccessibilityEventStubImpl(); + } + } + + /** + * Represents the event of a hover enter over a {@link android.view.View}. + */ + public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080; + + /** + * Represents the event of a hover exit over a {@link android.view.View}. + */ + public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100; + + /** + * Represents the event of starting a touch exploration gesture. + */ + public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200; + + /** + * Represents the event of ending a touch exploration gesture. + */ + public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400; + + /** + * Represents the event of changing the content of a window. + */ + public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800; + + /** + * Represents the event of scrolling a view. + */ + public static final int TYPE_VIEW_SCROLLED = 0x00001000; + + /** + * Represents the event of changing the selection in an {@link android.widget.EditText}. + */ + public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000; + + /** + * Represents the event of an application making an announcement. + */ + public static final int TYPE_ANNOUNCEMENT = 0x00004000; + + /** + * Represents the event of gaining accessibility focus. + */ + public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000; + + /** + * Represents the event of clearing accessibility focus. + */ + public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000; + + /** + * Represents the event of traversing the text of a view at a given movement granularity. + */ + public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000; + + /** + * Represents the event of beginning gesture detection. + */ + public static final int TYPE_GESTURE_DETECTION_START = 0x00040000; + + /** + * Represents the event of ending gesture detection. + */ + public static final int TYPE_GESTURE_DETECTION_END = 0x00080000; + + /** + * Represents the event of the user starting to touch the screen. + */ + public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000; + + /** + * Represents the event of the user ending to touch the screen. + */ + public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000; + + /** + * Mask for {@link AccessibilityEvent} all types. + * + * @see AccessibilityEvent#TYPE_VIEW_CLICKED + * @see AccessibilityEvent#TYPE_VIEW_LONG_CLICKED + * @see AccessibilityEvent#TYPE_VIEW_SELECTED + * @see AccessibilityEvent#TYPE_VIEW_FOCUSED + * @see AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED + * @see AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED + * @see AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED + * @see #TYPE_VIEW_HOVER_ENTER + * @see #TYPE_VIEW_HOVER_EXIT + * @see #TYPE_TOUCH_EXPLORATION_GESTURE_START + * @see #TYPE_TOUCH_EXPLORATION_GESTURE_END + * @see #TYPE_WINDOW_CONTENT_CHANGED + * @see #TYPE_VIEW_SCROLLED + * @see #TYPE_VIEW_TEXT_SELECTION_CHANGED + * @see #TYPE_ANNOUNCEMENT + * @see #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY + * @see #TYPE_GESTURE_DETECTION_START + * @see #TYPE_GESTURE_DETECTION_END + * @see #TYPE_TOUCH_INTERACTION_START + * @see #TYPE_TOUCH_INTERACTION_END + */ + public static final int TYPES_ALL_MASK = 0xFFFFFFFF; + + /* + * Hide constructor from clients. + */ + private AccessibilityEventCompat() { + + } + + /** + * Gets the number of records contained in the event. + * + * @return The number of records. + */ + public static int getRecordCount(AccessibilityEvent event) { + return IMPL.getRecordCount(event); + } + + /** + * Appends an {@link android.view.accessibility.AccessibilityRecord} to the end of + * event records. + * + * @param record The record to append. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + @SuppressWarnings("deprecation") + public static void appendRecord(AccessibilityEvent event, AccessibilityRecordCompat record) { + IMPL.appendRecord(event, record.getImpl()); + } + + /** + * Gets the record at a given index. + * + * @param index The index. + * @return The record at the specified index. + */ + @SuppressWarnings("deprecation") + public static AccessibilityRecordCompat getRecord(AccessibilityEvent event, int index) { + return new AccessibilityRecordCompat(IMPL.getRecord(event, index)); + } + + /** + * Creates an {@link AccessibilityRecordCompat} from an {@link AccessibilityEvent} + * that can be used to manipulate the event properties defined in + * {@link android.view.accessibility.AccessibilityRecord}. + * <p> + * <strong>Note:</strong> Do not call {@link AccessibilityRecordCompat#recycle()} on the + * returned {@link AccessibilityRecordCompat}. Call {@link AccessibilityEvent#recycle()} + * in case you want to recycle the event. + * </p> + * + * @param event The from which to create a record. + * @return An {@link AccessibilityRecordCompat}. + */ + @SuppressWarnings("deprecation") + public static AccessibilityRecordCompat asRecord(AccessibilityEvent event) { + return new AccessibilityRecordCompat(event); + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityEventCompatIcs.java b/src/android/support/v4/view/accessibility/AccessibilityEventCompatIcs.java new file mode 100644 index 0000000..53c7471 --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityEventCompatIcs.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityRecord; + +/** + * ICS specific AccessibilityEvent API implementation. + */ +class AccessibilityEventCompatIcs { + + public static int getRecordCount(AccessibilityEvent event) { + return event.getRecordCount(); + } + + public static void appendRecord(AccessibilityEvent event, Object record) { + event.appendRecord((AccessibilityRecord) record); + } + + public static Object getRecord(AccessibilityEvent event, int index) { + return event.getRecord(index); + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java new file mode 100644 index 0000000..a611d61 --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java @@ -0,0 +1,1960 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat; +import android.support.v4.view.ViewCompat; +import android.view.View; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Helper for accessing {@link android.view.accessibility.AccessibilityNodeInfo} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class AccessibilityNodeInfoCompat { + + static interface AccessibilityNodeInfoImpl { + public Object obtain(); + public Object obtain(View source); + public Object obtain(Object info); + public Object obtain(View root, int virtualDescendantId); + public void setSource(Object info, View source); + public void setSource(Object info, View root, int virtualDescendantId); + public Object findFocus(Object info, int focus); + public Object focusSearch(Object info, int direction); + public int getWindowId(Object info); + public int getChildCount(Object info); + public Object getChild(Object info, int index); + public void addChild(Object info, View child); + public void addChild(Object info, View child, int virtualDescendantId); + public int getActions(Object info); + public void addAction(Object info, int action); + public boolean performAction(Object info, int action); + public boolean performAction(Object info, int action, Bundle arguments); + public void setMovementGranularities(Object info, int granularities); + public int getMovementGranularities(Object info); + public List<Object> findAccessibilityNodeInfosByText(Object info, String text); + public Object getParent(Object info); + public void setParent(Object info, View root, int virtualDescendantId); + public void setParent(Object info, View parent); + public void getBoundsInParent(Object info, Rect outBounds); + public void setBoundsInParent(Object info, Rect bounds); + public void getBoundsInScreen(Object info, Rect outBounds); + public void setBoundsInScreen(Object info, Rect bounds); + public boolean isCheckable(Object info); + public void setCheckable(Object info, boolean checkable); + public boolean isChecked(Object info); + public void setChecked(Object info, boolean checked); + public boolean isFocusable(Object info); + public void setFocusable(Object info, boolean focusable); + public boolean isFocused(Object info); + public void setFocused(Object info, boolean focused); + public boolean isVisibleToUser(Object info); + public void setVisibleToUser(Object info, boolean visibleToUser); + public boolean isAccessibilityFocused(Object info); + public void setAccessibilityFocused(Object info, boolean focused); + public boolean isSelected(Object info); + public void setSelected(Object info, boolean selected); + public boolean isClickable(Object info); + public void setClickable(Object info, boolean clickable); + public boolean isLongClickable(Object info); + public void setLongClickable(Object info, boolean longClickable); + public boolean isEnabled(Object info); + public void setEnabled(Object info, boolean enabled); + public boolean isPassword(Object info); + public void setPassword(Object info, boolean password); + public boolean isScrollable(Object info); + public void setScrollable(Object info, boolean scrollable); + public CharSequence getPackageName(Object info); + public void setPackageName(Object info, CharSequence packageName); + public CharSequence getClassName(Object info); + public void setClassName(Object info, CharSequence className); + public CharSequence getText(Object info); + public void setText(Object info, CharSequence text); + public CharSequence getContentDescription(Object info); + public void setContentDescription(Object info, CharSequence contentDescription); + public void recycle(Object info); + public String getViewIdResourceName(Object info); + public void setViewIdResourceName(Object info, String viewId); + public int getLiveRegion(Object info); + public void setLiveRegion(Object info, int mode); + } + + static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl { + @Override + public Object obtain() { + return null; + } + + @Override + public Object obtain(View source) { + return null; + } + + @Override + public Object obtain(View root, int virtualDescendantId) { + return null; + } + + @Override + public Object obtain(Object info) { + return null; + } + + @Override + public void addAction(Object info, int action) { + return; + } + + @Override + public void addChild(Object info, View child) { + return; + } + + @Override + public void addChild(Object info, View child, int virtualDescendantId) { + return; + } + + @Override + public List<Object> findAccessibilityNodeInfosByText(Object info, String text) { + return Collections.emptyList(); + } + + @Override + public int getActions(Object info) { + return 0; + } + + @Override + public void getBoundsInParent(Object info, Rect outBounds) { + return; + } + + @Override + public void getBoundsInScreen(Object info, Rect outBounds) { + return; + } + + @Override + public Object getChild(Object info, int index) { + return null; + } + + @Override + public int getChildCount(Object info) { + return 0; + } + + @Override + public CharSequence getClassName(Object info) { + return null; + } + + @Override + public CharSequence getContentDescription(Object info) { + return null; + } + + @Override + public CharSequence getPackageName(Object info) { + return null; + } + + @Override + public Object getParent(Object info) { + return null; + } + + @Override + public CharSequence getText(Object info) { + return null; + } + + @Override + public int getWindowId(Object info) { + return 0; + } + + @Override + public boolean isCheckable(Object info) { + return false; + } + + @Override + public boolean isChecked(Object info) { + return false; + } + + @Override + public boolean isClickable(Object info) { + return false; + } + + @Override + public boolean isEnabled(Object info) { + return false; + } + + @Override + public boolean isFocusable(Object info) { + return false; + } + + @Override + public boolean isFocused(Object info) { + return false; + } + + @Override + public boolean isVisibleToUser(Object info) { + return false; + } + + @Override + public boolean isAccessibilityFocused(Object info) { + return false; + } + + @Override + public boolean isLongClickable(Object info) { + return false; + } + + @Override + public boolean isPassword(Object info) { + return false; + } + + @Override + public boolean isScrollable(Object info) { + return false; + } + + @Override + public boolean isSelected(Object info) { + return false; + } + + @Override + public boolean performAction(Object info, int action) { + return false; + } + + @Override + public boolean performAction(Object info, int action, Bundle arguments) { + return false; + } + + @Override + public void setMovementGranularities(Object info, int granularities) { + return; + } + + @Override + public int getMovementGranularities(Object info) { + return 0; + } + + @Override + public void setBoundsInParent(Object info, Rect bounds) { + return; + } + + @Override + public void setBoundsInScreen(Object info, Rect bounds) { + return; + } + + @Override + public void setCheckable(Object info, boolean checkable) { + return; + } + + @Override + public void setChecked(Object info, boolean checked) { + return; + } + + @Override + public void setClassName(Object info, CharSequence className) { + return; + } + + @Override + public void setClickable(Object info, boolean clickable) { + return; + } + + @Override + public void setContentDescription(Object info, CharSequence contentDescription) { + return; + } + + @Override + public void setEnabled(Object info, boolean enabled) { + return; + } + + @Override + public void setFocusable(Object info, boolean focusable) { + return; + } + + @Override + public void setFocused(Object info, boolean focused) { + return; + } + + @Override + public void setVisibleToUser(Object info, boolean visibleToUser) { + return; + } + + @Override + public void setAccessibilityFocused(Object info, boolean focused) { + return; + } + + @Override + public void setLongClickable(Object info, boolean longClickable) { + return; + } + + @Override + public void setPackageName(Object info, CharSequence packageName) { + return; + } + + @Override + public void setParent(Object info, View parent) { + return; + } + + @Override + public void setPassword(Object info, boolean password) { + return; + } + + @Override + public void setScrollable(Object info, boolean scrollable) { + return; + } + + @Override + public void setSelected(Object info, boolean selected) { + return; + } + + @Override + public void setSource(Object info, View source) { + return; + } + + @Override + public void setSource(Object info, View root, int virtualDescendantId) { + return; + } + + @Override + public Object findFocus(Object info, int focus) { + return null; + } + + @Override + public Object focusSearch(Object info, int direction) { + return null; + } + + @Override + public void setText(Object info, CharSequence text) { + return; + } + + @Override + public void recycle(Object info) { + return; + } + + @Override + public void setParent(Object info, View root, int virtualDescendantId) { + return; + } + + @Override + public String getViewIdResourceName(Object info) { + return null; + } + + @Override + public void setViewIdResourceName(Object info, String viewId) { + return; + } + + @Override + public int getLiveRegion(Object info) { + return ViewCompat.ACCESSIBILITY_LIVE_REGION_NONE; + } + + @Override + public void setLiveRegion(Object info, int mode) { + // No-op + } + } + + static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl { + @Override + public Object obtain() { + return AccessibilityNodeInfoCompatIcs.obtain(); + } + + @Override + public Object obtain(View source) { + return AccessibilityNodeInfoCompatIcs.obtain(source); + } + + @Override + public Object obtain(Object info) { + return AccessibilityNodeInfoCompatIcs.obtain(info); + } + + @Override + public void addAction(Object info, int action) { + AccessibilityNodeInfoCompatIcs.addAction(info, action); + } + + @Override + public void addChild(Object info, View child) { + AccessibilityNodeInfoCompatIcs.addChild(info, child); + } + + @Override + public List<Object> findAccessibilityNodeInfosByText(Object info, String text) { + return AccessibilityNodeInfoCompatIcs.findAccessibilityNodeInfosByText(info, text); + } + + @Override + public int getActions(Object info) { + return AccessibilityNodeInfoCompatIcs.getActions(info); + } + + @Override + public void getBoundsInParent(Object info, Rect outBounds) { + AccessibilityNodeInfoCompatIcs.getBoundsInParent(info, outBounds); + } + + @Override + public void getBoundsInScreen(Object info, Rect outBounds) { + AccessibilityNodeInfoCompatIcs.getBoundsInScreen(info, outBounds); + } + + @Override + public Object getChild(Object info, int index) { + return AccessibilityNodeInfoCompatIcs.getChild(info, index); + } + + @Override + public int getChildCount(Object info) { + return AccessibilityNodeInfoCompatIcs.getChildCount(info); + } + + @Override + public CharSequence getClassName(Object info) { + return AccessibilityNodeInfoCompatIcs.getClassName(info); + } + + @Override + public CharSequence getContentDescription(Object info) { + return AccessibilityNodeInfoCompatIcs.getContentDescription(info); + } + + @Override + public CharSequence getPackageName(Object info) { + return AccessibilityNodeInfoCompatIcs.getPackageName(info); + } + + @Override + public Object getParent(Object info) { + return AccessibilityNodeInfoCompatIcs.getParent(info); + } + + @Override + public CharSequence getText(Object info) { + return AccessibilityNodeInfoCompatIcs.getText(info); + } + + @Override + public int getWindowId(Object info) { + return AccessibilityNodeInfoCompatIcs.getWindowId(info); + } + + @Override + public boolean isCheckable(Object info) { + return AccessibilityNodeInfoCompatIcs.isCheckable(info); + } + + @Override + public boolean isChecked(Object info) { + return AccessibilityNodeInfoCompatIcs.isChecked(info); + } + + @Override + public boolean isClickable(Object info) { + return AccessibilityNodeInfoCompatIcs.isClickable(info); + } + + @Override + public boolean isEnabled(Object info) { + return AccessibilityNodeInfoCompatIcs.isEnabled(info); + } + + @Override + public boolean isFocusable(Object info) { + return AccessibilityNodeInfoCompatIcs.isFocusable(info); + } + + @Override + public boolean isFocused(Object info) { + return AccessibilityNodeInfoCompatIcs.isFocused(info); + } + + @Override + public boolean isLongClickable(Object info) { + return AccessibilityNodeInfoCompatIcs.isLongClickable(info); + } + + @Override + public boolean isPassword(Object info) { + return AccessibilityNodeInfoCompatIcs.isPassword(info); + } + + @Override + public boolean isScrollable(Object info) { + return AccessibilityNodeInfoCompatIcs.isScrollable(info); + } + + @Override + public boolean isSelected(Object info) { + return AccessibilityNodeInfoCompatIcs.isSelected(info); + } + + @Override + public boolean performAction(Object info, int action) { + return AccessibilityNodeInfoCompatIcs.performAction(info, action); + } + + @Override + public void setBoundsInParent(Object info, Rect bounds) { + AccessibilityNodeInfoCompatIcs.setBoundsInParent(info, bounds); + } + + @Override + public void setBoundsInScreen(Object info, Rect bounds) { + AccessibilityNodeInfoCompatIcs.setBoundsInScreen(info, bounds); + } + + @Override + public void setCheckable(Object info, boolean checkable) { + AccessibilityNodeInfoCompatIcs.setCheckable(info, checkable); + } + + @Override + public void setChecked(Object info, boolean checked) { + AccessibilityNodeInfoCompatIcs.setChecked(info, checked); + } + + @Override + public void setClassName(Object info, CharSequence className) { + AccessibilityNodeInfoCompatIcs.setClassName(info, className); + } + + @Override + public void setClickable(Object info, boolean clickable) { + AccessibilityNodeInfoCompatIcs.setClickable(info, clickable); + } + + @Override + public void setContentDescription(Object info, CharSequence contentDescription) { + AccessibilityNodeInfoCompatIcs.setContentDescription(info, contentDescription); + } + + @Override + public void setEnabled(Object info, boolean enabled) { + AccessibilityNodeInfoCompatIcs.setEnabled(info, enabled); + } + + @Override + public void setFocusable(Object info, boolean focusable) { + AccessibilityNodeInfoCompatIcs.setFocusable(info, focusable); + } + + @Override + public void setFocused(Object info, boolean focused) { + AccessibilityNodeInfoCompatIcs.setFocused(info, focused); + } + + @Override + public void setLongClickable(Object info, boolean longClickable) { + AccessibilityNodeInfoCompatIcs.setLongClickable(info, longClickable); + } + + @Override + public void setPackageName(Object info, CharSequence packageName) { + AccessibilityNodeInfoCompatIcs.setPackageName(info, packageName); + } + + @Override + public void setParent(Object info, View parent) { + AccessibilityNodeInfoCompatIcs.setParent(info, parent); + } + + @Override + public void setPassword(Object info, boolean password) { + AccessibilityNodeInfoCompatIcs.setPassword(info, password); + } + + @Override + public void setScrollable(Object info, boolean scrollable) { + AccessibilityNodeInfoCompatIcs.setScrollable(info, scrollable); + } + + @Override + public void setSelected(Object info, boolean selected) { + AccessibilityNodeInfoCompatIcs.setSelected(info, selected); + } + + @Override + public void setSource(Object info, View source) { + AccessibilityNodeInfoCompatIcs.setSource(info, source); + } + + @Override + public void setText(Object info, CharSequence text) { + AccessibilityNodeInfoCompatIcs.setText(info, text); + } + + @Override + public void recycle(Object info) { + AccessibilityNodeInfoCompatIcs.recycle(info); + } + } + + static { + if (Build.VERSION.SDK_INT >= 14) { // ICS + IMPL = new AccessibilityNodeInfoIcsImpl(); + } else { + IMPL = new AccessibilityNodeInfoStubImpl(); + } + } + + private static final AccessibilityNodeInfoImpl IMPL; + + private final Object mInfo; + + // Actions introduced in IceCreamSandwich + + /** + * Action that focuses the node. + */ + public static final int ACTION_FOCUS = 0x00000001; + + /** + * Action that unfocuses the node. + */ + public static final int ACTION_CLEAR_FOCUS = 0x00000002; + + /** + * Action that selects the node. + */ + public static final int ACTION_SELECT = 0x00000004; + + /** + * Action that unselects the node. + */ + public static final int ACTION_CLEAR_SELECTION = 0x00000008; + + /** + * Action that clicks on the node info. + */ + public static final int ACTION_CLICK = 0x00000010; + + /** + * Action that long clicks on the node. + */ + public static final int ACTION_LONG_CLICK = 0x00000020; + + // Actions introduced in JellyBean + + /** + * Action that gives accessibility focus to the node. + */ + public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000040; + + /** + * Action that clears accessibility focus of the node. + */ + public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080; + + /** + * Action that requests to go to the next entity in this node's text + * at a given movement granularity. For example, move to the next character, + * word, etc. + * <p> + * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, + * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br> + * <strong>Example:</strong> Move to the previous character and do not extend selection. + * <code><pre><p> + * Bundle arguments = new Bundle(); + * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, + * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER); + * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, + * false); + * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments); + * </code></pre></p> + * </p> + * + * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT + * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN + * + * @see #setMovementGranularities(int) + * @see #getMovementGranularities() + * + * @see #MOVEMENT_GRANULARITY_CHARACTER + * @see #MOVEMENT_GRANULARITY_WORD + * @see #MOVEMENT_GRANULARITY_LINE + * @see #MOVEMENT_GRANULARITY_PARAGRAPH + * @see #MOVEMENT_GRANULARITY_PAGE + */ + public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100; + + /** + * Action that requests to go to the previous entity in this node's text + * at a given movement granularity. For example, move to the next character, + * word, etc. + * <p> + * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, + * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br> + * <strong>Example:</strong> Move to the next character and do not extend selection. + * <code><pre><p> + * Bundle arguments = new Bundle(); + * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, + * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER); + * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, + * false); + * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, + * arguments); + * </code></pre></p> + * </p> + * + * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT + * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN + * + * @see #setMovementGranularities(int) + * @see #getMovementGranularities() + * + * @see #MOVEMENT_GRANULARITY_CHARACTER + * @see #MOVEMENT_GRANULARITY_WORD + * @see #MOVEMENT_GRANULARITY_LINE + * @see #MOVEMENT_GRANULARITY_PARAGRAPH + * @see #MOVEMENT_GRANULARITY_PAGE + */ + public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200; + + /** + * Action to move to the next HTML element of a given type. For example, move + * to the BUTTON, INPUT, TABLE, etc. + * <p> + * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br> + * <strong>Example:</strong> + * <code><pre><p> + * Bundle arguments = new Bundle(); + * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); + * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments); + * </code></pre></p> + * </p> + */ + public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400; + + /** + * Action to move to the previous HTML element of a given type. For example, move + * to the BUTTON, INPUT, TABLE, etc. + * <p> + * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br> + * <strong>Example:</strong> + * <code><pre><p> + * Bundle arguments = new Bundle(); + * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); + * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT, arguments); + * </code></pre></p> + * </p> + */ + public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800; + + /** + * Action to scroll the node content forward. + */ + public static final int ACTION_SCROLL_FORWARD = 0x00001000; + + /** + * Action to scroll the node content backward. + */ + public static final int ACTION_SCROLL_BACKWARD = 0x00002000; + + // Actions introduced in JellyBeanMr2 + + /** + * Action to copy the current selection to the clipboard. + */ + public static final int ACTION_COPY = 0x00004000; + + /** + * Action to paste the current clipboard content. + */ + public static final int ACTION_PASTE = 0x00008000; + + /** + * Action to cut the current selection and place it to the clipboard. + */ + public static final int ACTION_CUT = 0x00010000; + + /** + * Action to set the selection. Performing this action with no arguments + * clears the selection. + * <p> + * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SELECTION_START_INT}, + * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br> + * <strong>Example:</strong> + * <code><pre><p> + * Bundle arguments = new Bundle(); + * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1); + * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2); + * info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments); + * </code></pre></p> + * </p> + * + * @see #ACTION_ARGUMENT_SELECTION_START_INT + * @see #ACTION_ARGUMENT_SELECTION_END_INT + */ + public static final int ACTION_SET_SELECTION = 0x00020000; + + // Action arguments + + /** + * Argument for which movement granularity to be used when traversing the node text. + * <p> + * <strong>Type:</strong> int<br> + * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, + * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} + * </p> + */ + public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = + "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; + + /** + * Argument for which HTML element to get moving to the next/previous HTML element. + * <p> + * <strong>Type:</strong> String<br> + * <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT}, + * {@link #ACTION_PREVIOUS_HTML_ELEMENT} + * </p> + */ + public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING = + "ACTION_ARGUMENT_HTML_ELEMENT_STRING"; + + /** + * Argument for whether when moving at granularity to extend the selection + * or to move it otherwise. + * <p> + * <strong>Type:</strong> boolean<br> + * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, + * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} + * </p> + * + * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY + * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY + */ + public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = + "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"; + + /** + * Argument for specifying the selection start. + * <p> + * <strong>Type:</strong> int<br> + * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION} + * </p> + * + * @see #ACTION_SET_SELECTION + */ + public static final String ACTION_ARGUMENT_SELECTION_START_INT = + "ACTION_ARGUMENT_SELECTION_START_INT"; + + /** + * Argument for specifying the selection end. + * <p> + * <strong>Type:</strong> int<br> + * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION} + * </p> + * + * @see #ACTION_SET_SELECTION + */ + public static final String ACTION_ARGUMENT_SELECTION_END_INT = + "ACTION_ARGUMENT_SELECTION_END_INT"; + + // Focus types + + /** + * The input focus. + */ + public static final int FOCUS_INPUT = 1; + + /** + * The accessibility focus. + */ + public static final int FOCUS_ACCESSIBILITY = 2; + + // Movement granularities + + /** + * Movement granularity bit for traversing the text of a node by character. + */ + public static final int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001; + + /** + * Movement granularity bit for traversing the text of a node by word. + */ + public static final int MOVEMENT_GRANULARITY_WORD = 0x00000002; + + /** + * Movement granularity bit for traversing the text of a node by line. + */ + public static final int MOVEMENT_GRANULARITY_LINE = 0x00000004; + + /** + * Movement granularity bit for traversing the text of a node by paragraph. + */ + public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008; + + /** + * Movement granularity bit for traversing the text of a node by page. + */ + public static final int MOVEMENT_GRANULARITY_PAGE = 0x00000010; + + /** + * Creates a wrapper for info implementation. + * + * @param object The info to wrap. + * @return A wrapper for if the object is not null, null otherwise. + */ + static AccessibilityNodeInfoCompat wrapNonNullInstance(Object object) { + if (object != null) { + return new AccessibilityNodeInfoCompat(object); + } + return null; + } + + /** + * Creates a new instance wrapping an + * {@link android.view.accessibility.AccessibilityNodeInfo}. + * + * @param info The info. + */ + public AccessibilityNodeInfoCompat(Object info) { + mInfo = info; + } + + /** + * @return The wrapped {@link android.view.accessibility.AccessibilityNodeInfo}. + */ + public Object getInfo() { + return mInfo; + } + + /** + * Returns a cached instance if such is available otherwise a new one and + * sets the source. + * + * @return An instance. + * @see #setSource(View) + */ + public static AccessibilityNodeInfoCompat obtain(View source) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.obtain(source)); + } + + /** + * Returns a cached instance if such is available otherwise a new one + * and sets the source. + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + * @return An instance. + * + * @see #setSource(View, int) + */ + public static AccessibilityNodeInfoCompat obtain(View root, int virtualDescendantId) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance( + IMPL.obtain(root, virtualDescendantId)); + } + + /** + * Returns a cached instance if such is available otherwise a new one. + * + * @return An instance. + */ + public static AccessibilityNodeInfoCompat obtain() { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.obtain()); + } + + /** + * Returns a cached instance if such is available or a new one is create. + * The returned instance is initialized from the given <code>info</code>. + * + * @param info The other info. + * @return An instance. + */ + public static AccessibilityNodeInfoCompat obtain(AccessibilityNodeInfoCompat info) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.obtain(info.mInfo)); + } + + /** + * Sets the source. + * + * @param source The info source. + */ + public void setSource(View source) { + IMPL.setSource(mInfo, source); + } + + /** + * Sets the source to be a virtual descendant of the given <code>root</code>. + * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root + * is set as the source. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report themselves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setSource(View root, int virtualDescendantId) { + IMPL.setSource(mInfo, root, virtualDescendantId); + } + + /** + * Find the view that has the specified focus type. The search starts from + * the view represented by this node info. + * + * @param focus The focus to find. One of {@link #FOCUS_INPUT} or + * {@link #FOCUS_ACCESSIBILITY}. + * @return The node info of the focused view or null. + * + * @see #FOCUS_INPUT + * @see #FOCUS_ACCESSIBILITY + */ + public AccessibilityNodeInfoCompat findFocus(int focus) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.findFocus(mInfo, focus)); + } + + /** + * Searches for the nearest view in the specified direction that can take + * input focus. + * + * @param direction The direction. Can be one of: + * {@link View#FOCUS_DOWN}, + * {@link View#FOCUS_UP}, + * {@link View#FOCUS_LEFT}, + * {@link View#FOCUS_RIGHT}, + * {@link View#FOCUS_FORWARD}, + * {@link View#FOCUS_BACKWARD}. + * + * @return The node info for the view that can take accessibility focus. + */ + public AccessibilityNodeInfoCompat focusSearch(int direction) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.focusSearch(mInfo, direction)); + } + + /** + * Gets the id of the window from which the info comes from. + * + * @return The window id. + */ + public int getWindowId() { + return IMPL.getWindowId(mInfo); + } + + /** + * Gets the number of children. + * + * @return The child count. + */ + public int getChildCount() { + return IMPL.getChildCount(mInfo); + } + + /** + * Get the child at given index. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link AccessibilityNodeInfoCompat#recycle()} to + * avoid creating of multiple instances. + * </p> + * + * @param index The child index. + * @return The child node. + * @throws IllegalStateException If called outside of an + * AccessibilityService. + */ + public AccessibilityNodeInfoCompat getChild(int index) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.getChild(mInfo, index)); + } + + /** + * Adds a child. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param child The child. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void addChild(View child) { + IMPL.addChild(mInfo, child); + } + + /** + * Adds a virtual child which is a descendant of the given <code>root</code>. + * If <code>virtualDescendantId</code> is {@link View#NO_ID} the root + * is added as a child. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual child. + */ + public void addChild(View root, int virtualDescendantId) { + IMPL.addChild(mInfo, root, virtualDescendantId); + } + + /** + * Gets the actions that can be performed on the node. + * + * @return The bit mask of with actions. + * @see android.view.accessibility.AccessibilityNodeInfo#ACTION_FOCUS + * @see android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_FOCUS + * @see android.view.accessibility.AccessibilityNodeInfo#ACTION_SELECT + * @see android.view.accessibility.AccessibilityNodeInfo#ACTION_CLEAR_SELECTION + */ + public int getActions() { + return IMPL.getActions(mInfo); + } + + /** + * Adds an action that can be performed on the node. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param action The action. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void addAction(int action) { + IMPL.addAction(mInfo, action); + } + + /** + * Performs an action on the node. + * <p> + * <strong>Note:</strong> An action can be performed only if the request is + * made from an {@link android.accessibilityservice.AccessibilityService}. + * </p> + * + * @param action The action to perform. + * @return True if the action was performed. + * @throws IllegalStateException If called outside of an + * AccessibilityService. + */ + public boolean performAction(int action) { + return IMPL.performAction(mInfo, action); + } + + /** + * Performs an action on the node. + * <p> + * <strong>Note:</strong> An action can be performed only if the request is made + * from an {@link android.accessibilityservice.AccessibilityService}. + * </p> + * + * @param action The action to perform. + * @param arguments A bundle with additional arguments. + * @return True if the action was performed. + * + * @throws IllegalStateException If called outside of an AccessibilityService. + */ + public boolean performAction(int action, Bundle arguments) { + return IMPL.performAction(mInfo, action, arguments); + } + + /** + * Sets the movement granularities for traversing the text of this node. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param granularities The bit mask with granularities. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setMovementGranularities(int granularities) { + IMPL.setMovementGranularities(mInfo, granularities); + } + + /** + * Gets the movement granularities for traversing the text of this node. + * + * @return The bit mask with granularities. + */ + public int getMovementGranularities() { + return IMPL.getMovementGranularities(mInfo); + } + + /** + * Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by text. The match + * is case insensitive containment. The search is relative to this info i.e. this + * info is the root of the traversed tree. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link android.view.accessibility.AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. + * </p> + * + * @param text The searched text. + * @return A list of node info. + */ + public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(String text) { + List<AccessibilityNodeInfoCompat> result = new ArrayList<AccessibilityNodeInfoCompat>(); + List<Object> infos = IMPL.findAccessibilityNodeInfosByText(mInfo, text); + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + Object info = infos.get(i); + result.add(new AccessibilityNodeInfoCompat(info)); + } + return result; + } + + /** + * Gets the parent. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling {@link android.view.accessibility.AccessibilityNodeInfo#recycle()} + * to avoid creating of multiple instances. + * </p> + * + * @return The parent. + */ + public AccessibilityNodeInfoCompat getParent() { + return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.getParent(mInfo)); + } + + /** + * Sets the parent. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param parent The parent. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setParent(View parent) { + IMPL.setParent(mInfo, parent); + } + + /** + * Sets the parent to be a virtual descendant of the given <code>root</code>. + * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root + * is set as the parent. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setParent(View root, int virtualDescendantId) { + IMPL.setParent(mInfo, root, virtualDescendantId); + } + + /** + * Gets the node bounds in parent coordinates. + * + * @param outBounds The output node bounds. + */ + public void getBoundsInParent(Rect outBounds) { + IMPL.getBoundsInParent(mInfo, outBounds); + } + + /** + * Sets the node bounds in parent coordinates. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param bounds The node bounds. + *@throws IllegalStateException If called from an AccessibilityService. + */ + public void setBoundsInParent(Rect bounds) { + IMPL.setBoundsInParent(mInfo, bounds); + } + + /** + * Gets the node bounds in screen coordinates. + * + * @param outBounds The output node bounds. + */ + public void getBoundsInScreen(Rect outBounds) { + IMPL.getBoundsInScreen(mInfo, outBounds); + } + + /** + * Sets the node bounds in screen coordinates. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param bounds The node bounds. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setBoundsInScreen(Rect bounds) { + IMPL.setBoundsInScreen(mInfo, bounds); + } + + /** + * Gets whether this node is checkable. + * + * @return True if the node is checkable. + */ + public boolean isCheckable() { + return IMPL.isCheckable(mInfo); + } + + /** + * Sets whether this node is checkable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param checkable True if the node is checkable. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setCheckable(boolean checkable) { + IMPL.setCheckable(mInfo, checkable); + } + + /** + * Gets whether this node is checked. + * + * @return True if the node is checked. + */ + public boolean isChecked() { + return IMPL.isChecked(mInfo); + } + + /** + * Sets whether this node is checked. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param checked True if the node is checked. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setChecked(boolean checked) { + IMPL.setChecked(mInfo, checked); + } + + /** + * Gets whether this node is focusable. + * + * @return True if the node is focusable. + */ + public boolean isFocusable() { + return IMPL.isFocusable(mInfo); + } + + /** + * Sets whether this node is focusable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param focusable True if the node is focusable. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setFocusable(boolean focusable) { + IMPL.setFocusable(mInfo, focusable); + } + + /** + * Gets whether this node is focused. + * + * @return True if the node is focused. + */ + public boolean isFocused() { + return IMPL.isFocused(mInfo); + } + + /** + * Sets whether this node is focused. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param focused True if the node is focused. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setFocused(boolean focused) { + IMPL.setFocused(mInfo, focused); + } + + /** + * Sets whether this node is visible to the user. + * + * @return Whether the node is visible to the user. + */ + public boolean isVisibleToUser() { + return IMPL.isVisibleToUser(mInfo); + } + + /** + * Sets whether this node is visible to the user. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param visibleToUser Whether the node is visible to the user. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setVisibleToUser(boolean visibleToUser) { + IMPL.setVisibleToUser(mInfo, visibleToUser); + } + + /** + * Gets whether this node is accessibility focused. + * + * @return True if the node is accessibility focused. + */ + public boolean isAccessibilityFocused() { + return IMPL.isAccessibilityFocused(mInfo); + } + + /** + * Sets whether this node is accessibility focused. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param focused True if the node is accessibility focused. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setAccessibilityFocused(boolean focused) { + IMPL.setAccessibilityFocused(mInfo, focused); + } + + /** + * Gets whether this node is selected. + * + * @return True if the node is selected. + */ + public boolean isSelected() { + return IMPL.isSelected(mInfo); + } + + /** + * Sets whether this node is selected. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param selected True if the node is selected. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setSelected(boolean selected) { + IMPL.setSelected(mInfo, selected); + } + + /** + * Gets whether this node is clickable. + * + * @return True if the node is clickable. + */ + public boolean isClickable() { + return IMPL.isClickable(mInfo); + } + + /** + * Sets whether this node is clickable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param clickable True if the node is clickable. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setClickable(boolean clickable) { + IMPL.setClickable(mInfo, clickable); + } + + /** + * Gets whether this node is long clickable. + * + * @return True if the node is long clickable. + */ + public boolean isLongClickable() { + return IMPL.isLongClickable(mInfo); + } + + /** + * Sets whether this node is long clickable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param longClickable True if the node is long clickable. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setLongClickable(boolean longClickable) { + IMPL.setLongClickable(mInfo, longClickable); + } + + /** + * Gets whether this node is enabled. + * + * @return True if the node is enabled. + */ + public boolean isEnabled() { + return IMPL.isEnabled(mInfo); + } + + /** + * Sets whether this node is enabled. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param enabled True if the node is enabled. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setEnabled(boolean enabled) { + IMPL.setEnabled(mInfo, enabled); + } + + /** + * Gets whether this node is a password. + * + * @return True if the node is a password. + */ + public boolean isPassword() { + return IMPL.isPassword(mInfo); + } + + /** + * Sets whether this node is a password. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param password True if the node is a password. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setPassword(boolean password) { + IMPL.setPassword(mInfo, password); + } + + /** + * Gets if the node is scrollable. + * + * @return True if the node is scrollable, false otherwise. + */ + public boolean isScrollable() { + return IMPL.isScrollable(mInfo); + } + + /** + * Sets if the node is scrollable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param scrollable True if the node is scrollable, false otherwise. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setScrollable(boolean scrollable) { + IMPL.setScrollable(mInfo, scrollable); + } + + /** + * Gets the package this node comes from. + * + * @return The package name. + */ + public CharSequence getPackageName() { + return IMPL.getPackageName(mInfo); + } + + /** + * Sets the package this node comes from. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param packageName The package name. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setPackageName(CharSequence packageName) { + IMPL.setPackageName(mInfo, packageName); + } + + /** + * Gets the class this node comes from. + * + * @return The class name. + */ + public CharSequence getClassName() { + return IMPL.getClassName(mInfo); + } + + /** + * Sets the class this node comes from. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param className The class name. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setClassName(CharSequence className) { + IMPL.setClassName(mInfo, className); + } + + /** + * Gets the text of this node. + * + * @return The text. + */ + public CharSequence getText() { + return IMPL.getText(mInfo); + } + + /** + * Sets the text of this node. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param text The text. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setText(CharSequence text) { + IMPL.setText(mInfo, text); + } + + /** + * Gets the content description of this node. + * + * @return The content description. + */ + public CharSequence getContentDescription() { + return IMPL.getContentDescription(mInfo); + } + + /** + * Sets the content description of this node. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param contentDescription The content description. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setContentDescription(CharSequence contentDescription) { + IMPL.setContentDescription(mInfo, contentDescription); + } + + /** + * Return an instance back to be reused. + * <p> + * <strong>Note:</strong> You must not touch the object after calling this function. + * + * @throws IllegalStateException If the info is already recycled. + */ + public void recycle() { + IMPL.recycle(mInfo); + } + + /** + * Sets the fully qualified resource name of the source view's id. + * + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param viewId The id resource name. + */ + public void setViewIdResourceName(String viewId) { + IMPL.setViewIdResourceName(mInfo, viewId); + } + + /** + * Gets the fully qualified resource name of the source view's id. + * + * <p> + * <strong>Note:</strong> The primary usage of this API is for UI test automation + * and in order to report the source view id of an {@link AccessibilityNodeInfoCompat} + * the client has to set the {@link AccessibilityServiceInfoCompat#FLAG_REPORT_VIEW_IDS} + * flag when configuring his {@link android.accessibilityservice.AccessibilityService}. + * </p> + * + * @return The id resource name. + */ + public String getViewIdResourceName() { + return IMPL.getViewIdResourceName(mInfo); + } + + /** + * Gets the node's live region mode. + * <p> + * A live region is a node that contains information that is important for + * the user and when it changes the user should be notified. For example, + * in a login screen with a TextView that displays an "incorrect password" + * notification, that view should be marked as a live region with mode + * {@link ViewCompat#ACCESSIBILITY_LIVE_REGION_POLITE}. + * <p> + * It is the responsibility of the accessibility service to monitor + * {@link AccessibilityEventCompat#TYPE_WINDOW_CONTENT_CHANGED} events + * indicating changes to live region nodes and their children. + * + * @return The live region mode, or + * {@link ViewCompat#ACCESSIBILITY_LIVE_REGION_NONE} if the view is + * not a live region. + * @see ViewCompat#getAccessibilityLiveRegion(View) + */ + public int getLiveRegion() { + return IMPL.getLiveRegion(mInfo); + } + + /** + * Sets the node's live region mode. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is + * made immutable before being delivered to an AccessibilityService. + * + * @param mode The live region mode, or + * {@link ViewCompat#ACCESSIBILITY_LIVE_REGION_NONE} if the view is + * not a live region. + * @see ViewCompat#setAccessibilityLiveRegion(View, int) + */ + public void setLiveRegion(int mode) { + IMPL.setLiveRegion(mInfo, mode); + } + + @Override + public int hashCode() { + return (mInfo == null) ? 0 : mInfo.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AccessibilityNodeInfoCompat other = (AccessibilityNodeInfoCompat) obj; + if (mInfo == null) { + if (other.mInfo != null) { + return false; + } + } else if (!mInfo.equals(other.mInfo)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + + Rect bounds = new Rect(); + + getBoundsInParent(bounds); + builder.append("; boundsInParent: " + bounds); + + getBoundsInScreen(bounds); + builder.append("; boundsInScreen: " + bounds); + + builder.append("; packageName: ").append(getPackageName()); + builder.append("; className: ").append(getClassName()); + builder.append("; text: ").append(getText()); + builder.append("; contentDescription: ").append(getContentDescription()); + builder.append("; viewId: ").append(getViewIdResourceName()); + + builder.append("; checkable: ").append(isCheckable()); + builder.append("; checked: ").append(isChecked()); + builder.append("; focusable: ").append(isFocusable()); + builder.append("; focused: ").append(isFocused()); + builder.append("; selected: ").append(isSelected()); + builder.append("; clickable: ").append(isClickable()); + builder.append("; longClickable: ").append(isLongClickable()); + builder.append("; enabled: ").append(isEnabled()); + builder.append("; password: ").append(isPassword()); + builder.append("; scrollable: " + isScrollable()); + + builder.append("; ["); + for (int actionBits = getActions(); actionBits != 0;) { + final int action = 1 << Integer.numberOfTrailingZeros(actionBits); + actionBits &= ~action; + builder.append(getActionSymbolicName(action)); + if (actionBits != 0) { + builder.append(", "); + } + } + builder.append("]"); + + return builder.toString(); + } + + private static String getActionSymbolicName(int action) { + switch (action) { + case ACTION_FOCUS: + return "ACTION_FOCUS"; + case ACTION_CLEAR_FOCUS: + return "ACTION_CLEAR_FOCUS"; + case ACTION_SELECT: + return "ACTION_SELECT"; + case ACTION_CLEAR_SELECTION: + return "ACTION_CLEAR_SELECTION"; + case ACTION_CLICK: + return "ACTION_CLICK"; + case ACTION_LONG_CLICK: + return "ACTION_LONG_CLICK"; + case ACTION_ACCESSIBILITY_FOCUS: + return "ACTION_ACCESSIBILITY_FOCUS"; + case ACTION_CLEAR_ACCESSIBILITY_FOCUS: + return "ACTION_CLEAR_ACCESSIBILITY_FOCUS"; + case ACTION_NEXT_AT_MOVEMENT_GRANULARITY: + return "ACTION_NEXT_AT_MOVEMENT_GRANULARITY"; + case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: + return "ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY"; + case ACTION_NEXT_HTML_ELEMENT: + return "ACTION_NEXT_HTML_ELEMENT"; + case ACTION_PREVIOUS_HTML_ELEMENT: + return "ACTION_PREVIOUS_HTML_ELEMENT"; + case ACTION_SCROLL_FORWARD: + return "ACTION_SCROLL_FORWARD"; + case ACTION_SCROLL_BACKWARD: + return "ACTION_SCROLL_BACKWARD"; + case ACTION_CUT: + return "ACTION_CUT"; + case ACTION_COPY: + return "ACTION_COPY"; + case ACTION_PASTE: + return "ACTION_PASTE"; + case ACTION_SET_SELECTION: + return "ACTION_SET_SELECTION"; + default: + return"ACTION_UNKNOWN"; + } + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatIcs.java b/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatIcs.java new file mode 100644 index 0000000..367f995 --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatIcs.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.graphics.Rect; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; + +import java.util.List; + +/** + * ICS specific AccessibilityNodeInfo API implementation. + */ +class AccessibilityNodeInfoCompatIcs { + + public static Object obtain() { + return AccessibilityNodeInfo.obtain(); + } + + public static Object obtain(View source) { + return AccessibilityNodeInfo.obtain(source); + } + + public static Object obtain(Object info) { + return AccessibilityNodeInfo.obtain((AccessibilityNodeInfo) info); + } + + public static void addAction(Object info, int action) { + ((AccessibilityNodeInfo) info).addAction(action); + } + + public static void addChild(Object info, View child) { + ((AccessibilityNodeInfo) info).addChild(child); + } + + @SuppressWarnings("unchecked") + public static List<Object> findAccessibilityNodeInfosByText(Object info, String text) { + Object result = ((AccessibilityNodeInfo) info).findAccessibilityNodeInfosByText(text); + return (List<Object>) result; + } + + public static int getActions(Object info) { + return ((AccessibilityNodeInfo) info).getActions(); + } + + public static void getBoundsInParent(Object info, Rect outBounds) { + ((AccessibilityNodeInfo) info).getBoundsInParent(outBounds); + } + + public static void getBoundsInScreen(Object info, Rect outBounds) { + ((AccessibilityNodeInfo) info).getBoundsInScreen(outBounds); + } + + public static Object getChild(Object info, int index) { + return ((AccessibilityNodeInfo) info).getChild(index); + } + + public static int getChildCount(Object info) { + return ((AccessibilityNodeInfo) info).getChildCount(); + } + + public static CharSequence getClassName(Object info) { + return ((AccessibilityNodeInfo) info).getClassName(); + } + + public static CharSequence getContentDescription(Object info) { + return ((AccessibilityNodeInfo) info).getContentDescription(); + } + + public static CharSequence getPackageName(Object info) { + return ((AccessibilityNodeInfo) info).getPackageName(); + } + + public static Object getParent(Object info) { + return ((AccessibilityNodeInfo) info).getParent(); + } + + public static CharSequence getText(Object info) { + return ((AccessibilityNodeInfo) info).getText(); + } + + public static int getWindowId(Object info) { + return ((AccessibilityNodeInfo) info).getWindowId(); + } + + public static boolean isCheckable(Object info) { + return ((AccessibilityNodeInfo) info).isCheckable(); + } + + public static boolean isChecked(Object info) { + return ((AccessibilityNodeInfo) info).isChecked(); + } + + public static boolean isClickable(Object info) { + return ((AccessibilityNodeInfo) info).isClickable(); + } + + public static boolean isEnabled(Object info) { + return ((AccessibilityNodeInfo) info).isEnabled(); + } + + public static boolean isFocusable(Object info) { + return ((AccessibilityNodeInfo) info).isFocusable(); + } + + public static boolean isFocused(Object info) { + return ((AccessibilityNodeInfo) info).isFocused(); + } + + public static boolean isLongClickable(Object info) { + return ((AccessibilityNodeInfo) info).isLongClickable(); + } + + public static boolean isPassword(Object info) { + return ((AccessibilityNodeInfo) info).isPassword(); + } + + public static boolean isScrollable(Object info) { + return ((AccessibilityNodeInfo) info).isScrollable(); + } + + public static boolean isSelected(Object info) { + return ((AccessibilityNodeInfo) info).isSelected(); + } + + public static boolean performAction(Object info, int action) { + return ((AccessibilityNodeInfo) info).performAction(action); + } + + public static void setBoundsInParent(Object info, Rect bounds) { + ((AccessibilityNodeInfo) info).setBoundsInParent(bounds); + } + + public static void setBoundsInScreen(Object info, Rect bounds) { + ((AccessibilityNodeInfo) info).setBoundsInScreen(bounds); + } + + public static void setCheckable(Object info, boolean checkable) { + ((AccessibilityNodeInfo) info).setCheckable(checkable); + } + + public static void setChecked(Object info, boolean checked) { + ((AccessibilityNodeInfo) info).setChecked(checked); + } + + public static void setClassName(Object info, CharSequence className) { + ((AccessibilityNodeInfo) info).setClassName(className); + } + + public static void setClickable(Object info, boolean clickable) { + ((AccessibilityNodeInfo) info).setClickable(clickable); + } + + public static void setContentDescription(Object info, CharSequence contentDescription) { + ((AccessibilityNodeInfo) info).setContentDescription(contentDescription); + } + + public static void setEnabled(Object info, boolean enabled) { + ((AccessibilityNodeInfo) info).setEnabled(enabled); + } + + public static void setFocusable(Object info, boolean focusable) { + ((AccessibilityNodeInfo) info).setFocusable(focusable); + } + + public static void setFocused(Object info, boolean focused) { + ((AccessibilityNodeInfo) info).setFocused(focused); + } + + public static void setLongClickable(Object info, boolean longClickable) { + ((AccessibilityNodeInfo) info).setLongClickable(longClickable); + } + + public static void setPackageName(Object info, CharSequence packageName) { + ((AccessibilityNodeInfo) info).setPackageName(packageName); + } + + public static void setParent(Object info, View parent) { + ((AccessibilityNodeInfo) info).setParent(parent); + } + + public static void setPassword(Object info, boolean password) { + ((AccessibilityNodeInfo) info).setPassword(password); + } + + public static void setScrollable(Object info, boolean scrollable) { + ((AccessibilityNodeInfo) info).setScrollable(scrollable); + } + + public static void setSelected(Object info, boolean selected) { + ((AccessibilityNodeInfo) info).setSelected(selected); + } + + public static void setSource(Object info, View source) { + ((AccessibilityNodeInfo) info).setSource(source); + } + + public static void setText(Object info, CharSequence text) { + ((AccessibilityNodeInfo) info).setText(text); + } + + public static void recycle(Object info) { + ((AccessibilityNodeInfo) info).recycle(); + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityNodeProviderCompat.java b/src/android/support/v4/view/accessibility/AccessibilityNodeProviderCompat.java new file mode 100644 index 0000000..a1db285 --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityNodeProviderCompat.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.v4.view.accessibility; + +import android.os.Bundle; +import android.view.View; + +import java.util.List; + +/** + * Helper for accessing {@link android.view.accessibility.AccessibilityNodeProvider} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class AccessibilityNodeProviderCompat { + + interface AccessibilityNodeProviderImpl { + public Object newAccessibilityNodeProviderBridge(AccessibilityNodeProviderCompat compat); + } + + static class AccessibilityNodeProviderStubImpl implements AccessibilityNodeProviderImpl { + @Override + public Object newAccessibilityNodeProviderBridge(AccessibilityNodeProviderCompat compat) { + return null; + } + } + + private static final AccessibilityNodeProviderImpl IMPL; + + private final Object mProvider; + + static { + IMPL = new AccessibilityNodeProviderStubImpl(); + } + + /** + * Creates a new instance. + */ + public AccessibilityNodeProviderCompat() { + mProvider = IMPL.newAccessibilityNodeProviderBridge(this); + } + + /** + * Creates a new instance wrapping an + * {@link android.view.accessibility.AccessibilityNodeProvider}. + * + * @param provider The provider. + */ + public AccessibilityNodeProviderCompat(Object provider) { + mProvider = provider; + } + + /** + * @return The wrapped {@link android.view.accessibility.AccessibilityNodeProvider}. + */ + public Object getProvider() { + return mProvider; + } + + /** + * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual view, + * i.e. a descendant of the host View, with the given <code>virtualViewId</code> + * or the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * <p> + * The implementer is responsible for obtaining an accessibility node info from the + * pool of reusable instances and setting the desired properties of the node info + * before returning it. + * </p> + * + * @param virtualViewId A client defined virtual view id. + * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual descendant + * or the host View. + * + * @see AccessibilityNodeInfoCompat + */ + @SuppressWarnings("static-method") + public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { + return null; + } + + /** + * Performs an accessibility action on a virtual view, i.e. a descendant of the + * host View, with the given <code>virtualViewId</code> or the host View itself + * if <code>virtualViewId</code> equals to {@link View#NO_ID}. + * + * @param virtualViewId A client defined virtual view id. + * @param action The action to perform. + * @param arguments Optional arguments. + * @return True if the action was performed. + * + * @see #createAccessibilityNodeInfo(int) + * @see AccessibilityNodeInfoCompat + */ + @SuppressWarnings("static-method") + public boolean performAction(int virtualViewId, int action, Bundle arguments) { + return false; + } + + /** + * Finds {@link AccessibilityNodeInfoCompat}s by text. The match is case insensitive + * containment. The search is relative to the virtual view, i.e. a descendant of the + * host View, with the given <code>virtualViewId</code> or the host View itself + * <code>virtualViewId</code> equals to {@link View#NO_ID}. + * + * @param virtualViewId A client defined virtual view id which defined + * the root of the tree in which to perform the search. + * @param text The searched text. + * @return A list of node info. + * + * @see #createAccessibilityNodeInfo(int) + * @see AccessibilityNodeInfoCompat + */ + @SuppressWarnings("static-method") + public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(String text, + int virtualViewId) { + return null; + } + + /** + * Find the virtual view, i.e. a descendant of the host View, that has the + * specified focus type. + * + * @param focus The focus to find. One of + * {@link AccessibilityNodeInfoCompat#FOCUS_INPUT} or + * {@link AccessibilityNodeInfoCompat#FOCUS_ACCESSIBILITY}. + * @return The node info of the focused view or null. + * @see AccessibilityNodeInfoCompat#FOCUS_INPUT + * @see AccessibilityNodeInfoCompat#FOCUS_ACCESSIBILITY + */ + @SuppressWarnings("static-method") + public AccessibilityNodeInfoCompat findFocus(int focus) { + return null; + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java b/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java new file mode 100644 index 0000000..ce73b6c --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityRecordCompat.java @@ -0,0 +1,1083 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.os.Build; +import android.os.Parcelable; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; + +import java.util.Collections; +import java.util.List; + +/** + * Helper for accessing {@link android.view.accessibility.AccessibilityRecord} + * introduced after API level 4 in a backwards compatible fashion. + */ +public class AccessibilityRecordCompat { + + static interface AccessibilityRecordImpl { + public Object obtain(); + public Object obtain(Object record); + public void setSource(Object record, View source); + public void setSource(Object record, View root, int virtualDescendantId); + public AccessibilityNodeInfoCompat getSource(Object record); + public int getWindowId(Object record); + public boolean isChecked(Object record); + public void setChecked(Object record, boolean isChecked); + public boolean isEnabled(Object record); + public void setEnabled(Object record, boolean isEnabled); + public boolean isPassword(Object record); + public void setPassword(Object record, boolean isPassword); + public boolean isFullScreen(Object record); + public void setFullScreen(Object record, boolean isFullScreen); + public boolean isScrollable(Object record); + public void setScrollable(Object record, boolean scrollable); + public int getItemCount(Object record); + public void setItemCount(Object record, int itemCount); + public int getCurrentItemIndex(Object record); + public void setCurrentItemIndex(Object record, int currentItemIndex); + public int getFromIndex(Object record); + public void setFromIndex(Object record, int fromIndex); + public int getToIndex(Object record); + public void setToIndex(Object record, int toIndex); + public int getScrollX(Object record); + public void setScrollX(Object record, int scrollX); + public int getScrollY(Object record); + public void setScrollY(Object record, int scrollY); + public int getMaxScrollX(Object record); + public void setMaxScrollX(Object record, int maxScrollX); + public int getMaxScrollY(Object record); + public void setMaxScrollY(Object record, int maxScrollY); + public int getAddedCount(Object record); + public void setAddedCount(Object record, int addedCount); + public int getRemovedCount(Object record); + public void setRemovedCount(Object record, int removedCount); + public CharSequence getClassName(Object record); + public void setClassName(Object record, CharSequence className); + public List<CharSequence> getText(Object record); + public CharSequence getBeforeText(Object record); + public void setBeforeText(Object record, CharSequence beforeText); + public CharSequence getContentDescription(Object record); + public void setContentDescription(Object record, CharSequence contentDescription); + public Parcelable getParcelableData(Object record); + public void setParcelableData(Object record, Parcelable parcelableData); + public void recycle(Object record); + } + + static class AccessibilityRecordStubImpl implements AccessibilityRecordImpl { + @Override + public Object obtain() { + return null; + } + + @Override + public Object obtain(Object record) { + return null; + } + + @Override + public int getAddedCount(Object record) { + return 0; + } + + @Override + public CharSequence getBeforeText(Object record) { + return null; + } + + @Override + public CharSequence getClassName(Object record) { + return null; + } + + @Override + public CharSequence getContentDescription(Object record) { + return null; + } + + @Override + public int getCurrentItemIndex(Object record) { + return 0; + } + + @Override + public int getFromIndex(Object record) { + return 0; + } + + @Override + public int getItemCount(Object record) { + return 0; + } + + @Override + public int getMaxScrollX(Object record) { + return 0; + } + + @Override + public int getMaxScrollY(Object record) { + return 0; + } + + @Override + public Parcelable getParcelableData(Object record) { + return null; + } + + @Override + public int getRemovedCount(Object record) { + return 0; + } + + @Override + public int getScrollX(Object record) { + return 0; + } + + @Override + public int getScrollY(Object record) { + return 0; + } + + @Override + public AccessibilityNodeInfoCompat getSource(Object record) { + return null; + } + + @Override + public List<CharSequence> getText(Object record) { + return Collections.emptyList(); + } + + @Override + public int getToIndex(Object record) { + return 0; + } + + @Override + public int getWindowId(Object record) { + return 0; + } + + @Override + public boolean isChecked(Object record) { + return false; + } + + @Override + public boolean isEnabled(Object record) { + return false; + } + + @Override + public boolean isFullScreen(Object record) { + return false; + } + + @Override + public boolean isPassword(Object record) { + return false; + } + + @Override + public boolean isScrollable(Object record) { + return false; + } + + @Override + public void recycle(Object record) { + return; + } + + @Override + public void setAddedCount(Object record, int addedCount) { + return; + } + + @Override + public void setBeforeText(Object record, CharSequence beforeText) { + return; + } + + @Override + public void setChecked(Object record, boolean isChecked) { + return; + } + + @Override + public void setClassName(Object record, CharSequence className) { + return; + } + + @Override + public void setContentDescription(Object record, CharSequence contentDescription) { + return; + } + + @Override + public void setCurrentItemIndex(Object record, int currentItemIndex) { + return; + } + + @Override + public void setEnabled(Object record, boolean isEnabled) { + return; + } + + @Override + public void setFromIndex(Object record, int fromIndex) { + return; + } + + @Override + public void setFullScreen(Object record, boolean isFullScreen) { + return; + } + + @Override + public void setItemCount(Object record, int itemCount) { + return; + } + + @Override + public void setMaxScrollX(Object record, int maxScrollX) { + return; + } + + @Override + public void setMaxScrollY(Object record, int maxScrollY) { + return; + } + + @Override + public void setParcelableData(Object record, Parcelable parcelableData) { + return; + } + + @Override + public void setPassword(Object record, boolean isPassword) { + return; + } + + @Override + public void setRemovedCount(Object record, int removedCount) { + return; + } + + @Override + public void setScrollX(Object record, int scrollX) { + return; + } + + @Override + public void setScrollY(Object record, int scrollY) { + return; + } + + @Override + public void setScrollable(Object record, boolean scrollable) { + return; + } + + @Override + public void setSource(Object record, View source) { + return; + } + + @Override + public void setSource(Object record, View root, int virtualDescendantId) { + return; + } + + @Override + public void setToIndex(Object record, int toIndex) { + return; + } + } + + static class AccessibilityRecordIcsImpl extends AccessibilityRecordStubImpl { + @Override + public Object obtain() { + return AccessibilityRecordCompatIcs.obtain(); + } + + @Override + public Object obtain(Object record) { + return AccessibilityRecordCompatIcs.obtain(record); + } + + @Override + public int getAddedCount(Object record) { + return AccessibilityRecordCompatIcs.getAddedCount(record); + } + + @Override + public CharSequence getBeforeText(Object record) { + return AccessibilityRecordCompatIcs.getBeforeText(record); + } + + @Override + public CharSequence getClassName(Object record) { + return AccessibilityRecordCompatIcs.getClassName(record); + } + + @Override + public CharSequence getContentDescription(Object record) { + return AccessibilityRecordCompatIcs.getContentDescription(record); + } + + @Override + public int getCurrentItemIndex(Object record) { + return AccessibilityRecordCompatIcs.getCurrentItemIndex(record); + } + + @Override + public int getFromIndex(Object record) { + return AccessibilityRecordCompatIcs.getFromIndex(record); + } + + @Override + public int getItemCount(Object record) { + return AccessibilityRecordCompatIcs.getItemCount(record); + } + + @Override + public Parcelable getParcelableData(Object record) { + return AccessibilityRecordCompatIcs.getParcelableData(record); + } + + @Override + public int getRemovedCount(Object record) { + return AccessibilityRecordCompatIcs.getRemovedCount(record); + } + + @Override + public int getScrollX(Object record) { + return AccessibilityRecordCompatIcs.getScrollX(record); + } + + @Override + public int getScrollY(Object record) { + return AccessibilityRecordCompatIcs.getScrollY(record); + } + + @Override + public AccessibilityNodeInfoCompat getSource(Object record) { + return AccessibilityNodeInfoCompat.wrapNonNullInstance( + AccessibilityRecordCompatIcs.getSource(record)); + } + + @Override + public List<CharSequence> getText(Object record) { + return AccessibilityRecordCompatIcs.getText(record); + } + + @Override + public int getToIndex(Object record) { + return AccessibilityRecordCompatIcs.getToIndex(record); + } + + @Override + public int getWindowId(Object record) { + return AccessibilityRecordCompatIcs.getWindowId(record); + } + + @Override + public boolean isChecked(Object record) { + return AccessibilityRecordCompatIcs.isChecked(record); + } + + @Override + public boolean isEnabled(Object record) { + return AccessibilityRecordCompatIcs.isEnabled(record); + } + + @Override + public boolean isFullScreen(Object record) { + return AccessibilityRecordCompatIcs.isFullScreen(record); + } + + @Override + public boolean isPassword(Object record) { + return AccessibilityRecordCompatIcs.isPassword(record); + } + + @Override + public boolean isScrollable(Object record) { + return AccessibilityRecordCompatIcs.isScrollable(record); + } + + @Override + public void recycle(Object record) { + AccessibilityRecordCompatIcs.recycle(record); + } + + @Override + public void setAddedCount(Object record, int addedCount) { + AccessibilityRecordCompatIcs.setAddedCount(record, addedCount); + } + + @Override + public void setBeforeText(Object record, CharSequence beforeText) { + AccessibilityRecordCompatIcs.setBeforeText(record, beforeText); + } + + @Override + public void setChecked(Object record, boolean isChecked) { + AccessibilityRecordCompatIcs.setChecked(record, isChecked); + } + + @Override + public void setClassName(Object record, CharSequence className) { + AccessibilityRecordCompatIcs.setClassName(record, className); + } + + @Override + public void setContentDescription(Object record, CharSequence contentDescription) { + AccessibilityRecordCompatIcs.setContentDescription(record, contentDescription); + } + + @Override + public void setCurrentItemIndex(Object record, int currentItemIndex) { + AccessibilityRecordCompatIcs.setCurrentItemIndex(record, currentItemIndex); + } + + @Override + public void setEnabled(Object record, boolean isEnabled) { + AccessibilityRecordCompatIcs.setEnabled(record, isEnabled); + } + + @Override + public void setFromIndex(Object record, int fromIndex) { + AccessibilityRecordCompatIcs.setFromIndex(record, fromIndex); + } + + @Override + public void setFullScreen(Object record, boolean isFullScreen) { + AccessibilityRecordCompatIcs.setFullScreen(record, isFullScreen); + } + + @Override + public void setItemCount(Object record, int itemCount) { + AccessibilityRecordCompatIcs.setItemCount(record, itemCount); + } + + @Override + public void setParcelableData(Object record, Parcelable parcelableData) { + AccessibilityRecordCompatIcs.setParcelableData(record, parcelableData); + } + + @Override + public void setPassword(Object record, boolean isPassword) { + AccessibilityRecordCompatIcs.setPassword(record, isPassword); + } + + @Override + public void setRemovedCount(Object record, int removedCount) { + AccessibilityRecordCompatIcs.setRemovedCount(record, removedCount); + } + + @Override + public void setScrollX(Object record, int scrollX) { + AccessibilityRecordCompatIcs.setScrollX(record, scrollX); + } + + @Override + public void setScrollY(Object record, int scrollY) { + AccessibilityRecordCompatIcs.setScrollY(record, scrollY); + } + + @Override + public void setScrollable(Object record, boolean scrollable) { + AccessibilityRecordCompatIcs.setScrollable(record, scrollable); + } + + @Override + public void setSource(Object record, View source) { + AccessibilityRecordCompatIcs.setSource(record, source); + } + + @Override + public void setToIndex(Object record, int toIndex) { + AccessibilityRecordCompatIcs.setToIndex(record, toIndex); + } + } + + static class AccessibilityRecordIcsMr1Impl extends AccessibilityRecordIcsImpl { + @Override + public int getMaxScrollX(Object record) { + return AccessibilityRecordCompatIcsMr1.getMaxScrollX(record); + } + + @Override + public int getMaxScrollY(Object record) { + return AccessibilityRecordCompatIcsMr1.getMaxScrollY(record); + } + + @Override + public void setMaxScrollX(Object record, int maxScrollX) { + AccessibilityRecordCompatIcsMr1.setMaxScrollX(record, maxScrollX); + } + + @Override + public void setMaxScrollY(Object record, int maxScrollY) { + AccessibilityRecordCompatIcsMr1.setMaxScrollY(record, maxScrollY); + } + } + + static { + if (Build.VERSION.SDK_INT >= 15) { // ICS MR1 + IMPL = new AccessibilityRecordIcsMr1Impl(); + } else if (Build.VERSION.SDK_INT >= 14) { // ICS + IMPL = new AccessibilityRecordIcsImpl(); + } else { + IMPL = new AccessibilityRecordStubImpl(); + } + } + + private static final AccessibilityRecordImpl IMPL; + + private final Object mRecord; + + /** + * @deprecated This is not type safe. If you want to modify an + * {@link AccessibilityEvent}'s properties defined in + * {@link android.view.accessibility.AccessibilityRecord} use + * {@link AccessibilityEventCompat#asRecord(AccessibilityEvent)}. This method will be removed + * in a subsequent release of the support library. + */ + @Deprecated + public AccessibilityRecordCompat(Object record) { + mRecord = record; + } + + /** + * @return The wrapped implementation. + * + * @deprecated This method will be removed in a subsequent release of + * the support library. + */ + @Deprecated + public Object getImpl() { + return mRecord; + } + + /** + * Returns a cached instance if such is available or a new one is + * instantiated. The instance is initialized with data from the + * given record. + * + * @return An instance. + */ + public static AccessibilityRecordCompat obtain(AccessibilityRecordCompat record) { + return new AccessibilityRecordCompat(IMPL.obtain(record.mRecord)); + } + + /** + * Returns a cached instance if such is available or a new one is + * instantiated. + * + * @return An instance. + */ + public static AccessibilityRecordCompat obtain() { + return new AccessibilityRecordCompat(IMPL.obtain()); + } + + /** + * Sets the event source. + * + * @param source The source. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setSource(View source) { + IMPL.setSource(mRecord, source); + } + + /** + * Sets the source to be a virtual descendant of the given <code>root</code>. + * If <code>virtualDescendantId</code> equals to {@link View#NO_ID} the root + * is set as the source. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + * </p> + * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setSource(View root, int virtualDescendantId) { + IMPL.setSource(mRecord, root, virtualDescendantId); + } + + /** + * Gets the {@link android.view.accessibility.AccessibilityNodeInfo} of + * the event source. + * <p> + * <strong>Note:</strong> It is a client responsibility to recycle the + * received info by calling + * {@link android.view.accessibility.AccessibilityNodeInfo#recycle() + * AccessibilityNodeInfo#recycle()} to avoid creating of multiple instances. + *</p> + * + * @return The info of the source. + */ + public AccessibilityNodeInfoCompat getSource() { + return IMPL.getSource(mRecord); + } + + /** + * Gets the id of the window from which the event comes from. + * + * @return The window id. + */ + public int getWindowId() { + return IMPL.getWindowId(mRecord); + } + + /** + * Gets if the source is checked. + * + * @return True if the view is checked, false otherwise. + */ + public boolean isChecked() { + return IMPL.isChecked(mRecord); + } + + /** + * Sets if the source is checked. + * + * @param isChecked True if the view is checked, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setChecked(boolean isChecked) { + IMPL.setChecked(mRecord, isChecked); + } + + /** + * Gets if the source is enabled. + * + * @return True if the view is enabled, false otherwise. + */ + public boolean isEnabled() { + return IMPL.isEnabled(mRecord); + } + + /** + * Sets if the source is enabled. + * + * @param isEnabled True if the view is enabled, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setEnabled(boolean isEnabled) { + IMPL.setEnabled(mRecord, isEnabled); + } + + /** + * Gets if the source is a password field. + * + * @return True if the view is a password field, false otherwise. + */ + public boolean isPassword() { + return IMPL.isPassword(mRecord); + } + + /** + * Sets if the source is a password field. + * + * @param isPassword True if the view is a password field, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setPassword(boolean isPassword) { + IMPL.setPassword(mRecord, isPassword); + } + + /** + * Gets if the source is taking the entire screen. + * + * @return True if the source is full screen, false otherwise. + */ + public boolean isFullScreen() { + return IMPL.isFullScreen(mRecord); + } + + /** + * Sets if the source is taking the entire screen. + * + * @param isFullScreen True if the source is full screen, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setFullScreen(boolean isFullScreen) { + IMPL.setFullScreen(mRecord, isFullScreen); + } + + /** + * Gets if the source is scrollable. + * + * @return True if the source is scrollable, false otherwise. + */ + public boolean isScrollable() { + return IMPL.isScrollable(mRecord); + } + + /** + * Sets if the source is scrollable. + * + * @param scrollable True if the source is scrollable, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setScrollable(boolean scrollable) { + IMPL.setScrollable(mRecord, scrollable); + } + + /** + * Gets the number of items that can be visited. + * + * @return The number of items. + */ + public int getItemCount() { + return IMPL.getItemCount(mRecord); + } + + /** + * Sets the number of items that can be visited. + * + * @param itemCount The number of items. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setItemCount(int itemCount) { + IMPL.setItemCount(mRecord, itemCount); + } + + /** + * Gets the index of the source in the list of items the can be visited. + * + * @return The current item index. + */ + public int getCurrentItemIndex() { + return IMPL.getCurrentItemIndex(mRecord); + } + + /** + * Sets the index of the source in the list of items that can be visited. + * + * @param currentItemIndex The current item index. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setCurrentItemIndex(int currentItemIndex) { + IMPL.setCurrentItemIndex(mRecord, currentItemIndex); + } + + /** + * Gets the index of the first character of the changed sequence, + * or the beginning of a text selection or the index of the first + * visible item when scrolling. + * + * @return The index of the first character or selection + * start or the first visible item. + */ + public int getFromIndex() { + return IMPL.getFromIndex(mRecord); + } + + /** + * Sets the index of the first character of the changed sequence + * or the beginning of a text selection or the index of the first + * visible item when scrolling. + * + * @param fromIndex The index of the first character or selection + * start or the first visible item. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setFromIndex(int fromIndex) { + IMPL.setFromIndex(mRecord, fromIndex); + } + + /** + * Gets the index of text selection end or the index of the last + * visible item when scrolling. + * + * @return The index of selection end or last item index. + */ + public int getToIndex() { + return IMPL.getToIndex(mRecord); + } + + /** + * Sets the index of text selection end or the index of the last + * visible item when scrolling. + * + * @param toIndex The index of selection end or last item index. + */ + public void setToIndex(int toIndex) { + IMPL.setToIndex(mRecord, toIndex); + } + + /** + * Gets the scroll offset of the source left edge in pixels. + * + * @return The scroll. + */ + public int getScrollX() { + return IMPL.getScrollX(mRecord); + } + + /** + * Sets the scroll offset of the source left edge in pixels. + * + * @param scrollX The scroll. + */ + public void setScrollX(int scrollX) { + IMPL.setScrollX(mRecord, scrollX); + } + + /** + * Gets the scroll offset of the source top edge in pixels. + * + * @return The scroll. + */ + public int getScrollY() { + return IMPL.getScrollY(mRecord); + } + + /** + * Sets the scroll offset of the source top edge in pixels. + * + * @param scrollY The scroll. + */ + public void setScrollY(int scrollY) { + IMPL.setScrollY(mRecord, scrollY); + } + + /** + * Gets the max scroll offset of the source left edge in pixels. + * + * @return The max scroll. + */ + public int getMaxScrollX() { + return IMPL.getMaxScrollX(mRecord); + } + /** + * Sets the max scroll offset of the source left edge in pixels. + * + * @param maxScrollX The max scroll. + */ + public void setMaxScrollX(int maxScrollX) { + IMPL.setMaxScrollX(mRecord, maxScrollX); + } + + /** + * Gets the max scroll offset of the source top edge in pixels. + * + * @return The max scroll. + */ + public int getMaxScrollY() { + return IMPL.getMaxScrollY(mRecord); + } + + /** + * Sets the max scroll offset of the source top edge in pixels. + * + * @param maxScrollY The max scroll. + */ + public void setMaxScrollY(int maxScrollY) { + IMPL.setMaxScrollY(mRecord, maxScrollY); + } + + /** + * Gets the number of added characters. + * + * @return The number of added characters. + */ + public int getAddedCount() { + return IMPL.getAddedCount(mRecord); + } + + /** + * Sets the number of added characters. + * + * @param addedCount The number of added characters. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setAddedCount(int addedCount) { + IMPL.setAddedCount(mRecord, addedCount); + } + + /** + * Gets the number of removed characters. + * + * @return The number of removed characters. + */ + public int getRemovedCount() { + return IMPL.getRemovedCount(mRecord); + } + + /** + * Sets the number of removed characters. + * + * @param removedCount The number of removed characters. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setRemovedCount(int removedCount) { + IMPL.setRemovedCount(mRecord, removedCount); + } + + /** + * Gets the class name of the source. + * + * @return The class name. + */ + public CharSequence getClassName() { + return IMPL.getClassName(mRecord); + } + + /** + * Sets the class name of the source. + * + * @param className The lass name. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setClassName(CharSequence className) { + IMPL.setClassName(mRecord, className); + } + + /** + * Gets the text of the event. The index in the list represents the priority + * of the text. Specifically, the lower the index the higher the priority. + * + * @return The text. + */ + public List<CharSequence> getText() { + return IMPL.getText(mRecord); + } + + /** + * Sets the text before a change. + * + * @return The text before the change. + */ + public CharSequence getBeforeText() { + return IMPL.getBeforeText(mRecord); + } + + /** + * Sets the text before a change. + * + * @param beforeText The text before the change. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setBeforeText(CharSequence beforeText) { + IMPL.setBeforeText(mRecord, beforeText); + } + + /** + * Gets the description of the source. + * + * @return The description. + */ + public CharSequence getContentDescription() { + return IMPL.getContentDescription(mRecord); + } + + /** + * Sets the description of the source. + * + * @param contentDescription The description. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setContentDescription(CharSequence contentDescription) { + IMPL.setContentDescription(mRecord, contentDescription); + } + + /** + * Gets the {@link Parcelable} data. + * + * @return The parcelable data. + */ + public Parcelable getParcelableData() { + return IMPL.getParcelableData(mRecord); + } + + /** + * Sets the {@link Parcelable} data of the event. + * + * @param parcelableData The parcelable data. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setParcelableData(Parcelable parcelableData) { + IMPL.setParcelableData(mRecord, parcelableData); + } + + /** + * Return an instance back to be reused. + * <p> + * <strong>Note:</strong> You must not touch the object after calling this + * function. + * </p> + * + * @throws IllegalStateException If the record is already recycled. + */ + public void recycle() { + IMPL.recycle(mRecord); + } + + @Override + public int hashCode() { + return (mRecord == null) ? 0 : mRecord.hashCode(); + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AccessibilityRecordCompat other = (AccessibilityRecordCompat) obj; + if (mRecord == null) { + if (other.mRecord != null) { + return false; + } + } else if (!mRecord.equals(other.mRecord)) { + return false; + } + return true; + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityRecordCompatIcs.java b/src/android/support/v4/view/accessibility/AccessibilityRecordCompatIcs.java new file mode 100644 index 0000000..0ebcc6a --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityRecordCompatIcs.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.os.Parcelable; +import android.view.View; +import android.view.accessibility.AccessibilityRecord; + +import java.util.List; + +/** + * ICS specific AccessibilityRecord API implementation. + */ +class AccessibilityRecordCompatIcs { + + public static Object obtain() { + return AccessibilityRecord.obtain(); + } + + public static Object obtain(Object record) { + return AccessibilityRecord.obtain((AccessibilityRecord) record); + } + + public static int getAddedCount(Object record) { + return ((AccessibilityRecord) record).getAddedCount(); + } + + public static CharSequence getBeforeText(Object record) { + return ((AccessibilityRecord) record).getBeforeText(); + } + + public static CharSequence getClassName(Object record) { + return ((AccessibilityRecord) record).getClassName(); + } + + public static CharSequence getContentDescription(Object record) { + return ((AccessibilityRecord) record).getContentDescription(); + } + + public static int getCurrentItemIndex(Object record) { + return ((AccessibilityRecord) record).getCurrentItemIndex(); + } + + public static int getFromIndex(Object record) { + return ((AccessibilityRecord) record).getFromIndex(); + } + + public static int getItemCount(Object record) { + return ((AccessibilityRecord) record).getItemCount(); + } + + public static Parcelable getParcelableData(Object record) { + return ((AccessibilityRecord) record).getParcelableData(); + } + + public static int getRemovedCount(Object record) { + return ((AccessibilityRecord) record).getRemovedCount(); + } + + public static int getScrollX(Object record) { + return ((AccessibilityRecord) record).getScrollX(); + } + + public static int getScrollY(Object record) { + return ((AccessibilityRecord) record).getScrollY(); + } + + public static Object getSource(Object record) { + return ((AccessibilityRecord) record).getSource(); + } + + public static List<CharSequence> getText(Object record) { + return ((AccessibilityRecord) record).getText(); + } + + public static int getToIndex(Object record) { + return ((AccessibilityRecord) record).getToIndex(); + } + + public static int getWindowId(Object record) { + return ((AccessibilityRecord) record).getWindowId(); + } + + public static boolean isChecked(Object record) { + return ((AccessibilityRecord) record).isChecked(); + } + + public static boolean isEnabled(Object record) { + return ((AccessibilityRecord) record).isEnabled(); + } + + public static boolean isFullScreen(Object record) { + return ((AccessibilityRecord) record).isFullScreen(); + } + + public static boolean isPassword(Object record) { + return ((AccessibilityRecord) record).isPassword(); + } + + public static boolean isScrollable(Object record) { + return ((AccessibilityRecord) record).isScrollable(); + } + + public static void recycle(Object record) { + ((AccessibilityRecord) record).recycle(); + } + + public static void setAddedCount(Object record, int addedCount) { + ((AccessibilityRecord) record).setAddedCount(addedCount); + } + + public static void setBeforeText(Object record, CharSequence beforeText) { + ((AccessibilityRecord) record).setBeforeText(beforeText); + } + + public static void setChecked(Object record, boolean isChecked) { + ((AccessibilityRecord) record).setChecked(isChecked); + } + + public static void setClassName(Object record, CharSequence className) { + ((AccessibilityRecord) record).setClassName(className); + } + + public static void setContentDescription(Object record, CharSequence contentDescription) { + ((AccessibilityRecord) record).setContentDescription(contentDescription); + } + + public static void setCurrentItemIndex(Object record, int currentItemIndex) { + ((AccessibilityRecord) record).setCurrentItemIndex(currentItemIndex); + } + + public static void setEnabled(Object record, boolean isEnabled) { + ((AccessibilityRecord) record).setEnabled(isEnabled); + } + + public static void setFromIndex(Object record, int fromIndex) { + ((AccessibilityRecord) record).setFromIndex(fromIndex); + } + + public static void setFullScreen(Object record, boolean isFullScreen) { + ((AccessibilityRecord) record).setFullScreen(isFullScreen); + } + + public static void setItemCount(Object record, int itemCount) { + ((AccessibilityRecord) record).setItemCount(itemCount); + } + + public static void setParcelableData(Object record, Parcelable parcelableData) { + ((AccessibilityRecord) record).setParcelableData(parcelableData); + } + + public static void setPassword(Object record, boolean isPassword) { + ((AccessibilityRecord) record).setPassword(isPassword); + } + + public static void setRemovedCount(Object record, int removedCount) { + ((AccessibilityRecord) record).setRemovedCount(removedCount); + } + + public static void setScrollX(Object record, int scrollX) { + ((AccessibilityRecord) record).setScrollX(scrollX); + } + + public static void setScrollY(Object record, int scrollY) { + ((AccessibilityRecord) record).setScrollY(scrollY); + } + + public static void setScrollable(Object record, boolean scrollable) { + ((AccessibilityRecord) record).setScrollable(scrollable); + } + + public static void setSource(Object record, View source) { + ((AccessibilityRecord) record).setSource(source); + } + + public static void setToIndex(Object record, int toIndex) { + ((AccessibilityRecord) record).setToIndex(toIndex); + } +} diff --git a/src/android/support/v4/view/accessibility/AccessibilityRecordCompatIcsMr1.java b/src/android/support/v4/view/accessibility/AccessibilityRecordCompatIcsMr1.java new file mode 100644 index 0000000..94164d7 --- /dev/null +++ b/src/android/support/v4/view/accessibility/AccessibilityRecordCompatIcsMr1.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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.support.v4.view.accessibility; + +import android.view.accessibility.AccessibilityRecord; + +/** + * ICS MR1 specific AccessibilityRecord API implementation. + */ +class AccessibilityRecordCompatIcsMr1 { + + public static int getMaxScrollX(Object record) { + return ((AccessibilityRecord) record).getMaxScrollX(); + } + + public static int getMaxScrollY(Object record) { + return ((AccessibilityRecord) record).getMaxScrollY(); + } + public static void setMaxScrollX(Object record, int maxScrollX) { + ((AccessibilityRecord) record).setMaxScrollX(maxScrollX); + } + + public static void setMaxScrollY(Object record, int maxScrollY) { + ((AccessibilityRecord) record).setMaxScrollY(maxScrollY); + } +} diff --git a/src/android/support/v4/widget/EdgeEffectCompat.java b/src/android/support/v4/widget/EdgeEffectCompat.java new file mode 100644 index 0000000..9436c23 --- /dev/null +++ b/src/android/support/v4/widget/EdgeEffectCompat.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2011 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.support.v4.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.os.Build; + +/** + * Helper for accessing {@link android.widget.EdgeEffect} introduced after + * API level 4 in a backwards compatible fashion. + * + * This class is used to access {@link android.widget.EdgeEffect} on platform versions + * that support it. When running on older platforms it will result in no-ops. It should + * be used by views that wish to use the standard Android visual effects at the edges + * of scrolling containers. + */ +public class EdgeEffectCompat { + private Object mEdgeEffect; + + private static final EdgeEffectImpl IMPL; + + static { + if (Build.VERSION.SDK_INT >= 14) { // ICS + IMPL = new EdgeEffectIcsImpl(); + } else { + IMPL = new BaseEdgeEffectImpl(); + } + } + + interface EdgeEffectImpl { + public Object newEdgeEffect(Context context); + public void setSize(Object edgeEffect, int width, int height); + public boolean isFinished(Object edgeEffect); + public void finish(Object edgeEffect); + public boolean onPull(Object edgeEffect, float deltaDistance); + public boolean onRelease(Object edgeEffect); + public boolean onAbsorb(Object edgeEffect, int velocity); + public boolean draw(Object edgeEffect, Canvas canvas); + } + + /** + * Null implementation to use pre-ICS + */ + static class BaseEdgeEffectImpl implements EdgeEffectImpl { + @Override + public Object newEdgeEffect(Context context) { + return null; + } + + @Override + public void setSize(Object edgeEffect, int width, int height) { + return; + } + + @Override + public boolean isFinished(Object edgeEffect) { + return true; + } + + @Override + public void finish(Object edgeEffect) { + return; + } + + @Override + public boolean onPull(Object edgeEffect, float deltaDistance) { + return false; + } + + @Override + public boolean onRelease(Object edgeEffect) { + return false; + } + + @Override + public boolean onAbsorb(Object edgeEffect, int velocity) { + return false; + } + + @Override + public boolean draw(Object edgeEffect, Canvas canvas) { + return false; + } + } + + static class EdgeEffectIcsImpl implements EdgeEffectImpl { + @Override + public Object newEdgeEffect(Context context) { + return EdgeEffectCompatIcs.newEdgeEffect(context); + } + + @Override + public void setSize(Object edgeEffect, int width, int height) { + EdgeEffectCompatIcs.setSize(edgeEffect, width, height); + } + + @Override + public boolean isFinished(Object edgeEffect) { + return EdgeEffectCompatIcs.isFinished(edgeEffect); + } + + @Override + public void finish(Object edgeEffect) { + EdgeEffectCompatIcs.finish(edgeEffect); + } + + @Override + public boolean onPull(Object edgeEffect, float deltaDistance) { + return EdgeEffectCompatIcs.onPull(edgeEffect, deltaDistance); + } + + @Override + public boolean onRelease(Object edgeEffect) { + return EdgeEffectCompatIcs.onRelease(edgeEffect); + } + + @Override + public boolean onAbsorb(Object edgeEffect, int velocity) { + return EdgeEffectCompatIcs.onAbsorb(edgeEffect, velocity); + } + + @Override + public boolean draw(Object edgeEffect, Canvas canvas) { + return EdgeEffectCompatIcs.draw(edgeEffect, canvas); + } + } + + /** + * Construct a new EdgeEffect themed using the given context. + * + * <p>Note: On platform versions that do not support EdgeEffect, all operations + * on the newly constructed object will be mocked/no-ops.</p> + * + * @param context Context to use for theming the effect + */ + public EdgeEffectCompat(Context context) { + mEdgeEffect = IMPL.newEdgeEffect(context); + } + + /** + * Set the size of this edge effect in pixels. + * + * @param width Effect width in pixels + * @param height Effect height in pixels + */ + public void setSize(int width, int height) { + IMPL.setSize(mEdgeEffect, width, height); + } + + /** + * Reports if this EdgeEffectCompat's animation is finished. If this method returns false + * after a call to {@link #draw(Canvas)} the host widget should schedule another + * drawing pass to continue the animation. + * + * @return true if animation is finished, false if drawing should continue on the next frame. + */ + public boolean isFinished() { + return IMPL.isFinished(mEdgeEffect); + } + + /** + * Immediately finish the current animation. + * After this call {@link #isFinished()} will return true. + */ + public void finish() { + IMPL.finish(mEdgeEffect); + } + + /** + * A view should call this when content is pulled away from an edge by the user. + * This will update the state of the current visual effect and its associated animation. + * The host view should always {@link android.view.View#invalidate()} if this method + * returns true and draw the results accordingly. + * + * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to + * 1.f (full length of the view) or negative values to express change + * back toward the edge reached to initiate the effect. + * @return true if the host view should call invalidate, false if it should not. + */ + public boolean onPull(float deltaDistance) { + return IMPL.onPull(mEdgeEffect, deltaDistance); + } + + /** + * Call when the object is released after being pulled. + * This will begin the "decay" phase of the effect. After calling this method + * the host view should {@link android.view.View#invalidate()} if this method + * returns true and thereby draw the results accordingly. + * + * @return true if the host view should invalidate, false if it should not. + */ + public boolean onRelease() { + return IMPL.onRelease(mEdgeEffect); + } + + /** + * Call when the effect absorbs an impact at the given velocity. + * Used when a fling reaches the scroll boundary. + * + * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller}, + * the method <code>getCurrVelocity</code> will provide a reasonable approximation + * to use here.</p> + * + * @param velocity Velocity at impact in pixels per second. + * @return true if the host view should invalidate, false if it should not. + */ + public boolean onAbsorb(int velocity) { + return IMPL.onAbsorb(mEdgeEffect, velocity); + } + + /** + * Draw into the provided canvas. Assumes that the canvas has been rotated + * accordingly and the size has been set. The effect will be drawn the full + * width of X=0 to X=width, beginning from Y=0 and extending to some factor < + * 1.f of height. + * + * @param canvas Canvas to draw into + * @return true if drawing should continue beyond this frame to continue the + * animation + */ + public boolean draw(Canvas canvas) { + return IMPL.draw(mEdgeEffect, canvas); + } +} diff --git a/src/android/support/v4/widget/EdgeEffectCompatIcs.java b/src/android/support/v4/widget/EdgeEffectCompatIcs.java new file mode 100644 index 0000000..c02eeb4 --- /dev/null +++ b/src/android/support/v4/widget/EdgeEffectCompatIcs.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 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.support.v4.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.widget.EdgeEffect; + +/** + * Stub implementation that contains a real EdgeEffect on ICS. + * + * This class is an implementation detail for EdgeEffectCompat + * and should not be used directly. + */ +class EdgeEffectCompatIcs { + public static Object newEdgeEffect(Context context) { + return new EdgeEffect(context); + } + + public static void setSize(Object edgeEffect, int width, int height) { + ((EdgeEffect) edgeEffect).setSize(width, height); + } + + public static boolean isFinished(Object edgeEffect) { + return ((EdgeEffect) edgeEffect).isFinished(); + } + + public static void finish(Object edgeEffect) { + ((EdgeEffect) edgeEffect).finish(); + } + + public static boolean onPull(Object edgeEffect, float deltaDistance) { + ((EdgeEffect) edgeEffect).onPull(deltaDistance); + return true; + } + + public static boolean onRelease(Object edgeEffect) { + EdgeEffect eff = (EdgeEffect) edgeEffect; + eff.onRelease(); + return eff.isFinished(); + } + + public static boolean onAbsorb(Object edgeEffect, int velocity) { + ((EdgeEffect) edgeEffect).onAbsorb(velocity); + return true; + } + + public static boolean draw(Object edgeEffect, Canvas canvas) { + return ((EdgeEffect) edgeEffect).draw(canvas); + } +}
\ No newline at end of file diff --git a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java index c4950a3..0fb489f 100644 --- a/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java +++ b/src/org/cyanogenmod/wallpapers/photophase/PhotoPhaseRenderer.java @@ -167,7 +167,7 @@ public class PhotoPhaseRenderer implements GLSurfaceView.Renderer { // Select a new transition mWorld.selectRandomTransition(); mLastRunningTransition = System.currentTimeMillis(); - + // Now force continuously render while transition is applied mDispatcher.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); } @@ -514,7 +514,6 @@ public class PhotoPhaseRenderer implements GLSurfaceView.Renderer { * Recreate the world */ void recreateWorld() { -System.out.println("recreateWorld(): " + mIsPaused); if (mIsPaused) { mRecreateWorld = true; return; diff --git a/src/org/cyanogenmod/wallpapers/photophase/adapters/DispositionAdapter.java b/src/org/cyanogenmod/wallpapers/photophase/adapters/DispositionAdapter.java new file mode 100644 index 0000000..4fa4926 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/adapters/DispositionAdapter.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.adapters; + +import android.content.Context; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.model.Dispositions; +import org.cyanogenmod.wallpapers.photophase.widgets.DispositionView; +import org.cyanogenmod.wallpapers.photophase.widgets.DispositionView.OnFrameSelectedListener; +import org.cyanogenmod.wallpapers.photophase.widgets.ResizeFrame; + +import java.util.List; + +/** + * An {@link PagerAdapter} implementation for display all current templates + */ +public class DispositionAdapter extends PagerAdapter { + + final List<Dispositions> mDispositions; + private final ResizeFrame mResizeFrame; + private final OnFrameSelectedListener mCallback; + + private final SparseArray<DispositionView> mCurrentViews; + + private LayoutInflater mInflater; + + boolean mFirstAnimation; + + /** + * Constructor of <code>DispositionAdapter</code>. + * + * @param ctx The current context + * @param dispositions An array with all dispositions + * @param resizeFrame The resize frame + * @param callback The callback where return selection events + */ + public DispositionAdapter(Context ctx, List<Dispositions> dispositions, + ResizeFrame resizeFrame, OnFrameSelectedListener callback) { + super(); + mDispositions = dispositions; + mResizeFrame = resizeFrame; + mCallback = callback; + mFirstAnimation = true; + mInflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mCurrentViews = new SparseArray<DispositionView>(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getCount() { + return mDispositions.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object instantiateItem(ViewGroup container, final int position) { + final DispositionView view = (DispositionView)mInflater.inflate( + R.layout.disposition_view, null); + if (position == 0) { + view.setResizeFrame(mResizeFrame); + view.setOnFrameSelectedListener(mCallback); + } + view.post(new Runnable() { + @Override + public void run() { + view.setDispositions(mDispositions.get(position), + position == 0 && mFirstAnimation); + mFirstAnimation = false; + } + }); + ((ViewPager)container).addView(view, 0); + mCurrentViews.put(position, view); + return view; + } + + /** + * {@inheritDoc} + */ + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + mCurrentViews.remove(position); + ((ViewPager)container).removeView((View)object); + } + + /** + * Method that returns the current view + * + * @param position The position of the item to return + * @return DispositionView The view or null if is not instance + */ + public DispositionView getView(int position) { + return mCurrentViews.get(position); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isViewFromObject(View view, Object object) { + return view == ((View)object) || view == mResizeFrame; + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/model/Dispositions.java b/src/org/cyanogenmod/wallpapers/photophase/model/Dispositions.java new file mode 100644 index 0000000..fc27853 --- /dev/null +++ b/src/org/cyanogenmod/wallpapers/photophase/model/Dispositions.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 The CyanogenMod 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 org.cyanogenmod.wallpapers.photophase.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * An array of {@link Disposition} classes. + */ +public class Dispositions { + + private final List<Disposition> mDispositions; + private final int mRows; + private final int mCols; + + /** + * Constructor of <code>Dispositions</code> + * + * @param dispositions List of all dispositions + * @param rows The number of rows of the dispositions + * @param cols The number of columns of the dispositions + */ + public Dispositions(List<Disposition> dispositions, int rows, int cols) { + super(); + mDispositions = dispositions; + mRows = rows; + mCols = cols; + } + + /** + * Method that returns an array of all the dispositions + * + * @return List<Disposition> All the dispositions + */ + public List<Disposition> getDispositions() { + return new ArrayList<Disposition>(mDispositions); + } + + /** + * Method that returns the number of rows of the dispositions + * + * @return int The number of rows of the dispositions + */ + public int getRows() { + return mRows; + } + + /** + * Method that returns the number of columns of the dispositions + * + * @return int The number of columns of the dispositions + */ + public int getCols() { + return mCols; + } + +} diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java index 7071e76..5040178 100644 --- a/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/DispositionFragment.java @@ -20,41 +20,44 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.preference.PreferenceFragment; +import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import org.cyanogenmod.wallpapers.photophase.R; +import org.cyanogenmod.wallpapers.photophase.adapters.DispositionAdapter; import org.cyanogenmod.wallpapers.photophase.model.Disposition; +import org.cyanogenmod.wallpapers.photophase.model.Dispositions; +import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil; import org.cyanogenmod.wallpapers.photophase.widgets.DispositionView; import org.cyanogenmod.wallpapers.photophase.widgets.DispositionView.OnFrameSelectedListener; import org.cyanogenmod.wallpapers.photophase.widgets.ResizeFrame; +import java.util.ArrayList; import java.util.List; /** * An abstract fragment class that allow to choose the layout disposition of the wallpaper. */ -public abstract class DispositionFragment - extends PreferenceFragment implements OnFrameSelectedListener { - - private Runnable mRedraw = new Runnable() { - @Override - public void run() { - if (getActivity() == null) return; - try { - mDispositionView.setDispositions(getUserDispositions(), getCols(), getRows()); - } catch (Exception ex) { - // Ignored - } - } - }; +public abstract class DispositionFragment extends PreferenceFragment + implements OnFrameSelectedListener, OnPageChangeListener { - DispositionView mDispositionView; + private ViewPager mPager; + private DispositionAdapter mAdapter; + private ResizeFrame mResizeFrame; + private TextView mAdvise; + private DispositionView mCurrentDispositionView; + private int mCurrentPage; + private int mNumberOfTemplates; + + private MenuItem mRestoreMenu; private MenuItem mDeleteMenu; /** @@ -79,6 +82,13 @@ public abstract class DispositionFragment public abstract List<Disposition> getDefaultDispositions(); /** + * Method that returns the system-defined dispositions templates + * + * @return String[] The system-defined dispositions templates + */ + public abstract String[] getDispositionsTemplates(); + + /** * Method that request to save the dispositions * * @param dispositions The dispositions to save @@ -93,9 +103,9 @@ public abstract class DispositionFragment public abstract int getRows(); /** - * Method that returns the number of cols to use + * Method that returns the number of columns to use * - * @return int The number of cols + * @return int The number of columns */ public abstract int getCols(); @@ -117,13 +127,24 @@ public abstract class DispositionFragment * {@inheritDoc} */ @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { // Inflate the layout for this fragment - ViewGroup v = (ViewGroup)inflater.inflate(R.layout.choose_disposition_fragment, container, false); - mDispositionView = (DispositionView)v.findViewById(R.id.disposition_view); - mDispositionView.setResizeFrame((ResizeFrame)v.findViewById(R.id.resize_frame)); - mDispositionView.setOnFrameSelectedListener(this); - mDispositionView.post(mRedraw); + ViewGroup v = (ViewGroup)inflater.inflate(R.layout.choose_disposition_fragment, + container, false); + + mCurrentPage = 0; + mNumberOfTemplates = getDispositionsTemplates().length; + + mAdvise = (TextView)v.findViewById(R.id.advise); + mResizeFrame = (ResizeFrame)v.findViewById(R.id.resize_frame); + + mAdapter = new DispositionAdapter(getActivity(), getAllDispositions(), mResizeFrame, this); + mPager = (ViewPager)v.findViewById(R.id.dispositions_pager); + mPager.setAdapter(mAdapter); + mPager.setOnPageChangeListener(this); + mPager.setCurrentItem(0); + return v; } @@ -132,21 +153,28 @@ public abstract class DispositionFragment */ @Override public void onDestroyView() { - super.onDestroyView(); - if (mDispositionView != null) { - mDispositionView.removeCallbacks(mRedraw); - if (mDispositionView.isChanged()) { - saveDispositions(mDispositionView.getDispositions()); - } + boolean saved = false; + + if (mCurrentDispositionView == null) { + mCurrentDispositionView = mAdapter.getView(0); } - // Notify that the settings was changed - Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); - if (mDispositionView.isChanged()) { - intent.putExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, Boolean.TRUE); - intent.putExtra(PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, Boolean.TRUE); + if (mCurrentDispositionView != null) { + if (mCurrentPage != 0 || mCurrentDispositionView.isChanged()) { + saveDispositions(mCurrentDispositionView.getDispositions()); + saved = true; + } + + // Notify that the settings was changed + Intent intent = new Intent(PreferencesProvider.ACTION_SETTINGS_CHANGED); + if (saved) { + intent.putExtra(PreferencesProvider.EXTRA_FLAG_REDRAW, Boolean.TRUE); + intent.putExtra(PreferencesProvider.EXTRA_FLAG_RECREATE_WORLD, Boolean.TRUE); + } + getActivity().sendBroadcast(intent); } - getActivity().sendBroadcast(intent); + + super.onDestroyView(); } /** @@ -155,10 +183,9 @@ public abstract class DispositionFragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.dispositions, menu); + mRestoreMenu = menu.findItem(R.id.mnu_restore); mDeleteMenu = menu.findItem(R.id.mnu_delete); - if (mDeleteMenu != null) { - mDeleteMenu.setVisible(false); - } + mDeleteMenu.setVisible(false); } /** @@ -185,14 +212,51 @@ public abstract class DispositionFragment * Method that restores the disposition view to the default state */ private void restoreData() { - mDispositionView.setDispositions(getUserDispositions(), getCols(), getRows()); + if (mCurrentDispositionView == null) { + mCurrentDispositionView = mAdapter.getView(0); + } + mCurrentDispositionView.setDispositions(getUserDispositions(), getCols(), getRows(), true); } /** * Method that restores the disposition view to the default state */ private void deleteFrame() { - mDispositionView.deleteCurrentFrame(); + if (mCurrentDispositionView == null) { + mCurrentDispositionView = mAdapter.getView(0); + } + mCurrentDispositionView.deleteCurrentFrame(); + } + + /** + * Method that returns the system-defined dispositions templates + * + * @return List<Dispositions> All the system-defined dispositions templates + */ + public List<Dispositions> getAllDispositions() { + final int rows = getRows(); + final int cols = getCols(); + + List<Dispositions> allDispositions = new ArrayList<Dispositions>(); + allDispositions.add(new Dispositions(getUserDispositions(), rows, cols)); + allDispositions.addAll(getSystemDefinedDispositions(rows, cols)); + return allDispositions; + } + /** + * Method that returns the system-defined dispositions templates + * + * @param rows The number of rows + * @param cols The number of columns + * @return List<Dispositions> All the system-defined dispositions templates + */ + private List<Dispositions> getSystemDefinedDispositions(int rows, int cols) { + String[] templates = getDispositionsTemplates(); + List<Dispositions> systemDispositions = new ArrayList<Dispositions>(templates.length); + for (String template : templates) { + systemDispositions.add(new Dispositions( + DispositionUtil.toDispositions(template), rows, cols)); + } + return systemDispositions; } /** @@ -214,4 +278,50 @@ public abstract class DispositionFragment mDeleteMenu.setVisible(false); } } + + /** + * {@inheritDoc} + */ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + // Ignored + } + + /** + * {@inheritDoc} + */ + @Override + public void onPageSelected(int position) { + // Save state + mCurrentPage = position; + mCurrentDispositionView = mAdapter.getView(position); + + // Enable/Disable menus + if (mRestoreMenu != null) { + mRestoreMenu.setVisible(position == 0); + } + if (mDeleteMenu != null) { + mDeleteMenu.setVisible(false); + } + + // Set the title + if (position == 0) { + mAdvise.setText(getString(R.string.pref_disposition_description)); + } else { + mAdvise.setText(getString(R.string.pref_disposition_template, + String.valueOf(position), String.valueOf(mNumberOfTemplates))); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onPageScrollStateChanged(int state) { + if (mDeleteMenu != null) { + mDeleteMenu.setVisible(false); + } + mResizeFrame.setVisibility(View.GONE); + } + } diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java index 77267e6..b83c55f 100644 --- a/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/LandscapeDispositionFragment.java @@ -19,6 +19,7 @@ package org.cyanogenmod.wallpapers.photophase.preferences; import android.content.pm.ActivityInfo; import android.os.Bundle; +import org.cyanogenmod.wallpapers.photophase.R; import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences; import org.cyanogenmod.wallpapers.photophase.model.Disposition; import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil; @@ -68,6 +69,14 @@ public class LandscapeDispositionFragment extends DispositionFragment { * {@inheritDoc} */ @Override + public String[] getDispositionsTemplates() { + return getActivity().getResources().getStringArray(R.array.landscape_disposition_templates); + } + + /** + * {@inheritDoc} + */ + @Override public void saveDispositions(List<Disposition> dispositions) { Preferences.Layout.setLandscapeDisposition(getActivity(), dispositions); } diff --git a/src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java b/src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java index 8763e1e..a3a53d9 100644 --- a/src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java +++ b/src/org/cyanogenmod/wallpapers/photophase/preferences/PortraitDispositionFragment.java @@ -19,6 +19,7 @@ package org.cyanogenmod.wallpapers.photophase.preferences; import android.content.pm.ActivityInfo; import android.os.Bundle; +import org.cyanogenmod.wallpapers.photophase.R; import org.cyanogenmod.wallpapers.photophase.model.Disposition; import org.cyanogenmod.wallpapers.photophase.preferences.PreferencesProvider.Preferences; import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil; @@ -68,6 +69,14 @@ public class PortraitDispositionFragment extends DispositionFragment { * {@inheritDoc} */ @Override + public String[] getDispositionsTemplates() { + return getActivity().getResources().getStringArray(R.array.portrait_disposition_templates); + } + + /** + * {@inheritDoc} + */ + @Override public void saveDispositions(List<Disposition> dispositions) { Preferences.Layout.setPortraitDisposition(getActivity(), dispositions); } diff --git a/src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java b/src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java index 0bc462f..87e3999 100644 --- a/src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java +++ b/src/org/cyanogenmod/wallpapers/photophase/widgets/DispositionView.java @@ -40,6 +40,7 @@ import android.widget.ImageView.ScaleType; import org.cyanogenmod.wallpapers.photophase.R; import org.cyanogenmod.wallpapers.photophase.animations.Evaluators; import org.cyanogenmod.wallpapers.photophase.model.Disposition; +import org.cyanogenmod.wallpapers.photophase.model.Dispositions; import org.cyanogenmod.wallpapers.photophase.utils.DispositionUtil; import org.cyanogenmod.wallpapers.photophase.utils.MERAlgorithm; import org.cyanogenmod.wallpapers.photophase.widgets.ResizeFrame.OnResizeListener; @@ -140,18 +141,32 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen * Method that sets the disposition to draw on this view * * @param dispositions The dispositions to draw - * @param cols The number of cols + * @param animate If should animate the view + */ + public void setDispositions(Dispositions dispositions, boolean animate) { + setDispositions(dispositions.getDispositions(), dispositions.getCols(), + dispositions.getRows(), animate); + } + + /** + * Method that sets the disposition to draw on this view + * + * @param dispositions The dispositions to draw + * @param cols The number of columns * @param rows The number of rows + * @param animate If should animate the view */ - public void setDispositions( - List<Disposition> dispositions, int cols, int rows) { + public void setDispositions(List<Disposition> dispositions, int cols, int rows, + boolean animate) { mDispositions = dispositions; mCols = cols; mRows = rows; // Remove all the current views and add the new ones - recreateDispositions(true); - mResizeFrame.setVisibility(View.GONE); + recreateDispositions(animate); + if (mResizeFrame != null) { + mResizeFrame.setVisibility(View.GONE); + } mChanged = false; } @@ -207,6 +222,7 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen @SuppressWarnings("boxing") public void deleteCurrentFrame() { if (mTarget == null) return; + if (mResizeFrame == null) return; final Disposition targetDisposition = resizerToDisposition(); @@ -348,7 +364,13 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen final ImageView v = new ImageView(getContext()); v.setImageResource(R.drawable.ic_camera); v.setScaleType(ScaleType.CENTER); - v.setBackgroundColor(getResources().getColor(R.color.disposition_frame_bg_color)); + + // Is locked? Then change the background color + v.setBackgroundColor(getResources().getColor( + mResizeFrame == null + ? R.color.disposition_locked_frame_bg_color + : R.color.disposition_frame_bg_color)); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(r.width() - padding, r.height() - padding); v.setX(r.left + padding); @@ -401,13 +423,15 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen */ @Override public boolean onLongClick(View v) { - if (!selectTarget(v)) return false; - mVibrator.vibrate(300); + if (mResizeFrame != null && selectTarget(v)) { + mVibrator.vibrate(300); + } return true; } @Override public void onStartResize(int mode) { + if (mResizeFrame == null) return; mOldResizeFrameLocation = new Rect( mResizeFrame.getLeft(), mResizeFrame.getTop(), @@ -418,6 +442,7 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen @Override public void onResize(int mode, int delta) { if (mTarget == null) return; + if (mResizeFrame == null) return; int w = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight()); int h = getMeasuredHeight() - (getPaddingTop() + getPaddingBottom()); @@ -472,6 +497,7 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen @Override public void onEndResize(final int mode) { if (mTarget == null) return; + if (mResizeFrame == null) return; // Compute the removed dispositions computeRemovedDispositions(mode); @@ -555,8 +581,10 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen boolean selectTarget(View v) { //Do not do long click if we do not have a target if (mTarget != null && v.equals(mTarget)) return false; + if (mResizeFrame == null) return false; // Show the resize frame view just in place of the current clicked view + mResizeFrame.hide(); FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams)mResizeFrame.getLayoutParams(); @@ -666,7 +694,8 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen for (Disposition d : mDispositions) { if (d.compareTo(disposition) != 0) { if ((d.x + d.w) == disposition.x && - (d.y >= disposition.y) && ((d.y + d.h) <= (disposition.y + disposition.h))) { + (d.y >= disposition.y) && + ((d.y + d.h) <= (disposition.y + disposition.h))) { dispositions.add(d); } } @@ -686,7 +715,8 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen for (Disposition d : mDispositions) { if (d.compareTo(disposition) != 0) { if ((d.y + d.h) == disposition.y && - (d.x >= disposition.x) && ((d.x + d.w) <= (disposition.x + disposition.w))) { + (d.x >= disposition.x) && + ((d.x + d.w) <= (disposition.x + disposition.w))) { dispositions.add(d); } } @@ -706,7 +736,8 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen for (Disposition d : mDispositions) { if (d.compareTo(disposition) != 0) { if ((d.x) == (disposition.x + disposition.w) && - (d.y >= disposition.y) && ((d.y + d.h) <= (disposition.y + disposition.h))) { + (d.y >= disposition.y) && + ((d.y + d.h) <= (disposition.y + disposition.h))) { dispositions.add(d); } } @@ -726,7 +757,8 @@ public class DispositionView extends RelativeLayout implements OnLongClickListen for (Disposition d : mDispositions) { if (d.compareTo(disposition) != 0) { if ((d.y) == (disposition.y + disposition.h) && - (d.x >= disposition.x) && ((d.x + d.w) <= (disposition.x + disposition.w))) { + (d.x >= disposition.x) && + ((d.x + d.w) <= (disposition.x + disposition.w))) { dispositions.add(d); } } |