diff options
Diffstat (limited to 'core')
22 files changed, 803 insertions, 294 deletions
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java index 4bf5518..d49968f 100644 --- a/core/java/android/app/ListActivity.java +++ b/core/java/android/app/ListActivity.java @@ -309,7 +309,7 @@ public class ListActivity extends Activity { if (mList != null) { return; } - setContentView(com.android.internal.R.layout.list_content); + setContentView(com.android.internal.R.layout.list_content_simple); } diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java index 96485f7..73ef869 100644 --- a/core/java/android/app/ListFragment.java +++ b/core/java/android/app/ListFragment.java @@ -172,11 +172,17 @@ public class ListFragment extends Fragment { * is {@link android.R.id#list android.R.id.list} and can optionally * have a sibling view id {@link android.R.id#empty android.R.id.empty} * that is to be shown when the list is empty. + * + * <p>If you are overriding this method with your own custom content, + * consider including the standard layout {@link android.R.layout#list_content} + * in your layout file, so that you continue to retain all of the standard + * behavior of ListFragment. In particular, this is currently the only + * way to have the built-in indeterminant progress state be shown. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(com.android.internal.R.layout.list_content_rich, + return inflater.inflate(com.android.internal.R.layout.list_content, container, false); } @@ -217,9 +223,15 @@ public class ListFragment extends Fragment { * Provide the cursor for the list view. */ public void setListAdapter(ListAdapter adapter) { + boolean hadAdapter = mAdapter != null; mAdapter = adapter; if (mList != null) { mList.setAdapter(adapter); + if (!mListShown && !hadAdapter) { + // The list was hidden, and previously didn't have an + // adapter. It is now time to show it. + setListShown(true, getView().getWindowToken() != null); + } } } @@ -276,12 +288,38 @@ public class ListFragment extends Fragment { * displayed if you are waiting for the initial data to show in it. During * this time an indeterminant progress indicator will be shown instead. * + * <p>Applications do not normally need to use this themselves. The default + * behavior of ListFragment is to start with the list not being shown, only + * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}. + * If the list at that point had not been shown, when it does get shown + * it will be do without the user ever seeing the hidden state. + * + * @param shown If true, the list view is shown; if false, the progress + * indicator. The initial value is true. + */ + public void setListShown(boolean shown) { + setListShown(shown, true); + } + + /** + * Like {@link #setListShown(boolean)}, but no animation is used when + * transitioning from the previous state. + */ + public void setListShownNoAnimation(boolean shown) { + setListShown(shown, false); + } + + /** + * Control whether the list is being displayed. You can make it not + * displayed if you are waiting for the initial data to show in it. During + * this time an indeterminant progress indicator will be shown instead. + * * @param shown If true, the list view is shown; if false, the progress * indicator. The initial value is true. * @param animate If true, an animation will be used to transition to the * new state. */ - public void setListShown(boolean shown, boolean animate) { + private void setListShown(boolean shown, boolean animate) { ensureList(); if (mProgressContainer == null) { throw new IllegalStateException("Can't be used with a custom content view"); @@ -356,6 +394,12 @@ public class ListFragment extends Fragment { mList.setOnItemClickListener(mOnClickListener); if (mAdapter != null) { setListAdapter(mAdapter); + } else { + // We are starting without an adapter, so assume we won't + // have our data right away and start with the progress indicator. + if (mProgressContainer != null) { + setListShown(false, false); + } } mHandler.post(mRequestFocus); } diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index 31e3c40..7600899 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -180,7 +180,7 @@ public class LoaderManager { * will be called as the loader state changes. If at the point of call * the caller is in its started state, and the requested loader * already exists and has generated its data, then - * callback.{@link LoaderCallbacks#onLoadFinished(Loader, Object)} will + * callback. {@link LoaderCallbacks#onLoadFinished} will * be called immediately (inside of this function), so you must be prepared * for this to happen. */ diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index d4ce6a1..3f12bf9 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -22,7 +22,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.DisplayMetrics; -import android.util.Log; import android.util.TypedValue; import android.widget.RemoteViews; @@ -149,7 +148,7 @@ public class AppWidgetManager { * instances as possible.</td> * </tr> * </table> - * + * * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) */ public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; @@ -163,7 +162,7 @@ public class AppWidgetManager { /** * Sent when an instance of an AppWidget is removed from the last host. - * + * * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) */ public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED"; @@ -172,7 +171,7 @@ public class AppWidgetManager { * Sent when an instance of an AppWidget is added to a host for the first time. * This broadcast is sent at boot time if there is a AppWidgetHost installed with * an instance for this provider. - * + * * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) */ public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"; @@ -183,20 +182,21 @@ public class AppWidgetManager { * @see AppWidgetProviderInfo */ public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider"; - + /** * Field for the manifest meta-data tag used to indicate any previous name for the * app widget receiver. * * @see AppWidgetProviderInfo - * + * * @hide Pending API approval */ public static final String META_DATA_APPWIDGET_OLD_NAME = "android.appwidget.oldName"; - static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = new WeakHashMap(); + static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = + new WeakHashMap<Context, WeakReference<AppWidgetManager>>(); static IAppWidgetService sService; - + Context mContext; private DisplayMetrics mDisplayMetrics; @@ -219,7 +219,7 @@ public class AppWidgetManager { } if (result == null) { result = new AppWidgetManager(context); - sManagerCache.put(context, new WeakReference(result)); + sManagerCache.put(context, new WeakReference<AppWidgetManager>(result)); } return result; } @@ -292,7 +292,15 @@ public class AppWidgetManager { */ public List<AppWidgetProviderInfo> getInstalledProviders() { try { - return sService.getInstalledProviders(); + List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(); + for (AppWidgetProviderInfo info : providers) { + // Converting complex to dp. + info.minWidth = + TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics); + info.minHeight = + TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics); + } + return providers; } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -310,7 +318,7 @@ public class AppWidgetManager { AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId); if (info != null) { // Converting complex to dp. - info.minWidth = + info.minWidth = TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics); info.minHeight = TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics); @@ -344,7 +352,7 @@ public class AppWidgetManager { /** * Get the list of appWidgetIds that have been bound to the given AppWidget * provider. - * + * * @param provider The {@link android.content.BroadcastReceiver} that is the * AppWidget provider to find appWidgetIds for. */ diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 2c4a9f5..293d31c 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -237,6 +237,14 @@ public final class MediaStore { * <P>Type: TEXT</P> */ public static final String MIME_TYPE = "mime_type"; + + /** + * The MTP object handle of a newly transfered file. + * Used internally by the MediaScanner + * <P>Type: INTEGER</P> + * @hide + */ + public static final String MTP_OBJECT_HANDLE = "mtp_object_handle"; } @@ -274,6 +282,19 @@ public final class MediaStore { * <P>Type: INTEGER</P> */ public static final String PARENT = "parent"; + + /** + * Identifier for the media table containing the object. + * Used internally by MediaProvider + * <P>Type: INTEGER</P> + */ + public static final String MEDIA_TABLE = "media_table"; + + /** + * The ID of the object in its media table. + * <P>Type: INTEGER</P> + */ + public static final String MEDIA_ID = "media_id"; } } diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index 8eae111..c05a8fe 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -32,7 +32,7 @@ public class Time { private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000"; private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z"; private static final String Y_M_D = "%Y-%m-%d"; - + public static final String TIMEZONE_UTC = "UTC"; /** @@ -170,11 +170,11 @@ public class Time { public Time() { this(TimeZone.getDefault().getID()); } - + /** * A copy constructor. Construct a Time object by copying the given * Time object. No normalization occurs. - * + * * @param other */ public Time(Time other) { @@ -185,17 +185,17 @@ public class Time { * Ensures the values in each field are in range. For example if the * current value of this calendar is March 32, normalize() will convert it * to April 1. It also fills in weekDay, yearDay, isDst and gmtoff. - * + * * <p> * If "ignoreDst" is true, then this method sets the "isDst" field to -1 * (the "unknown" value) before normalizing. It then computes the * correct value for "isDst". - * + * * <p> * See {@link #toMillis(boolean)} for more information about when to * use <tt>true</tt> or <tt>false</tt> for "ignoreDst". - * - * @return the UTC milliseconds since the epoch + * + * @return the UTC milliseconds since the epoch */ native public long normalize(boolean ignoreDst); @@ -379,13 +379,13 @@ public class Time { * Parses a date-time string in either the RFC 2445 format or an abbreviated * format that does not include the "time" field. For example, all of the * following strings are valid: - * + * * <ul> * <li>"20081013T160000Z"</li> * <li>"20081013T160000"</li> * <li>"20081013"</li> * </ul> - * + * * Returns whether or not the time is in UTC (ends with Z). If the string * ends with "Z" then the timezone is set to UTC. If the date-time string * included only a date and no time field, then the <code>allDay</code> @@ -396,10 +396,10 @@ public class Time { * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero, * and the field <code>isDst</code> is set to -1 (unknown). To set those * fields, call {@link #normalize(boolean)} after parsing. - * + * * To parse a date-time string and convert it to UTC milliseconds, do * something like this: - * + * * <pre> * Time time = new Time(); * String date = "20081013T160000Z"; @@ -428,25 +428,25 @@ public class Time { * Parse a time in RFC 3339 format. This method also parses simple dates * (that is, strings that contain no time or time offset). For example, * all of the following strings are valid: - * + * * <ul> * <li>"2008-10-13T16:00:00.000Z"</li> * <li>"2008-10-13T16:00:00.000+07:00"</li> * <li>"2008-10-13T16:00:00.000-07:00"</li> * <li>"2008-10-13"</li> * </ul> - * + * * <p> * If the string contains a time and time offset, then the time offset will * be used to convert the time value to UTC. * </p> - * + * * <p> * If the given string contains just a date (with no time field), then * the {@link #allDay} field is set to true and the {@link #hour}, * {@link #minute}, and {@link #second} fields are set to zero. * </p> - * + * * <p> * Returns true if the resulting time value is in UTC time. * </p> @@ -462,7 +462,7 @@ public class Time { } return false; } - + native private boolean nativeParse3339(String s); /** @@ -484,13 +484,13 @@ public class Time { * <em>not</em> change any of the fields in this Time object. If you want * to normalize the fields in this Time object and also get the milliseconds * then use {@link #normalize(boolean)}. - * + * * <p> * If "ignoreDst" is false, then this method uses the current setting of the * "isDst" field and will adjust the returned time if the "isDst" field is * wrong for the given time. See the sample code below for an example of * this. - * + * * <p> * If "ignoreDst" is true, then this method ignores the current setting of * the "isDst" field in this Time object and will instead figure out the @@ -499,27 +499,27 @@ public class Time { * correct value of the "isDst" field is when the time is inherently * ambiguous because it falls in the hour that is repeated when switching * from Daylight-Saving Time to Standard Time. - * + * * <p> * Here is an example where <tt>toMillis(true)</tt> adjusts the time, * assuming that DST changes at 2am on Sunday, Nov 4, 2007. - * + * * <pre> * Time time = new Time(); - * time.set(2007, 10, 4); // set the date to Nov 4, 2007, 12am + * time.set(4, 10, 2007); // set the date to Nov 4, 2007, 12am * time.normalize(); // this sets isDst = 1 * time.monthDay += 1; // changes the date to Nov 5, 2007, 12am * millis = time.toMillis(false); // millis is Nov 4, 2007, 11pm * millis = time.toMillis(true); // millis is Nov 5, 2007, 12am * </pre> - * + * * <p> * To avoid this problem, use <tt>toMillis(true)</tt> * after adding or subtracting days or explicitly setting the "monthDay" * field. On the other hand, if you are adding * or subtracting hours or minutes, then you should use * <tt>toMillis(false)</tt>. - * + * * <p> * You should also use <tt>toMillis(false)</tt> if you want * to read back the same milliseconds that you set with {@link #set(long)} @@ -531,14 +531,14 @@ public class Time { * Sets the fields in this Time object given the UTC milliseconds. After * this method returns, all the fields are normalized. * This also sets the "isDst" field to the correct value. - * + * * @param millis the time in UTC milliseconds since the epoch. */ native public void set(long millis); /** * Format according to RFC 2445 DATETIME type. - * + * * <p> * The same as format("%Y%m%dT%H%M%S"). */ @@ -584,7 +584,7 @@ public class Time { * Sets the date from the given fields. Also sets allDay to true. * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1. * Call {@link #normalize(boolean)} if you need those. - * + * * @param monthDay the day of the month (in the range [1,31]) * @param month the zero-based month number (in the range [0,11]) * @param year the year @@ -606,7 +606,7 @@ public class Time { /** * Returns true if the time represented by this Time object occurs before * the given time. - * + * * @param that a given Time object to compare against * @return true if this time is less than the given time */ @@ -618,7 +618,7 @@ public class Time { /** * Returns true if the time represented by this Time object occurs after * the given time. - * + * * @param that a given Time object to compare against * @return true if this time is greater than the given time */ @@ -632,12 +632,12 @@ public class Time { * closest Thursday yearDay. */ private static final int[] sThursdayOffset = { -3, 3, 2, 1, 0, -1, -2 }; - + /** * Computes the week number according to ISO 8601. The current Time * object must already be normalized because this method uses the * yearDay and weekDay fields. - * + * * <p> * In IS0 8601, weeks start on Monday. * The first week of the year (week 1) is defined by ISO 8601 as the @@ -645,12 +645,12 @@ public class Time { * Or equivalently, the week containing January 4. Or equivalently, * the week with the year's first Thursday in it. * </p> - * + * * <p> * The week number can be calculated by counting Thursdays. Week N * contains the Nth Thursday of the year. * </p> - * + * * @return the ISO week number. */ public int getWeekNumber() { @@ -661,7 +661,7 @@ public class Time { if (closestThursday >= 0 && closestThursday <= 364) { return closestThursday / 7 + 1; } - + // The week crosses a year boundary. Time temp = new Time(this); temp.monthDay += sThursdayOffset[weekDay]; @@ -670,7 +670,7 @@ public class Time { } /** - * Return a string in the RFC 3339 format. + * Return a string in the RFC 3339 format. * <p> * If allDay is true, expresses the time as Y-M-D</p> * <p> @@ -691,13 +691,13 @@ public class Time { int offset = (int)Math.abs(gmtoff); int minutes = (offset % 3600) / 60; int hours = offset / 3600; - + return String.format("%s%s%02d:%02d", base, sign, hours, minutes); } } - + /** - * Returns true if the day of the given time is the epoch on the Julian Calendar + * Returns true if the day of the given time is the epoch on the Julian Calendar * (January 1, 1970 on the Gregorian calendar). * * @param time the time to test @@ -707,7 +707,7 @@ public class Time { long millis = time.toMillis(true); return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY; } - + /** * Computes the Julian day number, given the UTC milliseconds * and the offset (in seconds) from UTC. The Julian day for a given @@ -716,10 +716,10 @@ public class Time { * what timezone is being used. The Julian day is useful for testing * if two events occur on the same day and for determining the relative * time of an event from the present ("yesterday", "3 days ago", etc.). - * + * * <p> * Use {@link #toMillis(boolean)} to get the milliseconds. - * + * * @param millis the time in UTC milliseconds * @param gmtoff the offset from UTC in seconds * @return the Julian day @@ -729,7 +729,7 @@ public class Time { long julianDay = (millis + offsetMillis) / DateUtils.DAY_IN_MILLIS; return (int) julianDay + EPOCH_JULIAN_DAY; } - + /** * <p>Sets the time from the given Julian day number, which must be based on * the same timezone that is set in this Time object. The "gmtoff" field @@ -738,7 +738,7 @@ public class Time { * After this method returns all the fields will be normalized and the time * will be set to 12am at the beginning of the given Julian day. * </p> - * + * * <p> * The only exception to this is if 12am does not exist for that day because * of daylight saving time. For example, Cairo, Eqypt moves time ahead one @@ -746,7 +746,7 @@ public class Time { * also change daylight saving time at 12am. In those cases, the time * will be set to 1am. * </p> - * + * * @param julianDay the Julian day in the timezone for this Time object * @return the UTC milliseconds for the beginning of the Julian day */ @@ -756,13 +756,13 @@ public class Time { // the day. long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS; set(millis); - + // Figure out how close we are to the requested Julian day. // We can't be off by more than a day. int approximateDay = getJulianDay(millis, gmtoff); int diff = julianDay - approximateDay; monthDay += diff; - + // Set the time to 12am and re-normalize. hour = 0; minute = 0; diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 9df63a9..55ec655 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -138,35 +138,6 @@ public class ArrowKeyMovementMethod implements MovementMethod { } } - private int getOffset(int x, int y, TextView widget){ - // Converts the absolute X,Y coordinates to the character offset for the - // character whose position is closest to the specified - // horizontal position. - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - // Clamp the position to inside of the view. - if (x < 0) { - x = 0; - } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) { - x = widget.getWidth()-widget.getTotalPaddingRight() - 1; - } - if (y < 0) { - y = 0; - } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) { - y = widget.getHeight()-widget.getTotalPaddingBottom() - 1; - } - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - - int offset = layout.getOffsetForHorizontal(line, x); - return offset; - } - public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { if (executeDown(widget, buffer, keyCode)) { MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); @@ -263,7 +234,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { MetaKeyKeyListener.META_SELECTING) != 0); int x = (int) event.getX(); int y = (int) event.getY(); - int offset = getOffset(x, y, widget); + int offset = widget.getOffset(x, y); if (cap) { buffer.setSpan(LAST_TAP_DOWN, offset, offset, @@ -320,7 +291,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { // Get the current touch position int x = (int) event.getX(); int y = (int) event.getY(); - int offset = getOffset(x, y, widget); + int offset = widget.getOffset(x, y); final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(), OnePointFiveTapState.class); @@ -366,7 +337,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { int x = (int) event.getX(); int y = (int) event.getY(); - int off = getOffset(x, y, widget); + int off = widget.getOffset(x, y); // XXX should do the same adjust for x as we do for the line. @@ -442,11 +413,10 @@ public class ArrowKeyMovementMethod implements MovementMethod { widget.cancelLongPress(); // Offset the current touch position (from controller to cursor) - final int x = (int) event.getX() + mCursorController.getOffsetX(); - final int y = (int) event.getY() + mCursorController.getOffsetY(); - int offset = getOffset(x, y, widget); - Selection.setSelection(buffer, offset); - mCursorController.updatePosition(); + final float x = event.getX() + mCursorController.getOffsetX(); + final float y = event.getY() + mCursorController.getOffsetY(); + int offset = widget.getOffset((int) x, (int) y); + mCursorController.updatePosition(offset); return true; case MotionEvent.ACTION_UP: diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 49ef8dc..0ad3c0b 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -45,6 +45,10 @@ class GLES20Canvas extends Canvas { private final float[] mPoint = new float[2]; private final float[] mLine = new float[4]; + + private final Rect mClipBounds = new Rect(); + + private DrawFilter mFilter; /////////////////////////////////////////////////////////////////////////// // Constructors @@ -164,6 +168,7 @@ class GLES20Canvas extends Canvas { @Override public boolean clipRect(Rect rect, Region.Op op) { + // TODO: Implement throw new UnsupportedOperationException(); } @@ -174,6 +179,7 @@ class GLES20Canvas extends Canvas { @Override public boolean clipRect(RectF rect, Region.Op op) { + // TODO: Implement throw new UnsupportedOperationException(); } @@ -336,12 +342,14 @@ class GLES20Canvas extends Canvas { @Override public void setDrawFilter(DrawFilter filter) { - throw new UnsupportedOperationException(); + // Don't crash, but ignore the draw filter + // TODO: Implement PaintDrawFilter + mFilter = filter; } @Override public DrawFilter getDrawFilter() { - throw new UnsupportedOperationException(); + return mFilter; } /////////////////////////////////////////////////////////////////////////// @@ -408,7 +416,11 @@ class GLES20Canvas extends Canvas { @Override public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint) { - // TODO: Implement + final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config); + final int nativePaint = paint == null ? 0 : paint.mNativePaint; + nDrawBitmap(mRenderer, b.mNativeBitmap, x, y, nativePaint); + b.recycle(); } @Override @@ -420,7 +432,7 @@ class GLES20Canvas extends Canvas { @Override public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) { - throw new UnsupportedOperationException(); + // TODO: Implement } @Override @@ -466,7 +478,9 @@ class GLES20Canvas extends Canvas { @Override public void drawPaint(Paint paint) { - // TODO: Implement + final Rect r = mClipBounds; + nGetClipBounds(mRenderer, r); + drawRect(r.left, r.top, r.right, r.bottom, paint); } @Override @@ -591,6 +605,6 @@ class GLES20Canvas extends Canvas { public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices, int indexOffset, int indexCount, Paint paint) { - throw new UnsupportedOperationException(); + // TODO: Implement } } diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 7b1aab2..b021ded 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -301,6 +301,18 @@ class BrowserFrame extends Handler { } /** + * Saves the contents of the frame as a web archive. + * + * @param basename The filename where the archive should be placed. + * @param autoname If false, takes filename to be a file. If true, filename + * is assumed to be a directory in which a filename will be + * chosen according to the url of the current page. + */ + /* package */ String saveWebArchive(String basename, boolean autoname) { + return nativeSaveWebArchive(basename, autoname); + } + + /** * Go back or forward the number of steps given. * @param steps A negative or positive number indicating the direction * and number of steps to move. @@ -1040,5 +1052,7 @@ class BrowserFrame extends Handler { */ private native HashMap getFormTextData(); + private native String nativeSaveWebArchive(String basename, boolean autoname); + private native void nativeOrientationChanged(int orientation); } diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java index c1ac180..6e9c70a 100644 --- a/core/java/android/webkit/MimeTypeMap.java +++ b/core/java/android/webkit/MimeTypeMap.java @@ -363,6 +363,7 @@ public class MimeTypeMap { sMimeTypeMap.loadEntry("application/x-wais-source", "src"); sMimeTypeMap.loadEntry("application/x-wingz", "wz"); sMimeTypeMap.loadEntry("application/x-webarchive", "webarchive"); + sMimeTypeMap.loadEntry("application/x-webarchive-xml", "webarchivexml"); sMimeTypeMap.loadEntry("application/x-x509-ca-cert", "crt"); sMimeTypeMap.loadEntry("application/x-x509-user-cert", "crt"); sMimeTypeMap.loadEntry("application/x-xcf", "xcf"); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 140dd55..0c8fc79 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -590,6 +590,7 @@ public class WebView extends AbsoluteLayout static final int SET_SCROLLBAR_MODES = 129; static final int SELECTION_STRING_CHANGED = 130; static final int SET_TOUCH_HIGHLIGHT_RECTS = 131; + static final int SAVE_WEBARCHIVE_FINISHED = 132; private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; private static final int LAST_PACKAGE_MSG_ID = SET_TOUCH_HIGHLIGHT_RECTS; @@ -638,7 +639,8 @@ public class WebView extends AbsoluteLayout "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128; "SET_SCROLLBAR_MODES", // = 129; "SELECTION_STRING_CHANGED", // = 130; - "SET_TOUCH_HIGHLIGHT_RECTS" // = 131; + "SET_TOUCH_HIGHLIGHT_RECTS", // = 131; + "SAVE_WEBARCHIVE_FINISHED" // = 132; }; // If the site doesn't use the viewport meta tag to specify the viewport, @@ -1520,6 +1522,45 @@ public class WebView extends AbsoluteLayout } /** + * Saves the current view as a web archive. + * + * @param filename The filename where the archive should be placed. + */ + public void saveWebArchive(String filename) { + saveWebArchive(filename, false, null); + } + + /* package */ static class SaveWebArchiveMessage { + SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) { + mBasename = basename; + mAutoname = autoname; + mCallback = callback; + } + + /* package */ final String mBasename; + /* package */ final boolean mAutoname; + /* package */ final ValueCallback<String> mCallback; + /* package */ String mResultFile; + } + + /** + * Saves the current view as a web archive. + * + * @param basename The filename where the archive should be placed. + * @param autoname If false, takes basename to be a file. If true, basename + * is assumed to be a directory in which a filename will be + * chosen according to the url of the current page. + * @param callback Called after the web archive has been saved. The + * parameter for onReceiveValue will either be the filename + * under which the file was saved, or null if saving the + * file failed. + */ + public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) { + mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE, + new SaveWebArchiveMessage(basename, autoname, callback)); + } + + /** * Stop the current load. */ public void stopLoading() { @@ -6435,6 +6476,13 @@ public class WebView extends AbsoluteLayout } break; + case SAVE_WEBARCHIVE_FINISHED: + SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj; + if (saveMessage.mCallback != null) { + saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile); + } + break; + default: super.handleMessage(msg); break; diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index db86a0b..21af570 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -774,6 +774,7 @@ final class WebViewCore { "ON_RESUME", // = 144 "FREE_MEMORY", // = 145 "VALID_NODE_BOUNDS", // = 146 + "SAVE_WEBARCHIVE", // = 147 }; class EventHub { @@ -840,6 +841,9 @@ final class WebViewCore { static final int FREE_MEMORY = 145; static final int VALID_NODE_BOUNDS = 146; + // Load and save web archives + static final int SAVE_WEBARCHIVE = 147; + // Network-based messaging static final int CLEAR_SSL_PREF_TABLE = 150; @@ -1300,6 +1304,15 @@ final class WebViewCore { nativeSetJsFlags((String)msg.obj); break; + case SAVE_WEBARCHIVE: + WebView.SaveWebArchiveMessage saveMessage = + (WebView.SaveWebArchiveMessage)msg.obj; + saveMessage.mResultFile = + saveWebArchive(saveMessage.mBasename, saveMessage.mAutoname); + mWebView.mPrivateHandler.obtainMessage( + WebView.SAVE_WEBARCHIVE_FINISHED, saveMessage).sendToTarget(); + break; + case GEOLOCATION_PERMISSIONS_PROVIDE: GeolocationPermissionsData data = (GeolocationPermissionsData) msg.obj; @@ -1601,6 +1614,13 @@ final class WebViewCore { mBrowserFrame.loadUrl(url, extraHeaders); } + private String saveWebArchive(String filename, boolean autoname) { + if (DebugFlags.WEB_VIEW_CORE) { + Log.v(LOGTAG, " CORE saveWebArchive " + filename + " " + autoname); + } + return mBrowserFrame.saveWebArchive(filename, autoname); + } + private void key(KeyEvent evt, boolean isDown) { if (DebugFlags.WEB_VIEW_CORE) { Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f591483..a5cb151 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1133,6 +1133,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); fixFocusableAndClickableSettings(); + prepareCursorController(); } private void fixFocusableAndClickableSettings() { @@ -2375,8 +2376,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int end = 0; if (mText != null) { - start = Selection.getSelectionStart(mText); - end = Selection.getSelectionEnd(mText); + start = getSelectionStart(); + end = getSelectionEnd(); if (start >= 0 || end >= 0) { // Or save state if there is a selection save = true; @@ -2700,6 +2701,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (needEditableForNotification) { sendAfterTextChanged((Editable) text); } + + // Depends on canSelectText, which depends on text + prepareCursorController(); } /** @@ -3617,7 +3621,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void invalidateCursor() { - int where = Selection.getSelectionEnd(mText); + int where = getSelectionEnd(); invalidateCursor(where, where, where); } @@ -3693,7 +3697,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean changed = false; if (mMovement != null) { - int curs = Selection.getSelectionEnd(mText); + /* This code also provides auto-scrolling when a cursor is moved using a + * CursorController (insertion point or selection limits). + * For selection, ensure start or end is visible depending on controller's state. + */ + int curs = getSelectionEnd(); + if (mSelectionModifierCursorController != null) { + SelectionModifierCursorController selectionController = + (SelectionModifierCursorController) mSelectionModifierCursorController; + if (selectionController.isSelectionStartDragged()) { + curs = getSelectionStart(); + } + } /* * TODO: This should really only keep the end in view if @@ -3986,8 +4001,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // XXX This is not strictly true -- a program could set the // selection manually if it really wanted to. if (mMovement != null && (isFocused() || isPressed())) { - selStart = Selection.getSelectionStart(mText); - selEnd = Selection.getSelectionEnd(mText); + selStart = getSelectionStart(); + selEnd = getSelectionEnd(); if (mCursorVisible && selStart >= 0 && isEnabled()) { if (mHighlightPath == null) @@ -4097,6 +4112,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mInsertionPointCursorController != null) { mInsertionPointCursorController.draw(canvas); } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.draw(canvas); + } } @Override @@ -4511,8 +4529,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.hintText = mHint; if (mText instanceof Editable) { InputConnection ic = new EditableInputConnection(this); - outAttrs.initialSelStart = Selection.getSelectionStart(mText); - outAttrs.initialSelEnd = Selection.getSelectionEnd(mText); + outAttrs.initialSelStart = getSelectionStart(); + outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); return ic; } @@ -4597,8 +4615,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outText.flags |= ExtractedText.FLAG_SINGLE_LINE; } outText.startOffset = 0; - outText.selectionStart = Selection.getSelectionStart(content); - outText.selectionEnd = Selection.getSelectionEnd(content); + outText.selectionStart = getSelectionStart(); + outText.selectionEnd = getSelectionEnd(); return true; } return false; @@ -4777,7 +4795,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener void updateAfterEdit() { invalidate(); - int curs = Selection.getSelectionStart(mText); + int curs = getSelectionStart(); if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { @@ -4921,7 +4939,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener w, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad); } - // Log.e("aaa", "Boring: " + mTransformed); mSavedLayout = (BoringLayout) mLayout; } else if (shouldEllipsize && boring.width <= w) { @@ -5677,8 +5694,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!(mText instanceof Spannable)) { return false; } - int start = Selection.getSelectionStart(mText); - int end = Selection.getSelectionEnd(mText); + int start = getSelectionStart(); + int end = getSelectionEnd(); if (start != end) { return false; } @@ -6522,6 +6539,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // Don't leave us in the middle of a batch edit. onEndBatchEdit(); + + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.hide(); + } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.hide(); + } } startStopMarquee(focused); @@ -6651,12 +6675,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean handled = false; - int oldSelStart = Selection.getSelectionStart(mText); - int oldSelEnd = Selection.getSelectionEnd(mText); + int oldSelStart = getSelectionStart(); + int oldSelEnd = getSelectionEnd(); if (mInsertionPointCursorController != null) { mInsertionPointCursorController.onTouchEvent(event); } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.onTouchEvent(event); + } if (mMovement != null) { handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); @@ -6667,8 +6694,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - final int newSelStart = Selection.getSelectionStart(mText); - final int newSelEnd = Selection.getSelectionEnd(mText); + final int newSelStart = getSelectionStart(); + final int newSelEnd = getSelectionEnd(); CommitSelectionReceiver csr = null; if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) { @@ -6689,9 +6716,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void prepareCursorController() { + boolean atLeastOneController = false; + // TODO Add an extra android:cursorController flag to disable the controller? - mInsertionPointCursorController = - mCursorVisible ? new InsertionPointCursorController() : null; + if (mCursorVisible) { + atLeastOneController = true; + if (mInsertionPointCursorController == null) { + mInsertionPointCursorController = new InsertionPointCursorController(); + } + } else { + mInsertionPointCursorController = null; + } + + if (canSelectText()) { + atLeastOneController = true; + if (mSelectionModifierCursorController == null) { + mSelectionModifierCursorController = new SelectionModifierCursorController(); + } + } else { + mSelectionModifierCursorController = null; + } + + if (atLeastOneController) { + if (sCursorControllerTempRect == null) { + sCursorControllerTempRect = new Rect(); + } + Resources res = mContext.getResources(); + mCursorControllerVerticalOffset = res.getDimensionPixelOffset( + com.android.internal.R.dimen.cursor_controller_vertical_offset); + } else { + sCursorControllerTempRect = null; + } } /** @@ -6751,8 +6806,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener TextView tv = mView.get(); if (tv != null && tv.isFocused()) { - int st = Selection.getSelectionStart(tv.mText); - int en = Selection.getSelectionEnd(tv.mText); + int st = tv.getSelectionStart(); + int en = tv.getSelectionEnd(); if (st == en && st >= 0 && en >= 0) { if (tv.mLayout != null) { @@ -6944,6 +6999,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean canSelectText() { + // prepareCursorController() relies on this method. + // If you change this condition, make sure prepareCursorController is called anywhere + // the value of this condition might be changed. if (mText instanceof Spannable && mText.length() != 0 && mMovement != null && mMovement.canSelectArbitrarily()) { return true; @@ -6992,10 +7050,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns a word to add to the dictionary from the context menu, - * or null if there is no cursor or no word at the cursor. + * Returns the offsets delimiting the 'word' located at position offset. + * + * @param offset An offset in the text. + * @return The offsets for the start and end of the word located at <code>offset</code>. + * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits. + * Returns a negative value if no valid word was found. */ - private String getWordForDictionary() { + private long getWordLimitsAt(int offset) { /* * Quick return if the input type is one where adding words * to the dictionary doesn't make any sense. @@ -7004,7 +7066,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (klass == InputType.TYPE_CLASS_NUMBER || klass == InputType.TYPE_CLASS_PHONE || klass == InputType.TYPE_CLASS_DATETIME) { - return null; + return -1; } int variation = mInputType & InputType.TYPE_MASK_VARIATION; @@ -7013,13 +7075,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD || variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_FILTER) { - return null; + return -1; } - int end = getSelectionEnd(); + int end = offset; if (end < 0) { - return null; + return -1; } int start = end; @@ -7053,6 +7115,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + if (start == end) { + return -1; + } + + if (end - start > 48) { + return -1; + } + boolean hasLetter = false; for (int i = start; i < end; i++) { if (Character.isLetter(mTransformed.charAt(i))) { @@ -7060,19 +7130,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } } + if (!hasLetter) { - return null; + return -1; } - if (start == end) { - return null; - } + // Two ints packed in a long + return (((long) start) << 32) | end; + } - if (end - start > 48) { + /** + * Returns a word to add to the dictionary from the context menu, + * or null if there is no cursor or no word at the cursor. + */ + private String getWordForDictionary() { + long wordLimits = getWordLimitsAt(getSelectionEnd()); + if (wordLimits < 0) { return null; + } else { + int start = (int) (wordLimits >>> 32); + int end = (int) (wordLimits & 0x00000000FFFFFFFFL); + return TextUtils.substring(mTransformed, start, end); } - - return TextUtils.substring(mTransformed, start, end); } @Override @@ -7372,6 +7451,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean performLongClick() { + // TODO This behavior should be moved to View + // TODO handle legacy code that added items to context menu + if (canSelectText()) { + if (startSelectionMode()) { + mEatTouchRelease = true; + return true; + } + } + if (super.performLongClick()) { mEatTouchRelease = true; return true; @@ -7380,6 +7468,83 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + private boolean startSelectionMode() { + if (mSelectionModifierCursorController != null) { + int offset = ((SelectionModifierCursorController) mSelectionModifierCursorController). + getTouchOffset(); + + int selectionStart, selectionEnd; + + if (hasSelection()) { + selectionStart = getSelectionStart(); + selectionEnd = getSelectionEnd(); + if (selectionStart > selectionEnd) { + int tmp = selectionStart; + selectionStart = selectionEnd; + selectionEnd = tmp; + } + if ((offset >= selectionStart) && (offset <= selectionEnd)) { + // Long press in the current selection. + // Should initiate a drag. Return false, to rely on context menu for now. + return false; + } + } + + long wordLimits = getWordLimitsAt(offset); + if (wordLimits >= 0) { + selectionStart = (int) (wordLimits >>> 32); + selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL); + } else { + selectionStart = Math.max(offset - 5, 0); + selectionEnd = Math.min(offset + 5, mText.length()); + } + + Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); + + // Has to be done AFTER selection has been changed to correctly position controllers. + mSelectionModifierCursorController.show(); + + return true; + } + + return false; + } + + /** + * Get the offset character closest to the specified absolute position. + * + * @param x The horizontal absolute position of a point on screen + * @param y The vertical absolute position of a point on screen + * @return the character offset for the character whose position is closest to the specified + * position. + * + * @hide + */ + public int getOffset(int x, int y) { + x -= getTotalPaddingLeft(); + y -= getTotalPaddingTop(); + + // Clamp the position to inside of the view. + if (x < 0) { + x = 0; + } else if (x >= (getWidth() - getTotalPaddingRight())) { + x = getWidth()-getTotalPaddingRight() - 1; + } + if (y < 0) { + y = 0; + } else if (y >= (getHeight() - getTotalPaddingBottom())) { + y = getHeight()-getTotalPaddingBottom() - 1; + } + + x += getScrollX(); + y += getScrollY(); + + Layout layout = getLayout(); + final int line = layout.getLineForVertical(y); + final int offset = layout.getOffsetForHorizontal(line, x); + return offset; + } + /** * A CursorController instance can be used to control a cursor in the text. * @@ -7387,6 +7552,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * and send them to this object instead of the cursor. */ public interface CursorController { + /* Cursor fade-out animation duration, in milliseconds. */ + static final int FADE_OUT_DURATION = 400; + /** * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}. * See also {@link #hide()}. @@ -7402,19 +7570,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Update the controller's position. */ - public void updatePosition(); + public void updatePosition(int offset); /** * The controller and the cursor's positions can be link by a fixed offset, * computed when the controller is touched, and then maintained as it moves * @return Horizontal offset between the controller and the cursor. */ - public int getOffsetX(); + public float getOffsetX(); /** * @return Vertical offset between the controller and the cursor. */ - public int getOffsetY(); + public float getOffsetY(); /** * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller @@ -7434,12 +7602,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener class InsertionPointCursorController implements CursorController { private static final int DELAY_BEFORE_FADE_OUT = 2100; - private static final int FADE_OUT_DURATION = 400; // Whether or not the cursor control is currently visible private boolean mIsVisible = false; - // Current cursor control bounds, in content coordinates - private final Rect mBounds = new Rect(); // Starting time of the fade timer private long mFadeOutTimerStart; // The cursor controller image @@ -7447,7 +7612,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Used to detect a tap (vs drag) on the controller private long mOnDownTimerStart; // Offset between finger hot point on cursor controller and actual cursor - private int mOffsetX, mOffsetY; + private float mOffsetX, mOffsetY; InsertionPointCursorController() { Resources res = mContext.getResources(); @@ -7455,10 +7620,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public void show() { - updatePosition(); + updateDrawablePosition(); // Has to be done after updatePosition, so that previous position invalidate // in only done if necessary. mIsVisible = true; + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.hide(); + } } public void hide() { @@ -7467,7 +7635,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Start fading out, only if not already in progress if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) { mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT; - postInvalidate(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + postInvalidate(mDrawable); } } } @@ -7476,15 +7644,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mIsVisible) { int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); if (time <= DELAY_BEFORE_FADE_OUT) { - postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time, - mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time, mDrawable); } else { time -= DELAY_BEFORE_FADE_OUT; if (time <= FADE_OUT_DURATION) { - int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION; + final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION; mDrawable.setAlpha(alpha); - postInvalidateDelayed(30, - mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + postInvalidateDelayed(30, mDrawable); } else { mDrawable.setAlpha(0); mIsVisible = false; @@ -7494,113 +7660,299 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - public void updatePosition() { + public void updatePosition(int offset) { + Selection.setSelection((Spannable) mText, offset); + updateDrawablePosition(); + } + + private void updateDrawablePosition() { if (mIsVisible) { // Clear previous cursor controller before bounds are updated - postInvalidate(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + postInvalidate(mDrawable); } - final int offset = Selection.getSelectionStart(mText); + final int offset = getSelectionStart(); if (offset < 0) { // Should never happen, safety check. - Log.w(LOG_TAG, "Update cursor controller position called with no cursor", null); + Log.w(LOG_TAG, "Update cursor controller position called with no cursor"); mIsVisible = false; return; } - final int cursorControllerDrawableWidth = mDrawable.getIntrinsicWidth(); - final int cursorControllerDrawableHeight = mDrawable.getIntrinsicHeight(); - final int line = mLayout.getLineForOffset(offset); + positionDrawableUnderCursor(offset, mDrawable); + + mFadeOutTimerStart = System.currentTimeMillis(); + mDrawable.setAlpha(255); + } + + public void onTouchEvent(MotionEvent event) { + if (isFocused() && isTextEditable() && mIsVisible) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN : { + final float x = event.getX(); + final float y = event.getY(); + + if (fingerIsOnDrawable(x, y, mDrawable)) { + show(); + + if (mMovement instanceof ArrowKeyMovementMethod) { + ((ArrowKeyMovementMethod)mMovement).setCursorController(this); + } + + if (mParent != null) { + // Prevent possible scrollView parent from scrolling, so that + // we can use auto-scrolling. + mParent.requestDisallowInterceptTouchEvent(true); + + final Rect bounds = mDrawable.getBounds(); + mOffsetX = (bounds.left + bounds.right) / 2.0f - x; + mOffsetY = bounds.top - mCursorControllerVerticalOffset - y; + + mOnDownTimerStart = System.currentTimeMillis(); + } + } + break; + } + + case MotionEvent.ACTION_UP : { + int time = (int) (System.currentTimeMillis() - mOnDownTimerStart); + + if (time <= ViewConfiguration.getTapTimeout()) { + // A tap on the controller is not grabbed, move the cursor instead + int offset = getOffset((int) event.getX(), (int) event.getY()); + Selection.setSelection((Spannable) mText, offset); + + // Modified by cancelLongPress and prevents the cursor from changing + mScrolled = false; + } + break; + } + } + } + } + + public float getOffsetX() { + return mOffsetX; + } + + public float getOffsetY() { + return mOffsetY; + } + } + + class SelectionModifierCursorController implements CursorController { + // Whether or not the selection controls are currently visible + private boolean mIsVisible = false; + // Whether that start or the end of selection controller is dragged + private boolean mStartIsDragged = false; + // Starting time of the fade timer + private long mFadeOutTimerStart; + // The cursor controller images + private final Drawable mStartDrawable, mEndDrawable; + // Offset between finger hot point on active cursor controller and actual cursor + private float mOffsetX, mOffsetY; + // The offset of that last touch down event. Remembered to start selection there. + private int mTouchOffset; + + SelectionModifierCursorController() { + Resources res = mContext.getResources(); + mStartDrawable = res.getDrawable(com.android.internal.R.drawable.selection_start_handle); + mEndDrawable = res.getDrawable(com.android.internal.R.drawable.selection_end_handle); + } - mBounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5 - - cursorControllerDrawableWidth / 2.0); - mBounds.top = mLayout.getLineTop(line + 1); + public void show() { + updateDrawablesPositions(); + // Has to be done after updatePosition, so that previous position invalidate + // in only done if necessary. + mIsVisible = true; + mFadeOutTimerStart = -1; + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.hide(); + } + } - // Move cursor controller a little bit up when editing the last line of text - // (or a single line) so that it is visible and easier to grab. - if (line == mLayout.getLineCount() - 1) { - mBounds.top -= Math.max(0, - cursorControllerDrawableHeight / 2 - getExtendedPaddingBottom()); + public void hide() { + if (mIsVisible && (mFadeOutTimerStart < 0)) { + mFadeOutTimerStart = System.currentTimeMillis(); + postInvalidate(mStartDrawable); + postInvalidate(mEndDrawable); } + } - mBounds.right = mBounds.left + cursorControllerDrawableWidth; - mBounds.bottom = mBounds.top + cursorControllerDrawableHeight; + public void draw(Canvas canvas) { + if (mIsVisible) { + if (mFadeOutTimerStart >= 0) { + int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); + if (time <= FADE_OUT_DURATION) { + final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION; + mStartDrawable.setAlpha(alpha); + mEndDrawable.setAlpha(alpha); + postInvalidateDelayed(30, mStartDrawable); + postInvalidateDelayed(30, mEndDrawable); + } else { + mStartDrawable.setAlpha(0); + mEndDrawable.setAlpha(0); + mIsVisible = false; + } + } + mStartDrawable.draw(canvas); + mEndDrawable.draw(canvas); + } + } - convertFromViewportToContentCoordinates(mBounds); - mDrawable.setBounds(mBounds); + public void updatePosition(int offset) { + int selectionStart = getSelectionStart(); + int selectionEnd = getSelectionEnd(); - mFadeOutTimerStart = System.currentTimeMillis(); - mDrawable.setAlpha(255); + // Handle the case where start and end are swapped, making sure start <= end + if (mStartIsDragged) { + if (offset <= selectionEnd) { + selectionStart = offset; + } else { + selectionStart = selectionEnd; + selectionEnd = offset; + mStartIsDragged = false; + } + } else { + if (offset >= selectionStart) { + selectionEnd = offset; + } else { + selectionEnd = selectionStart; + selectionStart = offset; + mStartIsDragged = true; + } + } - postInvalidate(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); + updateDrawablesPositions(); } - public void onTouchEvent(MotionEvent event) { - if (isFocused() && isTextEditable()) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mIsVisible) { - final int x = (int) event.getX(); - final int y = (int) event.getY(); - - // Simulate a 'fat finger' to ease grabbing of the controller. - // Expand according to controller image size instead of using density. - // Assume controller imager has a sensible size, proportionnal to density. - final int cursorControllerDrawableWidth = mDrawable.getIntrinsicWidth(); - final int cursorControllerDrawableHeight = mDrawable.getIntrinsicHeight(); - final Rect fingerRect = new Rect( - x - cursorControllerDrawableWidth / 2, - y - cursorControllerDrawableHeight, - x + cursorControllerDrawableWidth / 2, - y); - - if (Rect.intersects(mBounds, fingerRect)) { - show(); - - if (mMovement instanceof ArrowKeyMovementMethod) { - ((ArrowKeyMovementMethod)mMovement).setCursorController(this); - } + private void updateDrawablesPositions() { + if (mIsVisible) { + // Clear previous cursor controller before bounds are updated + postInvalidate(mStartDrawable); + postInvalidate(mEndDrawable); + } + + final int selectionStart = getSelectionStart(); + final int selectionEnd = getSelectionEnd(); + + if ((selectionStart < 0) || (selectionEnd < 0)) { + // Should never happen, safety check. + Log.w(LOG_TAG, "Update selection controller position called with no cursor"); + mIsVisible = false; + return; + } + + positionDrawableUnderCursor(selectionStart, mStartDrawable); + positionDrawableUnderCursor(selectionEnd, mEndDrawable); - if (mParent != null) { - // Prevent possible scrollView parent from scrolling, so that - // we can use auto-scrolling. - mParent.requestDisallowInterceptTouchEvent(true); + mStartDrawable.setAlpha(255); + mEndDrawable.setAlpha(255); + } - Resources res = mContext.getResources(); - final int verticalOffset = res.getDimensionPixelOffset( - com.android.internal.R.dimen.cursor_controller_vertical_offset); + public void onTouchEvent(MotionEvent event) { + if (isFocused() && isTextEditable() && + (event.getActionMasked() == MotionEvent.ACTION_DOWN)) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + + // Remember finger down position, to be able to start selection on that point + mTouchOffset = getOffset(x, y); + + if (mIsVisible) { + if (mMovement instanceof ArrowKeyMovementMethod) { + boolean isOnStart = fingerIsOnDrawable(x, y, mStartDrawable); + boolean isOnEnd = fingerIsOnDrawable(x, y, mEndDrawable); + if (isOnStart || isOnEnd) { + if (mParent != null) { + // Prevent possible scrollView parent from scrolling, so that + // we can use auto-scrolling. + mParent.requestDisallowInterceptTouchEvent(true); + } - mOffsetX = (mBounds.left + mBounds.right) / 2 - x; - mOffsetY = mBounds.top - verticalOffset - y; + // Start handle will be dragged in case BOTH controller are under finger + mStartIsDragged = isOnStart; + final Rect bounds = + (mStartIsDragged ? mStartDrawable : mEndDrawable).getBounds(); + mOffsetX = (bounds.left + bounds.right) / 2.0f - x; + mOffsetY = bounds.top - mCursorControllerVerticalOffset - y; - mOnDownTimerStart = System.currentTimeMillis(); + ((ArrowKeyMovementMethod)mMovement).setCursorController(this); } } - } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { - int time = (int) (System.currentTimeMillis() - mOnDownTimerStart); - - if (mIsVisible && (time <= ViewConfiguration.getTapTimeout())) { - // A tap on the controller is not grabbed, move the cursor instead - final int x = (int) event.getX(); - final int y = (int) event.getY(); - - Layout layout = getLayout(); - int line = layout.getLineForVertical(y); - int offset = layout.getOffsetForHorizontal(line, x); - Selection.setSelection((Spannable) mText, offset); - // Modified by cancelLongPress and prevents the cursor from changing - mScrolled = false; - } } } } - public int getOffsetX() { + public int getTouchOffset() { + return mTouchOffset; + } + + public float getOffsetX() { return mOffsetX; } - public int getOffsetY() { + public float getOffsetY() { return mOffsetY; } + + /** + * @return true iff this controller is currently used to move the selection start. + */ + public boolean isSelectionStartDragged() { + return mIsVisible && mStartIsDragged; + } + } + + // Helper methods used by CursorController implementations + + private void positionDrawableUnderCursor(final int offset, Drawable drawable) { + final int drawableWidth = drawable.getIntrinsicWidth(); + final int drawableHeight = drawable.getIntrinsicHeight(); + final int line = mLayout.getLineForOffset(offset); + + final Rect bounds = sCursorControllerTempRect; + bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5 - drawableWidth / 2.0); + bounds.top = mLayout.getLineTop(line + 1); + + // Move cursor controller a little bit up when editing the last line of text + // (or a single line) so that it is visible and easier to grab. + if (line == mLayout.getLineCount() - 1) { + bounds.top -= Math.max(0, drawableHeight / 2 - getExtendedPaddingBottom()); + } + + bounds.right = bounds.left + drawableWidth; + bounds.bottom = bounds.top + drawableHeight; + + convertFromViewportToContentCoordinates(bounds); + drawable.setBounds(bounds); + postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + private boolean fingerIsOnDrawable(float x, float y, Drawable drawable) { + // Simulate a 'fat finger' to ease grabbing of the controller. + // Expands according to controller image size instead of using density. + // Assumes controller imager has a sensible size, proportionnal to density. + final int drawableWidth = drawable.getIntrinsicWidth(); + final int drawableHeight = drawable.getIntrinsicHeight(); + final Rect fingerRect = sCursorControllerTempRect; + fingerRect.set((int) (x - drawableWidth / 2.0), + (int) (y - drawableHeight), + (int) (x + drawableWidth / 2.0), + (int) y); + return Rect.intersects(drawable.getBounds(), fingerRect); + } + + private void postInvalidate(Drawable drawable) { + final Rect bounds = drawable.getBounds(); + postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + private void postInvalidateDelayed(long delay, Drawable drawable) { + final Rect bounds = drawable.getBounds(); + postInvalidateDelayed(delay, bounds.left, bounds.top, bounds.right, bounds.bottom); } @ViewDebug.ExportedProperty @@ -7624,15 +7976,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final TextPaint mTextPaint; private boolean mUserSetTextScaleX; private final Paint mHighlightPaint; - private int mHighlightColor = 0xFFBBDDFF; + private int mHighlightColor = 0xD077A14B; private Layout mLayout; private long mShowCursor; private Blink mBlink; private boolean mCursorVisible = true; - // Cursor Controller. Null when disabled. + // Cursor Controllers. Null when disabled. private CursorController mInsertionPointCursorController; + private CursorController mSelectionModifierCursorController; + // Stored once and for all. + private int mCursorControllerVerticalOffset; + // Created once and shared by different CursorController helper methods. + private static Rect sCursorControllerTempRect; private boolean mSelectAllOnFocus = false; diff --git a/core/res/res/drawable-hdpi/selection_end_handle.png b/core/res/res/drawable-hdpi/selection_end_handle.png Binary files differnew file mode 100644 index 0000000..624ab58 --- /dev/null +++ b/core/res/res/drawable-hdpi/selection_end_handle.png diff --git a/core/res/res/drawable-hdpi/selection_start_handle.png b/core/res/res/drawable-hdpi/selection_start_handle.png Binary files differnew file mode 100644 index 0000000..7d6f24c --- /dev/null +++ b/core/res/res/drawable-hdpi/selection_start_handle.png diff --git a/core/res/res/drawable-mdpi/selection_end_handle.png b/core/res/res/drawable-mdpi/selection_end_handle.png Binary files differnew file mode 100644 index 0000000..7e075eb --- /dev/null +++ b/core/res/res/drawable-mdpi/selection_end_handle.png diff --git a/core/res/res/drawable-mdpi/selection_start_handle.png b/core/res/res/drawable-mdpi/selection_start_handle.png Binary files differnew file mode 100644 index 0000000..d8022f7 --- /dev/null +++ b/core/res/res/drawable-mdpi/selection_start_handle.png diff --git a/core/res/res/layout/list_content.xml b/core/res/res/layout/list_content.xml index 6f9f1e0..1414032 100644 --- a/core/res/res/layout/list_content.xml +++ b/core/res/res/layout/list_content.xml @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* //device/apps/common/assets/res/layout/list_content.xml -** -** Copyright 2006, The Android Open Source Project +/* Copyright 2010, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -17,8 +15,42 @@ ** limitations under the License. */ --> -<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:drawSelectorOnTop="false" - /> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout android:id="@+id/progressContainer" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:gravity="center"> + + <ProgressBar style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/loading" + android:paddingTop="4dip" + android:singleLine="true" /> + + </LinearLayout> + + <FrameLayout android:id="@+id/listContainer" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ListView android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:drawSelectorOnTop="false" /> + <TextView android:id="@+android:id/internalEmpty" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:textAppearance="?android:attr/textAppearanceLarge" /> + </FrameLayout> + +</FrameLayout> diff --git a/core/res/res/layout/list_content_rich.xml b/core/res/res/layout/list_content_rich.xml deleted file mode 100644 index 1414032..0000000 --- a/core/res/res/layout/list_content_rich.xml +++ /dev/null @@ -1,56 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* Copyright 2010, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout android:id="@+id/progressContainer" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - android:gravity="center"> - - <ProgressBar style="?android:attr/progressBarStyleLarge" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - <TextView android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:text="@string/loading" - android:paddingTop="4dip" - android:singleLine="true" /> - - </LinearLayout> - - <FrameLayout android:id="@+id/listContainer" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ListView android:id="@android:id/list" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:drawSelectorOnTop="false" /> - <TextView android:id="@+android:id/internalEmpty" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center" - android:textAppearance="?android:attr/textAppearanceLarge" /> - </FrameLayout> - -</FrameLayout> diff --git a/core/res/res/layout/list_content_simple.xml b/core/res/res/layout/list_content_simple.xml new file mode 100644 index 0000000..6f9f1e0 --- /dev/null +++ b/core/res/res/layout/list_content_simple.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/assets/res/layout/list_content.xml +** +** Copyright 2006, 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. +*/ +--> +<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:drawSelectorOnTop="false" + /> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 8b9a4ae..d0be554 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -869,8 +869,11 @@ <public type="drawable" name="stat_sys_download" id="0x01080081" /> <public type="drawable" name="stat_sys_download_done" id="0x01080082" /> <public type="drawable" name="stat_sys_headset" id="0x01080083" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_phone_call" id="0x01080084" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_phone_call_forward" id="0x01080085" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_phone_call_on_hold" id="0x01080086" /> <public type="drawable" name="stat_sys_speakerphone" id="0x01080087" /> <public type="drawable" name="stat_sys_upload" id="0x01080088" /> @@ -1132,7 +1135,9 @@ <public type="style" name="Widget.ProgressBar.Large.Inverse" id="0x0103005c" /> <public type="style" name="Widget.ProgressBar.Small.Inverse" id="0x0103005d" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_vp_phone_call" id="0x010800a7" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_vp_phone_call_on_hold" id="0x010800a8" /> <public type="anim" name="anticipate_interpolator" id="0x010a0007" /> @@ -1310,4 +1315,11 @@ <public type="style" name="Widget.Spinner.DropDown" /> <public type="style" name="Widget.ActionButton" /> + <!-- Standard content view for a {@link android.app.ListFragment}. + If you are implementing a subclass of ListFragment with your + own customized content, you can include this layout in that + content to still retain all of the standard functionality of + the base class. --> + <public type="layout" name="list_content" /> + </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 449b56c..3c09a89 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -626,7 +626,7 @@ <style name="TextAppearance"> <item name="android:textColor">?textColorPrimary</item> - <item name="android:textColorHighlight">#FFFF9200</item> + <item name="android:textColorHighlight">#D077A14B</item> <item name="android:textColorHint">?textColorHint</item> <item name="android:textColorLink">#5C5CFF</item> <item name="android:textSize">16sp</item> |