diff options
Diffstat (limited to 'core/java')
50 files changed, 653 insertions, 2243 deletions
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index 5df8bdc..edd0fa3 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -54,8 +54,8 @@ public class ValueAnimator extends Animator { * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent * by the handler to itself to process the next animation frame */ - private static final int ANIMATION_START = 0; - private static final int ANIMATION_FRAME = 1; + static final int ANIMATION_START = 0; + static final int ANIMATION_FRAME = 1; /** * Values used with internal variable mPlayingState to indicate the current state of an diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index f7e5cf1..93e30af 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -18,6 +18,7 @@ package android.app; import com.android.internal.app.IUsageStats; import com.android.internal.os.PkgUsageStats; +import com.android.internal.util.MemInfoReader; import android.content.ComponentName; import android.content.Context; @@ -28,6 +29,7 @@ import android.content.pm.IPackageDataObserver; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Point; import android.os.Debug; import android.os.Handler; import android.os.Parcel; @@ -38,6 +40,8 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Slog; +import android.view.Display; import java.util.ArrayList; import java.util.HashMap; @@ -206,6 +210,31 @@ public class ActivityManager { } /** + * Used by persistent processes to determine if they are running on a + * higher-end device so should be okay using hardware drawing acceleration + * (which tends to consume a lot more RAM). + * @hide + */ + static public boolean isHighEndGfx(Display display) { + MemInfoReader reader = new MemInfoReader(); + reader.readMemInfo(); + if (reader.getTotalSize() >= (512*1024*1024)) { + // If the device has at least 512MB RAM available to the kernel, + // we can afford the overhead of graphics acceleration. + return true; + } + Point p = new Point(); + display.getRealSize(p); + int pixels = p.x * p.y; + if (pixels >= (1024*600)) { + // If this is a sufficiently large screen, then there are enough + // pixels on it that we'd really like to use hw drawing. + return true; + } + return false; + } + + /** * Information you can retrieve about tasks that the user has most recently * started or visited. */ diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index 491fcfe..c09e87f 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -75,6 +75,18 @@ public class AlertDialog extends Dialog implements DialogInterface { * the holographic alert theme with a light background. */ public static final int THEME_HOLO_LIGHT = 3; + + /** + * Special theme constant for {@link #AlertDialog(Context, int)}: use + * the device's default alert theme with a dark background. + */ + public static final int THEME_DEVICE_DEFAULT_DARK = 4; + + /** + * Special theme constant for {@link #AlertDialog(Context, int)}: use + * the device's default alert theme with a dark background. + */ + public static final int THEME_DEVICE_DEFAULT_LIGHT = 5; protected AlertDialog(Context context) { this(context, resolveDialogTheme(context, 0), true); @@ -113,6 +125,10 @@ public class AlertDialog extends Dialog implements DialogInterface { return com.android.internal.R.style.Theme_Holo_Dialog_Alert; } else if (resid == THEME_HOLO_LIGHT) { return com.android.internal.R.style.Theme_Holo_Light_Dialog_Alert; + } else if (resid == THEME_DEVICE_DEFAULT_DARK) { + return com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert; + } else if (resid == THEME_DEVICE_DEFAULT_LIGHT) { + return com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog_Alert; } else if (resid >= 0x01000000) { // start of real resource IDs. return resid; } else { diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 6524c9a..588125d 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -332,20 +332,31 @@ public class ApplicationErrorReport implements Parcelable { exceptionMessage = tr.getMessage(); // Populate fields with the "root cause" exception + Throwable rootTr = tr; while (tr.getCause() != null) { tr = tr.getCause(); + if (tr.getStackTrace() != null && tr.getStackTrace().length > 0) { + rootTr = tr; + } String msg = tr.getMessage(); if (msg != null && msg.length() > 0) { exceptionMessage = msg; } } - exceptionClassName = tr.getClass().getName(); - StackTraceElement trace = tr.getStackTrace()[0]; - throwFileName = trace.getFileName(); - throwClassName = trace.getClassName(); - throwMethodName = trace.getMethodName(); - throwLineNumber = trace.getLineNumber(); + exceptionClassName = rootTr.getClass().getName(); + if (rootTr.getStackTrace().length > 0) { + StackTraceElement trace = rootTr.getStackTrace()[0]; + throwFileName = trace.getFileName(); + throwClassName = trace.getClassName(); + throwMethodName = trace.getMethodName(); + throwLineNumber = trace.getLineNumber(); + } else { + throwFileName = "unknown"; + throwClassName = "unknown"; + throwMethodName = "unknown"; + throwLineNumber = 0; + } } /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index b4bdb2f..2139704 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -364,7 +364,8 @@ class ContextImpl extends Context { Resources.selectSystemTheme(0, outerContext.getApplicationInfo().targetSdkVersion, com.android.internal.R.style.Theme_Dialog, - com.android.internal.R.style.Theme_Holo_Dialog)), + com.android.internal.R.style.Theme_Holo_Dialog, + com.android.internal.R.style.Theme_DeviceDefault_Dialog)), ctx.mMainThread.getHandler()); }}); diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java index cce7cd6..8921578 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -204,7 +204,7 @@ public class DialogFragment extends Fragment public void setStyle(int style, int theme) { mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { - mTheme = com.android.internal.R.style.Theme_Holo_Dialog_NoFrame; + mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame; } if (theme != 0) { mTheme = theme; diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 931cb18..ffefaa2 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -62,7 +62,7 @@ public final class AssetManager { private static final boolean DEBUG_REFS = false; private static final Object sSync = new Object(); - private static AssetManager sSystem = null; + /*package*/ static AssetManager sSystem = null; private final TypedValue mValue = new TypedValue(); private final long[] mOffsets = new long[2]; @@ -252,7 +252,7 @@ public final class AssetManager { } } - private final void makeStringBlocks(boolean copyFromSystem) { + /*package*/ final void makeStringBlocks(boolean copyFromSystem) { final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; final int num = getStringBlockCount(); mStringBlocks = new StringBlock[num]; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 324c9fd..24f8319 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -77,7 +77,7 @@ public class Resources { private static final int ID_OTHER = 0x01000004; private static final Object mSync = new Object(); - private static Resources mSystem = null; + /*package*/ static Resources mSystem = null; // Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all @@ -134,18 +134,24 @@ public class Resources { /** @hide */ public static int selectDefaultTheme(int curTheme, int targetSdkVersion) { return selectSystemTheme(curTheme, targetSdkVersion, - com.android.internal.R.style.Theme, com.android.internal.R.style.Theme_Holo); + com.android.internal.R.style.Theme, + com.android.internal.R.style.Theme_Holo, + com.android.internal.R.style.Theme_DeviceDefault); } /** @hide */ - public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo) { + public static int selectSystemTheme(int curTheme, int targetSdkVersion, + int orig, int holo, int deviceDefault) { if (curTheme != 0) { return curTheme; } if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) { return orig; } - return holo; + if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return holo; + } + return deviceDefault; } /** diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 37fdeb6..2df492e 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -42,7 +42,7 @@ public class TypedArray { /*package*/ int[] mData; /*package*/ int[] mIndices; /*package*/ int mLength; - private TypedValue mValue = new TypedValue(); + /*package*/ TypedValue mValue = new TypedValue(); /** * Return the number of values in this array. diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 9481a88..370e22a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -617,7 +617,9 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void onCreate() { mTheme = Resources.selectSystemTheme(mTheme, getApplicationInfo().targetSdkVersion, - android.R.style.Theme_InputMethod, android.R.style.Theme_Holo_InputMethod); + android.R.style.Theme_InputMethod, + android.R.style.Theme_Holo, + android.R.style.Theme_DeviceDefault_InputMethod); super.setTheme(mTheme); super.onCreate(); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 3b21590..2c875c8 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1689,7 +1689,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { return HierarchicalUri.readFrom(in); } - throw new AssertionError("Unknown URI type: " + type); + throw new IllegalArgumentException("Unknown URI type: " + type); } public Uri[] newArray(int size) { @@ -1996,7 +1996,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { parcel.writeInt(Representation.DECODED); parcel.writeString(decoded); } else { - throw new AssertionError(); + throw new IllegalArgumentException("Neither encoded nor decoded"); } } } @@ -2037,7 +2037,8 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { case Representation.DECODED: return fromDecoded(parcel.readString()); default: - throw new AssertionError(); + throw new IllegalArgumentException("Unknown representation: " + + representation); } } @@ -2221,7 +2222,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { case Representation.DECODED: return fromDecoded(parcel.readString()); default: - throw new AssertionError(); + throw new IllegalArgumentException("Bad representation: " + representation); } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 6c7c58d..1e9ee7c 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -287,6 +287,15 @@ public class Build { * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated} * to turn it off if needed, although this is strongly discouraged since * it will result in poor performance on larger screen devices. + * <li> The default theme for applications is now the "device default" theme: + * {@link android.R.style#Theme_DeviceDefault}. This may be the + * holo dark theme or a different dark theme defined by the specific device. + * The {@link android.R.style#Theme_Holo} family must not be modified + * for a device to be considered compatible. Applications that explicitly + * request a theme from the Holo family will be guaranteed that these themes + * will not change character within the same platform version. Applications + * that wish to blend in with the device should use a theme from the + * {@link android.R.style#Theme_DeviceDefault} family. * </ul> */ public static final int ICE_CREAM_SANDWICH = CUR_DEVELOPMENT; diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java index 911439a..d61b3b4 100644 --- a/core/java/android/os/HandlerThread.java +++ b/core/java/android/os/HandlerThread.java @@ -21,9 +21,9 @@ package android.os; * used to create handler classes. Note that start() must still be called. */ public class HandlerThread extends Thread { - private int mPriority; - private int mTid = -1; - private Looper mLooper; + int mPriority; + int mTid = -1; + Looper mLooper; public HandlerThread(String name) { super(name); diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index c61f28a..5607f7f 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -54,7 +54,7 @@ public class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare(). - private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); + static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); final MessageQueue mQueue; final Thread mThread; diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java index b48e8ce..fe5e76c 100644 --- a/core/java/android/preference/VolumePreference.java +++ b/core/java/android/preference/VolumePreference.java @@ -114,7 +114,9 @@ public class VolumePreference extends SeekBarDialogPreference implements } public void onActivityStop() { - cleanup(); + if (mSeekBarVolumizer != null) { + mSeekBarVolumizer.stopSample(); + } } /** diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 9c6f5c9..c5a924b 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -241,6 +241,9 @@ public class CallLog { values.put(DATE, Long.valueOf(start)); values.put(DURATION, Long.valueOf(duration)); values.put(NEW, Integer.valueOf(1)); + if (callType == MISSED_TYPE) { + values.put(IS_READ, Integer.valueOf(0)); + } if (ci != null) { values.put(CACHED_NAME, ci.name); values.put(CACHED_NUMBER_TYPE, ci.numberType); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 5765dde..d867e35 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -2890,8 +2890,6 @@ public final class ContactsContract { * values.put(StreamItems.TEXT, "Breakfasted at Tiffanys"); * values.put(StreamItems.TIMESTAMP, timestamp); * values.put(StreamItems.COMMENTS, "3 people reshared this"); - * values.put(StreamItems.ACTION, action); - * values.put(StreamItems.ACTION_URI, actionUri); * Uri streamItemUri = getContentResolver().insert( * Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId), * RawContacts.StreamItems.CONTENT_DIRECTORY), values); @@ -2905,8 +2903,6 @@ public final class ContactsContract { * values.put(StreamItems.TEXT, "Breakfasted at Tiffanys"); * values.put(StreamItems.TIMESTAMP, timestamp); * values.put(StreamItems.COMMENTS, "3 people reshared this"); - * values.put(StreamItems.ACTION, action); - * values.put(StreamItems.ACTION_URI, actionUri); * Uri streamItemUri = getContentResolver().insert(StreamItems.CONTENT_URI, values); * long streamItemId = ContentUris.parseId(streamItemUri); * </dd> @@ -2924,8 +2920,6 @@ public final class ContactsContract { * values.clear(); * values.put(StreamItemPhotos.SORT_INDEX, 1); * values.put(StreamItemPhotos.PHOTO, photoData); - * values.put(StreamItemPhotos.ACTION, action); - * values.put(StreamItemPhotos.ACTION_URI, actionUri); * getContentResolver().insert(Uri.withAppendedPath( * ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId), * StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), values); @@ -2938,8 +2932,6 @@ public final class ContactsContract { * values.put(StreamItemPhotos.STREAM_ITEM_ID, streamItemId); * values.put(StreamItemPhotos.SORT_INDEX, 1); * values.put(StreamItemPhotos.PHOTO, photoData); - * values.put(StreamItemPhotos.ACTION, action); - * values.put(StreamItemPhotos.ACTION_URI, actionUri); * getContentResolver().insert(StreamItems.CONTENT_PHOTO_URI, values); * </pre> * Note that this latter form allows the insertion of a stream item and its @@ -3081,16 +3073,56 @@ public final class ContactsContract { public static final String RES_PACKAGE = "res_package"; /** - * The resource ID of the icon for the source of the stream item. - * This resource should be scoped by the {@link #RES_PACKAGE}. - * <P>Type: NUMBER</P> + * The account type to which the raw_contact of this item is associated. See + * {@link RawContacts#ACCOUNT_TYPE} + * + * <p>TYPE: text</p> + * <p>read-only</p> + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** + * The account name to which the raw_contact of this item is associated. See + * {@link RawContacts#ACCOUNT_NAME} + * + * <p>TYPE: text</p> + * <p>read-only</p> + */ + public static final String ACCOUNT_NAME = "account_name"; + + /** + * The data set within the account that the raw_contact of this row belongs to. This allows + * multiple sync adapters for the same account type to distinguish between + * each others' data. + * {@link RawContacts#DATA_SET} + * + * <P>Type: TEXT</P> + * <p>read-only</p> + */ + public static final String DATA_SET = "data_set"; + + /** + * The source_id of the raw_contact that this row belongs to. + * {@link RawContacts#SOURCE_ID} + * + * <P>Type: TEXT</P> + * <p>read-only</p> + */ + public static final String RAW_CONTACT_SOURCE_ID = "raw_contact_source_id"; + + /** + * The resource name of the icon for the source of the stream item. + * This resource should be scoped by the {@link #RES_PACKAGE}. As this can only reference + * drawables, the "@drawable/" prefix must be omitted. + * <P>Type: TEXT</P> */ public static final String RES_ICON = "icon"; /** - * The resource ID of the label describing the source of the status update, e.g. "Google - * Talk". This resource should be scoped by the {@link #RES_PACKAGE}. - * <p>Type: NUMBER</p> + * The resource name of the label describing the source of the status update, e.g. "Google + * Talk". This resource should be scoped by the {@link #RES_PACKAGE}. As this can only + * reference strings, the "@string/" prefix must be omitted. + * <p>Type: TEXT</p> */ public static final String RES_LABEL = "label"; @@ -3136,18 +3168,14 @@ public final class ContactsContract { */ public static final String COMMENTS = "comments"; - /** - * The activity action to execute when the item is tapped. - * <P>Type: TEXT</P> - */ - public static final String ACTION = "action"; - - /** - * The URI that is launched when the item is pressed. May be handled by - * the source app, but could also reference a website (e.g. YouTube). - * <P>Type: TEXT</P> - */ - public static final String ACTION_URI = "action_uri"; + /** Generic column for use by sync adapters. */ + public static final String SYNC1 = "stream_item_sync1"; + /** Generic column for use by sync adapters. */ + public static final String SYNC2 = "stream_item_sync2"; + /** Generic column for use by sync adapters. */ + public static final String SYNC3 = "stream_item_sync3"; + /** Generic column for use by sync adapters. */ + public static final String SYNC4 = "stream_item_sync4"; } /** @@ -3171,8 +3199,6 @@ public final class ContactsContract { * ContentValues values = new ContentValues(); * values.put(StreamItemPhotos.SORT_INDEX, 1); * values.put(StreamItemPhotos.PHOTO, photoData); - * values.put(StreamItemPhotos.ACTION, action); - * values.put(StreamItemPhotos.ACTION_URI, actionUri); * Uri photoUri = getContentResolver().insert(Uri.withAppendedPath( * ContentUris.withAppendedId(StreamItems.CONTENT_URI, streamItemId) * StreamItems.StreamItemPhotos#CONTENT_DIRECTORY), values); @@ -3186,8 +3212,6 @@ public final class ContactsContract { * values.put(StreamItemPhotos.STREAM_ITEM_ID, streamItemId); * values.put(StreamItemPhotos.SORT_INDEX, 1); * values.put(StreamItemPhotos.PHOTO, photoData); - * values.put(StreamItemPhotos.ACTION, action); - * values.put(StreamItemPhotos.ACTION_URI, actionUri); * Uri photoUri = getContentResolver().insert(StreamItems.CONTENT_PHOTO_URI, values); * long photoId = ContentUris.parseId(photoUri); * </pre> @@ -3353,18 +3377,14 @@ public final class ContactsContract { */ public static final String PHOTO_URI = "photo_uri"; - /** - * The activity action to execute when the photo is tapped. - * <P>Type: TEXT</P> - */ - public static final String ACTION = "action"; - - /** - * The URI that is launched when the photo is pressed. May be handled by - * the source app, but could also reference a website (e.g. YouTube). - * <P>Type: TEXT</P> - */ - public static final String ACTION_URI = "action_uri"; + /** Generic column for use by sync adapters. */ + public static final String SYNC1 = "stream_item_photo_sync1"; + /** Generic column for use by sync adapters. */ + public static final String SYNC2 = "stream_item_photo_sync2"; + /** Generic column for use by sync adapters. */ + public static final String SYNC3 = "stream_item_photo_sync3"; + /** Generic column for use by sync adapters. */ + public static final String SYNC4 = "stream_item_photo_sync4"; } /** @@ -6496,28 +6516,6 @@ public final class ContactsContract { public static final String NOTES = "notes"; /** - * The Activity action to open the group in the source app (e.g. - * {@link Intent#ACTION_VIEW}). Can be NULL if the group does not have a dedicated viewer. - * This is used in conjunction with {@link #ACTION_URI}: In order to show an "Open in - * (sourceapp)"-button, both of these fields must be set - * <p> - * Type: TEXT - */ - public static final String ACTION = "action"; - - - /** - * Uri to open the group in the source app. - * Can be NULL if the group does not have a dedicated viewer. - * This is used in conjunction with {@link #ACTION}: In order to show an "Open in - * (sourceapp)"-button, both of these fields must be set - * <p> - * Type: TEXT - */ - public static final String ACTION_URI = "action_uri"; - - - /** * The ID of this group if it is a System Group, i.e. a group that has a special meaning * to the sync adapter, null otherwise. * <P>Type: TEXT</P> diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java index 031375e..e15e61f 100644 --- a/core/java/android/server/BluetoothAdapterStateMachine.java +++ b/core/java/android/server/BluetoothAdapterStateMachine.java @@ -39,14 +39,14 @@ import java.io.PrintWriter; * (BluetootOn)<----------------------<- * | ^ -------------------->- | * | | | | - * TURN_OFF | | BECAME_PAIRABLE m1 | | USER_TURN_ON + * TURN_OFF | | SCAN_MODE_CHANGED m1 | | USER_TURN_ON * AIRPLANE_MODE_ON | | | | * V | | | * (Switching) (PerProcessState) * | ^ | | - * BECAME_NON_PAIRABLE& | | TURN_ON(_CONTINUE) | | + * POWER_STATE_CHANGED & | | TURN_ON(_CONTINUE) | | * ALL_DEVICES_DISCONNECTED | | m2 | | - * V |------------------------< | BECAME_PAIRABLE + * V |------------------------< | SCAN_MODE_CHANGED * (HotOff)-------------------------->- PER_PROCESS_TURN_ON * / ^ * / | SERVICE_RECORD_LOADED @@ -61,7 +61,7 @@ import java.io.PrintWriter; * Legend: * m1 = TURN_HOT * m2 = Transition to HotOff when number of process wanting BT on is 0. - * BECAME_NON_PAIRABLE will make the transition. + * POWER_STATE_CHANGED will make the transition. */ final class BluetoothAdapterStateMachine extends StateMachine { private static final String TAG = "BluetoothAdapterStateMachine"; @@ -83,10 +83,10 @@ final class BluetoothAdapterStateMachine extends StateMachine { static final int SERVICE_RECORD_LOADED = 51; // Event indicates all the remote Bluetooth devices has been disconnected static final int ALL_DEVICES_DISCONNECTED = 52; - // Event indicates the Bluetooth is connectable - static final int BECAME_PAIRABLE = 53; - // Event indicates the Bluetooth is non-connectable. - static final int BECAME_NON_PAIRABLE = 54; + // Event indicates the Bluetooth scan mode has changed + static final int SCAN_MODE_CHANGED = 53; + // Event indicates the powered state has changed + static final int POWER_STATE_CHANGED = 54; // Event indicates airplane mode is turned on static final int AIRPLANE_MODE_ON = 55; // Event indicates airplane mode is turned off @@ -107,6 +107,8 @@ final class BluetoothAdapterStateMachine extends StateMachine { private static final int TURN_COLD = 103; // Device disconnecting timeout happens private static final int DEVICES_DISCONNECT_TIMEOUT = 104; + // Prepare Bluetooth timeout happens + private static final int PREPARE_BLUETOOTH_TIMEOUT = 105; private Context mContext; private BluetoothService mBluetoothService; @@ -125,6 +127,8 @@ final class BluetoothAdapterStateMachine extends StateMachine { // timeout value waiting for all the devices to be disconnected private static final int DEVICES_DISCONNECT_TIMEOUT_TIME = 3000; + private static final int PREPARE_BLUETOOTH_TIMEOUT_TIME = 7000; + BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService, BluetoothAdapter bluetoothAdapter) { super(TAG); @@ -165,7 +169,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { } @Override public boolean processMessage(Message message) { - if (DBG) log("PowerOff process message: " + message.what); + log("PowerOff process message: " + message.what); boolean retValue = HANDLED; switch(message.what) { @@ -257,6 +261,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { try { Thread.sleep(100); } catch (InterruptedException e) { + log("prepareBluetooth sleep interrupted: " + pollCount); break; } } @@ -274,6 +279,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { return false; } + sendMessageDelayed(PREPARE_BLUETOOTH_TIMEOUT, PREPARE_BLUETOOTH_TIMEOUT_TIME); return true; } } @@ -291,13 +297,20 @@ final class BluetoothAdapterStateMachine extends StateMachine { @Override public boolean processMessage(Message message) { - if (DBG) log("WarmUp process message: " + message.what); + log("WarmUp process message: " + message.what); boolean retValue = HANDLED; switch(message.what) { case SERVICE_RECORD_LOADED: + removeMessages(PREPARE_BLUETOOTH_TIMEOUT); transitionTo(mHotOff); break; + case PREPARE_BLUETOOTH_TIMEOUT: + Log.e(TAG, "Bluetooth adapter SDP failed to load"); + shutoffBluetooth(); + transitionTo(mPowerOff); + broadcastState(BluetoothAdapter.STATE_OFF); + break; case USER_TURN_ON: // handle this at HotOff state case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth // on to the BluetoothOn state @@ -331,7 +344,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { @Override public boolean processMessage(Message message) { - if (DBG) log("HotOff process message: " + message.what); + log("HotOff process message: " + message.what); boolean retValue = HANDLED; switch(message.what) { @@ -348,8 +361,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { break; case AIRPLANE_MODE_ON: case TURN_COLD: - mBluetoothService.shutoffBluetooth(); - mEventLoop.stop(); + shutoffBluetooth(); transitionTo(mPowerOff); broadcastState(BluetoothAdapter.STATE_OFF); break; @@ -390,32 +402,32 @@ final class BluetoothAdapterStateMachine extends StateMachine { } @Override public boolean processMessage(Message message) { - if (DBG) log("Switching process message: " + message.what); + log("Switching process message: " + message.what); boolean retValue = HANDLED; switch(message.what) { - case BECAME_PAIRABLE: - mBluetoothService.initBluetoothAfterTurningOn(); - transitionTo(mBluetoothOn); - broadcastState(BluetoothAdapter.STATE_ON); - // run bluetooth now that it's turned on - // Note runBluetooth should be called only in adapter STATE_ON - mBluetoothService.runBluetooth(); + case SCAN_MODE_CHANGED: + // This event matches mBluetoothService.switchConnectable action + if (mPublicState == BluetoothAdapter.STATE_TURNING_ON) { + // set pairable if it's not + mBluetoothService.setPairable(); + mBluetoothService.initBluetoothAfterTurningOn(); + transitionTo(mBluetoothOn); + broadcastState(BluetoothAdapter.STATE_ON); + // run bluetooth now that it's turned on + // Note runBluetooth should be called only in adapter STATE_ON + mBluetoothService.runBluetooth(); + } break; - case BECAME_NON_PAIRABLE: - if (mBluetoothService.getAdapterConnectionState() == - BluetoothAdapter.STATE_DISCONNECTED) { - removeMessages(DEVICES_DISCONNECT_TIMEOUT); + case POWER_STATE_CHANGED: + if (!((Boolean) message.obj)) { transitionTo(mHotOff); finishSwitchingOff(); } break; case ALL_DEVICES_DISCONNECTED: removeMessages(DEVICES_DISCONNECT_TIMEOUT); - if (mBluetoothService.getScanMode() == BluetoothAdapter.SCAN_MODE_NONE) { - transitionTo(mHotOff); - finishSwitchingOff(); - } + mBluetoothService.switchConnectable(false); break; case DEVICES_DISCONNECT_TIMEOUT: sendMessage(ALL_DEVICES_DISCONNECTED); @@ -461,7 +473,7 @@ final class BluetoothAdapterStateMachine extends StateMachine { } @Override public boolean processMessage(Message message) { - if (DBG) log("BluetoothOn process message: " + message.what); + log("BluetoothOn process message: " + message.what); boolean retValue = HANDLED; switch(message.what) { @@ -482,9 +494,14 @@ final class BluetoothAdapterStateMachine extends StateMachine { case AIRPLANE_MODE_ON: transitionTo(mSwitching); broadcastState(BluetoothAdapter.STATE_TURNING_OFF); - mBluetoothService.switchConnectable(false); - mBluetoothService.disconnectDevices(); - sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT, DEVICES_DISCONNECT_TIMEOUT_TIME); + if (mBluetoothService.getAdapterConnectionState() != + BluetoothAdapter.STATE_DISCONNECTED) { + mBluetoothService.disconnectDevices(); + sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT, + DEVICES_DISCONNECT_TIMEOUT_TIME); + } else { + mBluetoothService.switchConnectable(false); + } // we turn all the way to PowerOff with AIRPLANE_MODE_ON if (message.what == AIRPLANE_MODE_ON) { @@ -514,15 +531,25 @@ final class BluetoothAdapterStateMachine extends StateMachine { private class PerProcessState extends State { IBluetoothStateChangeCallback mCallback = null; + boolean isTurningOn = false; @Override public void enter() { - if (DBG) log("Enter PerProcessState: " + getCurrentMessage().what); + int what = getCurrentMessage().what; + if (DBG) log("Enter PerProcessState: " + what); + + if (what == PER_PROCESS_TURN_ON) { + isTurningOn = true; + } else if (what == PER_PROCESS_TURN_OFF) { + isTurningOn = false; + } else { + Log.e(TAG, "enter PerProcessState: wrong msg: " + what); + } } @Override public boolean processMessage(Message message) { - if (DBG) log("PerProcessState process message: " + message.what); + log("PerProcessState process message: " + message.what); boolean retValue = HANDLED; switch (message.what) { @@ -534,8 +561,20 @@ final class BluetoothAdapterStateMachine extends StateMachine { perProcessCallback(true, mCallback); } break; - case BECAME_PAIRABLE: - perProcessCallback(true, mCallback); + case SCAN_MODE_CHANGED: + if (isTurningOn) { + perProcessCallback(true, mCallback); + isTurningOn = false; + } + break; + case POWER_STATE_CHANGED: + if (!((Boolean) message.obj)) { + transitionTo(mHotOff); + if (!mContext.getResources().getBoolean + (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { + deferMessage(obtainMessage(TURN_COLD)); + } + } break; case USER_TURN_ON: broadcastState(BluetoothAdapter.STATE_TURNING_ON); @@ -579,13 +618,6 @@ final class BluetoothAdapterStateMachine extends StateMachine { mBluetoothService.switchConnectable(false); } break; - case BECAME_NON_PAIRABLE: - transitionTo(mHotOff); - if (!mContext.getResources().getBoolean - (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) { - deferMessage(obtainMessage(TURN_COLD)); - } - break; case AIRPLANE_MODE_ON: mBluetoothService.switchConnectable(false); allProcessesCallback(false); @@ -602,6 +634,11 @@ final class BluetoothAdapterStateMachine extends StateMachine { } } + private void shutoffBluetooth() { + mBluetoothService.shutoffBluetooth(); + mEventLoop.stop(); + mBluetoothService.cleanNativeAfterShutoffBluetooth(); + } private void perProcessCallback(boolean on, IBluetoothStateChangeCallback c) { if (c == null) return; diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index d73f8c9..6eff796 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -322,6 +322,12 @@ class BluetoothEventLoop { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, BLUETOOTH_PERM); } else if (name.equals("Pairable") || name.equals("Discoverable")) { + adapterProperties.setProperty(name, propValues[1]); + + if (name.equals("Discoverable")) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED); + } + String pairable = name.equals("Pairable") ? propValues[1] : adapterProperties.getProperty("Pairable"); String discoverable = name.equals("Discoverable") ? propValues[1] : @@ -331,16 +337,6 @@ class BluetoothEventLoop { if (pairable == null || discoverable == null) return; - adapterProperties.setProperty(name, propValues[1]); - - if (name.equals("Pairable")) { - if (pairable.equals("true")) { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.BECAME_PAIRABLE); - } else { - mBluetoothState.sendMessage(BluetoothAdapterStateMachine.BECAME_NON_PAIRABLE); - } - } - int mode = BluetoothService.bluezStringToScanMode( pairable.equals("true"), discoverable.equals("true")); @@ -377,10 +373,11 @@ class BluetoothEventLoop { mBluetoothService.updateBluetoothState(value); } } else if (name.equals("Powered")) { + mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED, + propValues[1].equals("true") ? new Boolean(true) : new Boolean(false)); // bluetoothd has restarted, re-read all our properties. // Note: bluez only sends this property change when it restarts. - if (propValues[1].equals("true")) - onRestartRequired(); + onRestartRequired(); } else if (name.equals("DiscoverableTimeout")) { adapterProperties.setProperty(name, propValues[1]); } diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 0357958..ee14673 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -437,7 +437,21 @@ public class BluetoothService extends IBluetooth.Stub { * power off Bluetooth */ synchronized void shutoffBluetooth() { + if (mAdapterSdpHandles != null) removeReservedServiceRecordsNative(mAdapterSdpHandles); + setBluetoothTetheringNative(false, BluetoothPanProfileHandler.NAP_ROLE, + BluetoothPanProfileHandler.NAP_BRIDGE); tearDownNativeDataNative(); + } + + /** + * Data clean up after Bluetooth shutoff + */ + synchronized void cleanNativeAfterShutoffBluetooth() { + // Ths method is called after shutdown of event loop in the Bluetooth shut down + // procedure + + // the adapter property could be changed before event loop is stoped, clear it again + mAdapterProperties.clear(); disableNative(); if (mRestart) { mRestart = false; @@ -743,26 +757,6 @@ public class BluetoothService extends IBluetooth.Stub { public synchronized boolean setScanMode(int mode, int duration) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, "Need WRITE_SECURE_SETTINGS permission"); - return setScanMode(mode, duration, true); - } - - /** - * @param on true set the local Bluetooth module to be connectable - * but not dicoverable - * false set the local Bluetooth module to be not connectable - * and not dicoverable - */ - /*package*/ synchronized void switchConnectable(boolean on) { - if (on) { - // 0 is a dummy value, does not apply for SCAN_MODE_CONNECTABLE - setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, 0, false); - } else { - // 0 is a dummy value, does not apply for SCAN_MODE_NONE - setScanMode(BluetoothAdapter.SCAN_MODE_NONE, 0, false); - } - } - - private synchronized boolean setScanMode(int mode, int duration, boolean allowOnlyInOnState) { boolean pairable; boolean discoverable; @@ -785,19 +779,33 @@ public class BluetoothService extends IBluetooth.Stub { return false; } - if (allowOnlyInOnState) { - setPropertyBoolean("Discoverable", discoverable); - setPropertyBoolean("Pairable", pairable); - } else { - // allowed to set the property through native layer directly - setAdapterPropertyBooleanNative("Discoverable", discoverable ? 1 : 0); - // do setting pairable after setting discoverable since the adapter - // state machine uses pairable event for state change - setAdapterPropertyBooleanNative("Pairable", pairable ? 1 : 0); - } + setPropertyBoolean("Discoverable", discoverable); + setPropertyBoolean("Pairable", pairable); return true; } + /** + * @param on true set the local Bluetooth module to be connectable + * The dicoverability is recovered to what it was before + * switchConnectable(false) call + * false set the local Bluetooth module to be not connectable + * and not dicoverable + */ + /*package*/ synchronized void switchConnectable(boolean on) { + setAdapterPropertyBooleanNative("Powered", on ? 1 : 0); + } + + /*package*/ synchronized void setPairable() { + String pairableString = getProperty("Pairable", false); + if (pairableString == null) { + Log.e(TAG, "null pairableString"); + return; + } + if (pairableString.equals("false")) { + setAdapterPropertyBooleanNative("Pairable", 1); + } + } + /*package*/ synchronized String getProperty(String name, boolean checkState) { // If checkState is false, check if the event loop is running. // before making the call to Bluez diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 3e5f32e..376e4f5 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -16,8 +16,6 @@ package android.text; -import com.android.internal.util.ArrayUtils; - import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; @@ -30,6 +28,8 @@ import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Log; +import com.android.internal.util.ArrayUtils; + /** * Represents a line of styled text, for measuring in visual order and * for rendering. @@ -720,7 +720,7 @@ class TextLine { float ret = 0; int contextLen = contextEnd - contextStart; - if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) { + if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor !=0 || runIsRtl))) { int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; if (mCharsValid) { ret = wp.getTextRunAdvances(mChars, start, runLen, @@ -739,15 +739,32 @@ class TextLine { } if (wp.bgColor != 0) { - int color = wp.getColor(); - Paint.Style s = wp.getStyle(); + int previousColor = wp.getColor(); + Paint.Style previousStyle = wp.getStyle(); + wp.setColor(wp.bgColor); wp.setStyle(Paint.Style.FILL); - c.drawRect(x, top, x + ret, bottom, wp); - wp.setStyle(s); - wp.setColor(color); + wp.setStyle(previousStyle); + wp.setColor(previousColor); + } + + if (wp.underlineColor != 0) { + // kStdUnderline_Offset = 1/9, defined in SkTextFormatParams.h + float middle = y + wp.baselineShift + (1.0f / 9.0f) * wp.getTextSize(); + // kStdUnderline_Thickness = 1/18, defined in SkTextFormatParams.h + float halfHeight = wp.underlineThickness * (1.0f / 18.0f / 2.0f) * wp.getTextSize(); + + int previousColor = wp.getColor(); + Paint.Style previousStyle = wp.getStyle(); + + wp.setColor(wp.underlineColor); + wp.setStyle(Paint.Style.FILL); + c.drawRect(x, middle - halfHeight, x + ret, middle + halfHeight, wp); + + wp.setStyle(previousStyle); + wp.setColor(previousColor); } drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl, diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java index f9e7cac..de57dfa 100644 --- a/core/java/android/text/TextPaint.java +++ b/core/java/android/text/TextPaint.java @@ -23,11 +23,22 @@ import android.graphics.Paint; * data used during text measuring and drawing. */ public class TextPaint extends Paint { + // Special value 0 means no background paint public int bgColor; public int baselineShift; public int linkColor; public int[] drawableState; public float density = 1.0f; + /** + * Special value 0 means no custom underline + * @hide + */ + public int underlineColor; + /** + * Defined as a multiplier of the default underline thickness. Use 1.0f for default thickness. + * @hide + */ + public float underlineThickness; public TextPaint() { super(); @@ -53,5 +64,24 @@ public class TextPaint extends Paint { linkColor = tp.linkColor; drawableState = tp.drawableState; density = tp.density; + underlineColor = tp.underlineColor; + underlineThickness = tp.underlineThickness; + } + + /** + * Defines a custom underline for this Paint. + * @param color underline solid color + * @param thickness underline thickness, defined as a multiplier of the default underline + * thickness. + * @hide + */ + public void setUnderlineText(boolean isUnderlined, int color, float thickness) { + setUnderlineText(false); + if (isUnderlined) { + underlineColor = color; + underlineThickness = thickness; + } else { + underlineColor = 0; + } } } diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java index ecedbe1..6c8bb39 100644 --- a/core/java/android/util/XmlPullAttributes.java +++ b/core/java/android/util/XmlPullAttributes.java @@ -143,5 +143,5 @@ class XmlPullAttributes implements AttributeSet { return getAttributeResourceValue(null, "style", 0); } - private XmlPullParser mParser; + /*package*/ XmlPullParser mParser; } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index d9efe0c..85e990a 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -366,9 +366,9 @@ public class Display { // Following fields are initialized from native code private int mPixelFormat; private float mRefreshRate; - private float mDensity; - private float mDpiX; - private float mDpiY; + /*package*/ float mDensity; + /*package*/ float mDpiX; + /*package*/ float mDpiY; private final Point mTmpPoint = new Point(); private final DisplayMetrics mTmpMetrics = new DisplayMetrics(); diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index a7fe95d..e586370 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -509,13 +509,6 @@ class GLES20Canvas extends HardwareCanvas { private static native void nSetMatrix(int renderer, int matrix); @Override - public int getNativeMatrix() { - return nGetMatrix(mRenderer); - } - - private static native int nGetMatrix(int renderer); - - @Override public void getMatrix(Matrix matrix) { nGetMatrix(mRenderer, matrix.native_instance); } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index f2b6b1f..f2c3131 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -199,8 +199,10 @@ public abstract class HardwareRenderer { * @param attachInfo AttachInfo tied to the specified view. * @param callbacks Callbacks invoked when drawing happens. * @param dirty The dirty rectangle to update, can be null. + * + * @return true if the dirty rect was ignored, false otherwise */ - abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, + abstract boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty); /** @@ -757,7 +759,7 @@ public abstract class HardwareRenderer { } @Override - void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, + boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { if (canDraw()) { if (!hasDirtyRegions()) { @@ -825,8 +827,12 @@ public abstract class HardwareRenderer { sEgl.eglSwapBuffers(sEglDisplay, mEglSurface); checkEglErrors(); + + return dirty == null; } } + + return false; } /** diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 332a0fa..9628d6b 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -75,9 +75,9 @@ public abstract class LayoutInflater { private Factory2 mPrivateFactory; private Filter mFilter; - private final Object[] mConstructorArgs = new Object[2]; + final Object[] mConstructorArgs = new Object[2]; - private static final Class<?>[] mConstructorSignature = new Class[] { + static final Class<?>[] mConstructorSignature = new Class[] { Context.class, AttributeSet.class}; private static final HashMap<String, Constructor<? extends View>> sConstructorMap = @@ -705,7 +705,7 @@ public abstract class LayoutInflater { * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). */ - private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, + void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 5b77cf7..54bd637 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6249,15 +6249,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } // Walk up the hierarchy to determine if we're inside a scrolling container. - boolean isInScrollingContainer = false; - ViewParent p = getParent(); - while (p != null && p instanceof ViewGroup) { - if (((ViewGroup) p).shouldDelayChildPressedState()) { - isInScrollingContainer = true; - break; - } - p = p.getParent(); - } + boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. @@ -6307,6 +6299,20 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * @hide + */ + public boolean isInScrollingContainer() { + ViewParent p = getParent(); + while (p != null && p instanceof ViewGroup) { + if (((ViewGroup) p).shouldDelayChildPressedState()) { + return true; + } + p = p.getParent(); + } + return false; + } + + /** * Remove the longpress detection timer. */ private void removeLongPressCallback() { @@ -8774,6 +8780,12 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_OVERLAY, to = "INSIDE_OVERLAY"), + @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_INSET, to = "INSIDE_INSET"), + @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_OVERLAY, to = "OUTSIDE_OVERLAY"), + @ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_INSET, to = "OUTSIDE_INSET") + }) public int getScrollBarStyle() { return mViewFlags & SCROLLBARS_STYLE_MASK; } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 9520958..5e104f9 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -225,7 +225,7 @@ public class ViewConfiguration { private boolean sHasPermanentMenuKey; private boolean sHasPermanentMenuKeySet; - private static final SparseArray<ViewConfiguration> sConfigurations = + static final SparseArray<ViewConfiguration> sConfigurations = new SparseArray<ViewConfiguration>(2); /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 380fc15..f23c366 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1798,7 +1798,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, currentDirty = null; } - mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, currentDirty); + if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, currentDirty)) { + mPreviousDirty.set(0, 0, mWidth, mHeight); + } } if (animating) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 7620a63..2bb9da1 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -537,7 +537,10 @@ public class WebView extends AbsoluteLayout // This should be ViewConfiguration.getTapTimeout() // But system time out is 100ms, which is too short for the browser. // In the browser, if it switches out of tap too soon, jump tap won't work. - private static final int TAP_TIMEOUT = 200; + // In addition, a double tap on a trackpad will always have a duration of + // 300ms, so this value must be at least that (otherwise we will timeout the + // first tap and convert it to a long press). + private static final int TAP_TIMEOUT = 300; // This should be ViewConfiguration.getLongPressTimeout() // But system time out is 500ms, which is too short for the browser. // With a short timeout, it's difficult to treat trigger a short press. diff --git a/core/java/android/webkit/webdriver/By.java b/core/java/android/webkit/webdriver/By.java deleted file mode 100644 index fa4fe74..0000000 --- a/core/java/android/webkit/webdriver/By.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 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.webkit.webdriver; - -import java.util.List; - -/** - * Mechanism to locate elements within the DOM of the page. - * @hide - */ -public abstract class By { - public abstract WebElement findElement(WebElement element); - public abstract List<WebElement> findElements(WebElement element); - - /** - * Locates an element by its HTML id attribute. - * - * @param id The HTML id attribute to look for. - * @return A By instance that locates elements by their HTML id attributes. - */ - public static By id(final String id) { - throwIfNull(id); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementById(id); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsById(id); // Yes, it happens a lot. - } - - @Override - public String toString() { - return "By.id: " + id; - } - }; - } - - /** - * Locates an element by the matching the exact text on the HTML link. - * - * @param linkText The exact text to match against. - * @return A By instance that locates elements by the text displayed by - * the link. - */ - public static By linkText(final String linkText) { - throwIfNull(linkText); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByLinkText(linkText); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByLinkText(linkText); - } - - @Override - public String toString() { - return "By.linkText: " + linkText; - } - }; - } - - /** - * Locates an element by matching partial part of the text displayed by an - * HTML link. - * - * @param linkText The text that should be contained by the text displayed - * on the link. - * @return A By instance that locates elements that contain the given link - * text. - */ - public static By partialLinkText(final String linkText) { - throwIfNull(linkText); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByPartialLinkText(linkText); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByPartialLinkText(linkText); - } - - @Override - public String toString() { - return "By.partialLinkText: " + linkText; - } - }; - } - - /** - * Locates an element by matching its HTML name attribute. - * - * @param name The value of the HTML name attribute. - * @return A By instance that locates elements by the HTML name attribute. - */ - public static By name(final String name) { - throwIfNull(name); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByName(name); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByName(name); - } - - @Override - public String toString() { - return "By.name: " + name; - } - }; - } - - /** - * Locates an element by matching its class name. - * @param className The class name - * @return A By instance that locates elements by their class name attribute. - */ - public static By className(final String className) { - throwIfNull(className); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByClassName(className); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByClassName(className); - } - - @Override - public String toString() { - return "By.className: " + className; - } - }; - } - - /** - * Locates an element by matching its css property. - * - * @param css The css property. - * @return A By instance that locates elements by their css property. - */ - public static By css(final String css) { - throwIfNull(css); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByCss(css); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByCss(css); - } - - @Override - public String toString() { - return "By.css: " + css; - } - }; - } - - /** - * Locates an element by matching its HTML tag name. - * - * @param tagName The HTML tag name to look for. - * @return A By instance that locates elements using the name of the - * HTML tag. - */ - public static By tagName(final String tagName) { - throwIfNull(tagName); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByTagName(tagName); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByTagName(tagName); - } - - @Override - public String toString() { - return "By.tagName: " + tagName; - } - }; - } - - /** - * Locates an element using an XPath expression. - * - * <p>When using XPath, be aware that this follows standard conventions: a - * search prefixed with "//" will search the entire document, not just the - * children of the current node. Use ".//" to limit your search to the - * children of this {@link android.webkit.webdriver.WebElement}. - * - * @param xpath The XPath expression to use. - * @return A By instance that locates elements using the given XPath. - */ - public static By xpath(final String xpath) { - throwIfNull(xpath); - return new By() { - @Override - public WebElement findElement(WebElement element) { - return element.findElementByXPath(xpath); - } - - @Override - public List<WebElement> findElements(WebElement element) { - return element.findElementsByXPath(xpath); - } - - @Override - public String toString() { - return "By.xpath: " + xpath; - } - }; - } - - private static void throwIfNull(String argument) { - if (argument == null) { - throw new IllegalArgumentException( - "Cannot find elements with null locator."); - } - } -} diff --git a/core/java/android/webkit/webdriver/WebDriver.java b/core/java/android/webkit/webdriver/WebDriver.java deleted file mode 100644 index 79e6523..0000000 --- a/core/java/android/webkit/webdriver/WebDriver.java +++ /dev/null @@ -1,843 +0,0 @@ -/* - * 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.webkit.webdriver; - -import android.graphics.Point; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.view.InputDevice; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.webkit.WebView; -import android.webkit.WebViewCore; - -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; - -import com.android.internal.R; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Drives a web application by controlling the WebView. This class - * provides a DOM-like API allowing to get information about the page, - * navigate, and interact with the web application. This is particularly useful - * for testing a web application. - * - * <p/>{@link android.webkit.webdriver.WebDriver} should be created in the main - * thread, and invoked from another thread. Here is a sample usage: - * - * public class WebDriverStubActivity extends Activity { - * private WebDriver mDriver; - * - * public void onCreate(Bundle savedInstanceState) { - * super.onCreate(savedInstanceState); - * WebView view = new WebView(this); - * mDriver = new WebDriver(view); - * setContentView(view); - * } - * - * - * public WebDriver getDriver() { - * return mDriver; - * } - *} - * - * public class WebDriverTest extends - * ActivityInstrumentationTestCase2<WebDriverStubActivity>{ - * private WebDriver mDriver; - * - * public WebDriverTest() { - * super(WebDriverStubActivity.class); - * } - * - * protected void setUp() throws Exception { - * super.setUp(); - * mDriver = getActivity().getDriver(); - * } - * - * public void testGoogle() { - * mDriver.get("http://google.com"); - * WebElement searchBox = mDriver.findElement(By.name("q")); - * q.sendKeys("Cheese!"); - * q.submit(); - * assertTrue(mDriver.findElements(By.partialLinkText("Cheese")).size() > 0); - * } - *} - * - * @hide - */ -public class WebDriver { - // Timeout for page load in milliseconds. - private static final int LOADING_TIMEOUT = 30000; - // Timeout for executing JavaScript in the WebView in milliseconds. - private static final int JS_EXECUTION_TIMEOUT = 10000; - // Timeout for the MotionEvent to be completely handled - private static final int MOTION_EVENT_TIMEOUT = 1000; - // Timeout for detecting a new page load - private static final int PAGE_STARTED_LOADING = 500; - // Timeout for handling KeyEvents - private static final int KEY_EVENT_TIMEOUT = 2000; - - // Commands posted to the handler - private static final int CMD_GET_URL = 1; - private static final int CMD_EXECUTE_SCRIPT = 2; - private static final int CMD_SEND_TOUCH = 3; - private static final int CMD_SEND_KEYS = 4; - private static final int CMD_NAV_REFRESH = 5; - private static final int CMD_NAV_BACK = 6; - private static final int CMD_NAV_FORWARD = 7; - private static final int CMD_SEND_KEYCODE = 8; - private static final int CMD_MOVE_CURSOR_RIGHTMOST_POS = 9; - private static final int CMD_MESSAGE_RELAY_ECHO = 10; - - private static final String ELEMENT_KEY = "ELEMENT"; - private static final String STATUS = "status"; - private static final String VALUE = "value"; - - private static final long MAIN_THREAD = Thread.currentThread().getId(); - - // This is updated by a callabck from JavaScript when the result is ready. - private String mJsResult; - - // Used for synchronization - private final Object mSyncObject; - private final Object mSyncPageLoad; - - // Updated when the command is done executing in the main thread. - private volatile boolean mCommandDone; - // Used by WebViewClientWrapper.onPageStarted() to notify that - // a page started loading. - private volatile boolean mPageStartedLoading; - // Used by WebChromeClientWrapper.onProgressChanged to notify when - // a page finished loading. - private volatile boolean mPageFinishedLoading; - private WebView mWebView; - private Navigation mNavigation; - // This WebElement represents the object document.documentElement - private WebElement mDocumentElement; - - - // This Handler runs in the main UI thread. - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case CMD_GET_URL: - final String url = (String) msg.obj; - mWebView.loadUrl(url); - break; - case CMD_EXECUTE_SCRIPT: - mWebView.loadUrl("javascript:" + (String) msg.obj); - break; - case CMD_MESSAGE_RELAY_ECHO: - notifyCommandDone(); - break; - case CMD_SEND_TOUCH: - touchScreen((Point) msg.obj); - notifyCommandDone(); - break; - case CMD_SEND_KEYS: - dispatchKeys((CharSequence[]) msg.obj); - notifyCommandDone(); - break; - case CMD_NAV_REFRESH: - mWebView.reload(); - break; - case CMD_NAV_BACK: - mWebView.goBack(); - break; - case CMD_NAV_FORWARD: - mWebView.goForward(); - break; - case CMD_SEND_KEYCODE: - dispatchKeyCodes((int[]) msg.obj); - notifyCommandDone(); - break; - case CMD_MOVE_CURSOR_RIGHTMOST_POS: - moveCursorToLeftMostPos((String) msg.obj); - notifyCommandDone(); - break; - } - } - }; - - /** - * Error codes from the WebDriver wire protocol - * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes - */ - private enum ErrorCode { - SUCCESS(0), - NO_SUCH_ELEMENT(7), - NO_SUCH_FRAME(8), - UNKNOWN_COMMAND(9), - UNSUPPORTED_OPERATION(9), // Alias - STALE_ELEMENT_REFERENCE(10), - ELEMENT_NOT_VISISBLE(11), - INVALID_ELEMENT_STATE(12), - UNKNOWN_ERROR(13), - ELEMENT_NOT_SELECTABLE(15), - XPATH_LOOKUP_ERROR(19), - NO_SUCH_WINDOW(23), - INVALID_COOKIE_DOMAIN(24), - UNABLE_TO_SET_COOKIE(25), - MODAL_DIALOG_OPENED(26), - MODAL_DIALOG_OPEN(27), - SCRIPT_TIMEOUT(28); - - private final int mCode; - private static ErrorCode[] values = ErrorCode.values(); - - ErrorCode(int code) { - this.mCode = code; - } - - public int getCode() { - return mCode; - } - - public static ErrorCode get(final int intValue) { - for (int i = 0; i < values.length; i++) { - if (values[i].getCode() == intValue) { - return values[i]; - } - } - return UNKNOWN_ERROR; - } - } - - public WebDriver(WebView webview) { - this.mWebView = webview; - mWebView.requestFocus(); - if (mWebView == null) { - throw new IllegalArgumentException("WebView cannot be null"); - } - if (!mWebView.getSettings().getJavaScriptEnabled()) { - throw new RuntimeException("Javascript is disabled in the WebView. " - + "Enable it to use WebDriver"); - } - shouldRunInMainThread(true); - - mSyncObject = new Object(); - mSyncPageLoad = new Object(); - this.mWebView = webview; - WebChromeClientWrapper chromeWrapper = new WebChromeClientWrapper( - webview.getWebChromeClient(), this); - mWebView.setWebChromeClient(chromeWrapper); - WebViewClientWrapper viewWrapper = new WebViewClientWrapper( - webview.getWebViewClient(), this); - mWebView.setWebViewClient(viewWrapper); - mWebView.addJavascriptInterface(new JavascriptResultReady(), - "webdriver"); - mDocumentElement = new WebElement(this, ""); - mNavigation = new Navigation(); - } - - /** - * @return The title of the current page, null if not set. - */ - public String getTitle() { - return mWebView.getTitle(); - } - - /** - * Loads a URL in the WebView. This function is blocking and will return - * when the page has finished loading. - * - * @param url The URL to load. - */ - public void get(String url) { - mNavigation.to(url); - } - - /** - * @return The source page of the currently loaded page in WebView. - */ - public String getPageSource() { - return (String) executeScript("return new XMLSerializer()." - + "serializeToString(document);"); - } - - /** - * Find the first {@link android.webkit.webdriver.WebElement} using the - * given method. - * - * @param by The locating mechanism to use. - * @return The first matching element on the current context. - * @throws {@link android.webkit.webdriver.WebElementNotFoundException} if - * no matching element was found. - */ - public WebElement findElement(By by) { - checkNotNull(mDocumentElement, "Load a page using WebDriver.get() " - + "before looking for elements."); - return by.findElement(mDocumentElement); - } - - /** - * Finds all {@link android.webkit.webdriver.WebElement} within the page - * using the given method. - * - * @param by The locating mechanism to use. - * @return A list of all {@link android.webkit.webdriver.WebElement} found, - * or an empty list if nothing matches. - */ - public List<WebElement> findElements(By by) { - checkNotNull(mDocumentElement, "Load a page using WebDriver.get() " - + "before looking for elements."); - return by.findElements(mDocumentElement); - } - - /** - * Clears the WebView's state and closes associated views. - */ - public void quit() { - mWebView.clearCache(true); - mWebView.clearFormData(); - mWebView.clearHistory(); - mWebView.clearSslPreferences(); - mWebView.clearView(); - mWebView.removeAllViewsInLayout(); - } - - /** - * Executes javascript in the context of the main frame. - * - * If the script has a return value the following happens: - * <ul> - * <li>For an HTML element, this method returns a WebElement</li> - * <li>For a decimal, a Double is returned</li> - * <li>For non-decimal number, a Long is returned</li> - * <li>For a boolean, a Boolean is returned</li> - * <li>For all other cases, a String is returned</li> - * <li>For an array, this returns a List<Object> with each object - * following the rules above.</li> - * <li>For an object literal this returns a Map<String, Object>. Note that - * Object literals keys can only be Strings. Non Strings keys will - * be filtered out.</li> - * </ul> - * - * <p> Arguments must be a number, a boolean, a string a WebElement or - * a list of any combination of the above. The arguments will be made - * available to the javascript via the "arguments" magic variable, - * as if the function was called via "Function.apply". - * - * @param script The JavaScript to execute. - * @param args The arguments to the script. Can be any of a number, boolean, - * string, WebElement or a List of those. - * @return A Boolean, Long, Double, String, WebElement, List or null. - */ - public Object executeScript(final String script, final Object... args) { - String scriptArgs = "[" + convertToJsArgs(args) + "]"; - String injectScriptJs = getResourceAsString(R.raw.execute_script_android); - return executeRawJavascript("(" + injectScriptJs + - ")(" + escapeAndQuote(script) + ", " + scriptArgs + ", true)"); - } - - public Navigation navigate() { - return mNavigation; - } - - - /** - * @hide - */ - public class Navigation { - /* package */ Navigation () {} - - public void back() { - navigate(CMD_NAV_BACK, null); - } - - public void forward() { - navigate(CMD_NAV_FORWARD, null); - } - - public void to(String url) { - navigate(CMD_GET_URL, url); - } - - public void refresh() { - navigate(CMD_NAV_REFRESH, null); - } - - private void navigate(int command, String url) { - synchronized (mSyncPageLoad) { - mPageFinishedLoading = false; - Message msg = mHandler.obtainMessage(command); - msg.obj = url; - mHandler.sendMessage(msg); - waitForPageLoad(); - } - } - } - - /** - * Converts the arguments passed to a JavaScript friendly format. - * - * @param args The arguments to convert. - * @return Comma separated Strings containing the arguments. - */ - /* package */ String convertToJsArgs(final Object... args) { - StringBuilder toReturn = new StringBuilder(); - int length = args.length; - for (int i = 0; i < length; i++) { - toReturn.append((i > 0) ? "," : ""); - if (args[i] instanceof List<?>) { - toReturn.append("["); - List<Object> aList = (List<Object>) args[i]; - for (int j = 0 ; j < aList.size(); j++) { - String comma = ((j == 0) ? "" : ","); - toReturn.append(comma + convertToJsArgs(aList.get(j))); - } - toReturn.append("]"); - } else if (args[i] instanceof Map<?, ?>) { - Map<Object, Object> aMap = (Map<Object, Object>) args[i]; - String toAdd = "{"; - for (Object key: aMap.keySet()) { - toAdd += key + ":" - + convertToJsArgs(aMap.get(key)) + ","; - } - toReturn.append(toAdd.substring(0, toAdd.length() -1) + "}"); - } else if (args[i] instanceof WebElement) { - // WebElement are represented in JavaScript by Objects as - // follow: {ELEMENT:"id"} where "id" refers to the id - // of the HTML element in the javascript cache that can - // be accessed throught bot.inject.cache.getCache_() - toReturn.append("{\"" + ELEMENT_KEY + "\":\"" - + ((WebElement) args[i]).getId() + "\"}"); - } else if (args[i] instanceof Number || args[i] instanceof Boolean) { - toReturn.append(String.valueOf(args[i])); - } else if (args[i] instanceof String) { - toReturn.append(escapeAndQuote((String) args[i])); - } else { - throw new IllegalArgumentException( - "Javascript arguments can be " - + "a Number, a Boolean, a String, a WebElement, " - + "or a List or a Map of those. Got: " - + ((args[i] == null) ? "null" : args[i].getClass() - + ", value: " + args[i].toString())); - } - } - return toReturn.toString(); - } - - /* package */ Object executeRawJavascript(final String script) { - if (mWebView.getUrl() == null) { - throw new WebDriverException("Cannot operate on a blank page. " - + "Load a page using WebDriver.get()."); - } - String result = executeCommand(CMD_EXECUTE_SCRIPT, - "if (!window.webdriver || !window.webdriver.resultReady) {" + - " return;" + - "}" + - "window.webdriver.resultReady(" + script + ")", - JS_EXECUTION_TIMEOUT); - if (result == null || "undefined".equals(result)) { - return null; - } - try { - JSONObject json = new JSONObject(result); - throwIfError(json); - Object value = json.get(VALUE); - return convertJsonToJavaObject(value); - } catch (JSONException e) { - throw new RuntimeException("Failed to parse JavaScript result: " - + result.toString(), e); - } - } - - /* package */ String getResourceAsString(final int resourceId) { - InputStream is = mWebView.getResources().openRawResource(resourceId); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - try { - while ((line = br.readLine()) != null) { - sb.append(line); - } - br.close(); - is.close(); - } catch (IOException e) { - throw new RuntimeException("Failed to open JavaScript resource.", e); - } - return sb.toString(); - } - - /* package */ void sendTouchScreen(Point coords) { - // Reset state - resetPageLoadState(); - executeCommand(CMD_SEND_TOUCH, coords,LOADING_TIMEOUT); - // Wait for the events to be fully handled - waitForMessageRelay(MOTION_EVENT_TIMEOUT); - - // If a page started loading, block until page finishes loading - waitForPageLoadIfNeeded(); - } - - /* package */ void resetPageLoadState() { - synchronized (mSyncPageLoad) { - mPageStartedLoading = false; - mPageFinishedLoading = false; - } - } - - /* package */ void waitForPageLoadIfNeeded() { - synchronized (mSyncPageLoad) { - Long end = System.currentTimeMillis() + PAGE_STARTED_LOADING; - // Wait PAGE_STARTED_LOADING milliseconds to see if we detect a - // page load. - while (!mPageStartedLoading && (System.currentTimeMillis() <= end)) { - try { - // This is notified by WebChromeClientWrapper#onProgressChanged - // when the page finished loading. - mSyncPageLoad.wait(PAGE_STARTED_LOADING); - } catch (InterruptedException e) { - new RuntimeException(e); - } - } - if (mPageStartedLoading) { - waitForPageLoad(); - } - } - } - - private void touchScreen(Point coords) { - // Convert to screen coords - // screen = JS x zoom - offset - float zoom = mWebView.getScale(); - float xOffset = mWebView.getX(); - float yOffset = mWebView.getY(); - Point screenCoords = new Point( (int)(coords.x*zoom - xOffset), - (int)(coords.y*zoom - yOffset)); - - long downTime = SystemClock.uptimeMillis(); - MotionEvent down = MotionEvent.obtain(downTime, - SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, screenCoords.x, - screenCoords.y, 0); - down.setSource(InputDevice.SOURCE_TOUCHSCREEN); - MotionEvent up = MotionEvent.obtain(downTime, - SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, screenCoords.x, - screenCoords.y, 0); - up.setSource(InputDevice.SOURCE_TOUCHSCREEN); - // Dispatch the events to WebView - mWebView.dispatchTouchEvent(down); - mWebView.dispatchTouchEvent(up); - } - - /* package */ void notifyPageStartedLoading() { - synchronized (mSyncPageLoad) { - mPageStartedLoading = true; - mSyncPageLoad.notify(); - } - } - - /* package */ void notifyPageFinishedLoading() { - synchronized (mSyncPageLoad) { - mPageFinishedLoading = true; - mSyncPageLoad.notify(); - } - } - - /** - * - * @param keys The first element of the CharSequence should be the - * existing value in the text input, or the empty string if none. - */ - /* package */ void sendKeys(CharSequence[] keys) { - executeCommand(CMD_SEND_KEYS, keys, KEY_EVENT_TIMEOUT); - // Wait for all KeyEvents to be handled - waitForMessageRelay(KEY_EVENT_TIMEOUT); - } - - /* package */ void sendKeyCodes(int[] keycodes) { - executeCommand(CMD_SEND_KEYCODE, keycodes, KEY_EVENT_TIMEOUT); - // Wait for all KeyEvents to be handled - waitForMessageRelay(KEY_EVENT_TIMEOUT); - } - - /* package */ void moveCursorToRightMostPosition(String value) { - executeCommand(CMD_MOVE_CURSOR_RIGHTMOST_POS, value, KEY_EVENT_TIMEOUT); - waitForMessageRelay(KEY_EVENT_TIMEOUT); - } - - private void moveCursorToLeftMostPos(String value) { - // If there is text, move the cursor to the rightmost position - if (value != null && !value.equals("")) { - long downTime = SystemClock.uptimeMillis(); - KeyEvent down = new KeyEvent(downTime, SystemClock.uptimeMillis(), - KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, 0); - KeyEvent up = new KeyEvent(downTime, SystemClock.uptimeMillis(), - KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_RIGHT, - value.length()); - mWebView.dispatchKeyEvent(down); - mWebView.dispatchKeyEvent(up); - } - } - - private void dispatchKeyCodes(int[] keycodes) { - for (int i = 0; i < keycodes.length; i++) { - KeyEvent down = new KeyEvent(KeyEvent.ACTION_DOWN, keycodes[i]); - KeyEvent up = new KeyEvent(KeyEvent.ACTION_UP, keycodes[i]); - mWebView.dispatchKeyEvent(down); - mWebView.dispatchKeyEvent(up); - } - } - - private void dispatchKeys(CharSequence[] keys) { - KeyCharacterMap chararcterMap = KeyCharacterMap.load( - KeyCharacterMap.VIRTUAL_KEYBOARD); - for (int i = 0; i < keys.length; i++) { - CharSequence s = keys[i]; - for (int j = 0; j < s.length(); j++) { - KeyEvent[] events = - chararcterMap.getEvents(new char[]{s.charAt(j)}); - for (KeyEvent e : events) { - mWebView.dispatchKeyEvent(e); - } - } - } - } - - private void waitForMessageRelay(long timeout) { - synchronized (mSyncObject) { - mCommandDone = false; - } - Message msg = Message.obtain(); - msg.what = WebViewCore.EventHub.MESSAGE_RELAY; - Message echo = mHandler.obtainMessage(CMD_MESSAGE_RELAY_ECHO); - msg.obj = echo; - - mWebView.getWebViewCore().sendMessage(msg); - synchronized (mSyncObject) { - long end = System.currentTimeMillis() + timeout; - while (!mCommandDone && (System.currentTimeMillis() <= end)) { - try { - // This is notifed by the mHandler when it receives the - // MESSAGE_RELAY back - mSyncObject.wait(timeout); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - } - - private void waitForPageLoad() { - long endLoad = System.currentTimeMillis() + LOADING_TIMEOUT; - while (!mPageFinishedLoading - && (System.currentTimeMillis() <= endLoad)) { - try { - mSyncPageLoad.wait(LOADING_TIMEOUT); - } catch (InterruptedException e) { - throw new RuntimeException(); - } - } - } - - /** - * Wraps the given string into quotes and escape existing quotes - * and backslashes. - * "foo" -> "\"foo\"" - * "foo\"" -> "\"foo\\\"\"" - * "fo\o" -> "\"fo\\o\"" - * - * @param toWrap The String to wrap in quotes - * @return a String wrapping the original String in quotes - */ - private static String escapeAndQuote(final String toWrap) { - StringBuilder toReturn = new StringBuilder("\""); - for (int i = 0; i < toWrap.length(); i++) { - char c = toWrap.charAt(i); - if (c == '\"') { - toReturn.append("\\\""); - } else if (c == '\\') { - toReturn.append("\\\\"); - } else { - toReturn.append(c); - } - } - toReturn.append("\""); - return toReturn.toString(); - } - - private Object convertJsonToJavaObject(final Object toConvert) { - try { - if (toConvert == null - || toConvert.equals(null) - || "undefined".equals(toConvert) - || "null".equals(toConvert)) { - return null; - } else if (toConvert instanceof Boolean) { - return toConvert; - } else if (toConvert instanceof Double - || toConvert instanceof Float) { - return Double.valueOf(String.valueOf(toConvert)); - } else if (toConvert instanceof Integer - || toConvert instanceof Long) { - return Long.valueOf(String.valueOf(toConvert)); - } else if (toConvert instanceof JSONArray) { // List - return convertJsonArrayToList((JSONArray) toConvert); - } else if (toConvert instanceof JSONObject) { // Map or WebElment - JSONObject map = (JSONObject) toConvert; - if (map.opt(ELEMENT_KEY) != null) { // WebElement - return new WebElement(this, (String) map.get(ELEMENT_KEY)); - } else { // Map - return convertJsonObjectToMap(map); - } - } else { - return toConvert.toString(); - } - } catch (JSONException e) { - throw new RuntimeException("Failed to parse JavaScript result: " - + toConvert.toString(), e); - } - } - - private List<Object> convertJsonArrayToList(final JSONArray json) { - List<Object> toReturn = Lists.newArrayList(); - for (int i = 0; i < json.length(); i++) { - try { - toReturn.add(convertJsonToJavaObject(json.get(i))); - } catch (JSONException e) { - throw new RuntimeException("Failed to parse JSON: " - + json.toString(), e); - } - } - return toReturn; - } - - private Map<Object, Object> convertJsonObjectToMap(final JSONObject json) { - Map<Object, Object> toReturn = Maps.newHashMap(); - for (Iterator it = json.keys(); it.hasNext();) { - String key = (String) it.next(); - try { - Object value = json.get(key); - toReturn.put(convertJsonToJavaObject(key), - convertJsonToJavaObject(value)); - } catch (JSONException e) { - throw new RuntimeException("Failed to parse JSON:" - + json.toString(), e); - } - } - return toReturn; - } - - private void throwIfError(final JSONObject jsonObject) { - ErrorCode status; - String errorMsg; - try { - status = ErrorCode.get((Integer) jsonObject.get(STATUS)); - errorMsg = String.valueOf(jsonObject.get(VALUE)); - } catch (JSONException e) { - throw new RuntimeException("Failed to parse JSON Object: " - + jsonObject, e); - } - switch (status) { - case SUCCESS: - return; - case NO_SUCH_ELEMENT: - throw new WebElementNotFoundException("Could not find " - + "WebElement."); - case STALE_ELEMENT_REFERENCE: - throw new WebElementStaleException("WebElement is stale."); - default: - throw new WebDriverException("Error: " + errorMsg); - } - } - - private void shouldRunInMainThread(boolean value) { - assert (value == (MAIN_THREAD == Thread.currentThread().getId())); - } - - /** - * Interface called from JavaScript when the result is ready. - */ - private class JavascriptResultReady { - - /** - * A callback from JavaScript to Java that passes the result as a - * parameter. This method is available from the WebView's - * JavaScript DOM as window.webdriver.resultReady(). - * - * @param result The result that should be sent to Java from Javascript. - */ - public void resultReady(final String result) { - synchronized (mSyncObject) { - mJsResult = result; - mCommandDone = true; - mSyncObject.notify(); - } - } - } - - /* package */ void notifyCommandDone() { - synchronized (mSyncObject) { - mCommandDone = true; - mSyncObject.notify(); - } - } - - /** - * Executes the given command by posting a message to mHandler. This thread - * will block until the command which runs in the main thread is done. - * - * @param command The command to run. - * @param arg The argument for that command. - * @param timeout A timeout in milliseconds. - */ - private String executeCommand(int command, final Object arg, long timeout) { - shouldRunInMainThread(false); - synchronized (mSyncObject) { - mCommandDone = false; - Message msg = mHandler.obtainMessage(command); - msg.obj = arg; - mHandler.sendMessage(msg); - - long end = System.currentTimeMillis() + timeout; - while (!mCommandDone) { - if (System.currentTimeMillis() >= end) { - throw new RuntimeException("Timeout executing command: " - + command); - } - try { - mSyncObject.wait(timeout); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - return mJsResult; - } - - private void checkNotNull(Object obj, String errosMsg) { - if (obj == null) { - throw new NullPointerException(errosMsg); - } - } -} diff --git a/core/java/android/webkit/webdriver/WebDriverException.java b/core/java/android/webkit/webdriver/WebDriverException.java deleted file mode 100644 index 1a579c2..0000000 --- a/core/java/android/webkit/webdriver/WebDriverException.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.webkit.webdriver; - -/** - * @hide - */ -public class WebDriverException extends RuntimeException { - public WebDriverException() { - super(); - } - - public WebDriverException(String reason) { - super(reason); - } - - public WebDriverException(String reason, Throwable cause) { - super(reason, cause); - } - - public WebDriverException(Throwable cause) { - super(cause); - } -} diff --git a/core/java/android/webkit/webdriver/WebElement.java b/core/java/android/webkit/webdriver/WebElement.java deleted file mode 100644 index 02c1595..0000000 --- a/core/java/android/webkit/webdriver/WebElement.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * 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.webkit.webdriver; - -import android.graphics.Point; -import android.view.KeyEvent; - -import com.android.internal.R; - -import java.util.List; -import java.util.Map; - -/** - * Represents an HTML element. Typically most interactions with a web page - * will be performed through this class. - * - * @hide - */ -public class WebElement { - private final String mId; - private final WebDriver mDriver; - - private static final String LOCATOR_ID = "id"; - private static final String LOCATOR_LINK_TEXT = "linkText"; - private static final String LOCATOR_PARTIAL_LINK_TEXT = "partialLinkText"; - private static final String LOCATOR_NAME = "name"; - private static final String LOCATOR_CLASS_NAME = "className"; - private static final String LOCATOR_CSS = "css"; - private static final String LOCATOR_TAG_NAME = "tagName"; - private static final String LOCATOR_XPATH = "xpath"; - - /** - * Package constructor to prevent clients from creating a new WebElement - * instance. - * - * <p> A WebElement represents an HTML element on the page. - * The corresponding HTML element is stored in a JS cache in the page - * that can be accessed through JavaScript using "bot.inject.cache". - * - * @param driver The WebDriver instance to use. - * @param id The index of the HTML element in the JavaSctipt cache. - * document.documentElement object. - */ - /* package */ WebElement(final WebDriver driver, final String id) { - this.mId = id; - this.mDriver = driver; - } - - /** - * Finds the first {@link android.webkit.webdriver.WebElement} using the - * given method. - * - * @param by The locating mechanism to use. - * @return The first matching element on the current context. - */ - public WebElement findElement(final By by) { - return by.findElement(this); - } - - /** - * Finds all {@link android.webkit.webdriver.WebElement} within the page - * using the given method. - * - * @param by The locating mechanism to use. - * @return A list of all {@link android.webkit.webdriver.WebElement} found, - * or an empty list if nothing matches. - */ - public List<WebElement> findElements(final By by) { - return by.findElements(this); - } - - /** - * Gets the visisble (i.e. not hidden by CSS) innerText of this element, - * inlcuding sub-elements. - * - * @return the innerText of this element. - * @throws {@link android.webkit.webdriver.WebElementStaleException} if this - * element is stale, i.e. not on the current DOM. - */ - public String getText() { - String getText = mDriver.getResourceAsString(R.raw.get_text_android); - return (String) executeAtom(getText, this); - } - - /** - * Gets the value of an HTML attribute for this element or the value of the - * property with the same name if the attribute is not present. If neither - * is set, null is returned. - * - * @param attribute the HTML attribute. - * @return the value of that attribute or the value of the property with the - * same name if the attribute is not set, or null if neither are set. For - * boolean attribute values this will return the string "true" or "false". - */ - public String getAttribute(String attribute) { - String getAttribute = mDriver.getResourceAsString( - R.raw.get_attribute_value_android); - return (String) executeAtom(getAttribute, this, attribute); - } - - /** - * @return the tag name of this element. - */ - public String getTagName() { - return (String) mDriver.executeScript("return arguments[0].tagName;", - this); - } - - /** - * @return true if this element is enabled, false otherwise. - */ - public boolean isEnabled() { - String isEnabled = mDriver.getResourceAsString( - R.raw.is_enabled_android); - return (Boolean) executeAtom(isEnabled, this); - } - - /** - * Determines whether this element is selected or not. This applies to input - * elements such as checkboxes, options in a select, and radio buttons. - * - * @return True if this element is selected, false otherwise. - */ - public boolean isSelected() { - String isSelected = mDriver.getResourceAsString( - R.raw.is_selected_android); - return (Boolean) executeAtom(isSelected, this); - } - - /** - * Selects an element on the page. This works for selecting checkboxes, - * options in a select, and radio buttons. - */ - public void setSelected() { - String setSelected = mDriver.getResourceAsString( - R.raw.set_selected_android); - executeAtom(setSelected, this); - } - - /** - * This toggles the checkboxe state from selected to not selected, or - * from not selected to selected. - * - * @return True if the toggled element is selected, false otherwise. - */ - public boolean toggle() { - String toggle = mDriver.getResourceAsString(R.raw.toggle_android); - return (Boolean) executeAtom(toggle, this); - } - - /** - * Sends the KeyEvents for the given sequence of characters to the - * WebElement to simulate typing. The KeyEvents are generated using the - * device's {@link android.view.KeyCharacterMap.VIRTUAL_KEYBOARD}. - * - * @param keys The keys to send to this WebElement - */ - public void sendKeys(CharSequence... keys) { - if (keys == null || keys.length == 0) { - return; - } - click(); - mDriver.moveCursorToRightMostPosition(getAttribute("value")); - mDriver.sendKeys(keys); - } - - /** - * Use this to send one of the key code constants defined in - * {@link android.view.KeyEvent} - * - * @param keys - */ - public void sendKeyCodes(int... keys) { - if (keys == null || keys.length == 0) { - return; - } - click(); - mDriver.moveCursorToRightMostPosition(getAttribute("value")); - mDriver.sendKeyCodes(keys); - } - - /** - * Sends a touch event to the center coordinates of this WebElement. - */ - public void click() { - Point topLeft = getLocation(); - Point size = getSize(); - int jsX = topLeft.x + size.x/2; - int jsY = topLeft.y + size.y/2; - Point center = new Point(jsX, jsY); - mDriver.sendTouchScreen(center); - } - - /** - * Submits the form containing this WebElement. - */ - public void submit() { - mDriver.resetPageLoadState(); - String submit = mDriver.getResourceAsString(R.raw.submit_android); - executeAtom(submit, this); - mDriver.waitForPageLoadIfNeeded(); - } - - /** - * Clears the text value if this is a text entry element. Does nothing - * otherwise. - */ - public void clear() { - String value = getAttribute("value"); - if (value == null || value.equals("")) { - return; - } - int length = value.length(); - int[] keys = new int[length]; - for (int i = 0; i < length; i++) { - keys[i] = KeyEvent.KEYCODE_DEL; - } - sendKeyCodes(keys); - } - - /** - * @return the value of the given CSS property if found, null otherwise. - */ - public String getCssValue(String cssProperty) { - String getCssProp = mDriver.getResourceAsString( - R.raw.get_value_of_css_property_android); - return (String) executeAtom(getCssProp, this, cssProperty); - } - - /** - * Gets the width and height of the rendered element. - * - * @return a {@link android.graphics.Point}, where Point.x represents the - * width, and Point.y represents the height of the element. - */ - public Point getSize() { - String getSize = mDriver.getResourceAsString(R.raw.get_size_android); - Map<String, Long> map = (Map<String, Long>) executeAtom(getSize, this); - return new Point(map.get("width").intValue(), - map.get("height").intValue()); - } - - /** - * Gets the location of the top left corner of this element on the screen. - * If the element is not visisble, this will scroll to get the element into - * the visisble screen. - * - * @return a {@link android.graphics.Point} containing the x and y - * coordinates of the top left corner of this element. - */ - public Point getLocation() { - String getLocation = mDriver.getResourceAsString( - R.raw.get_top_left_coordinates_android); - Map<String,Long> map = (Map<String, Long>) executeAtom(getLocation, - this); - return new Point(map.get("x").intValue(), map.get("y").intValue()); - } - - /** - * @return True if the WebElement is displayed on the screen, - * false otherwise. - */ - public boolean isDisplayed() { - String isDisplayed = mDriver.getResourceAsString( - R.raw.is_displayed_android); - return (Boolean) executeAtom(isDisplayed, this); - } - - /*package*/ String getId() { - return mId; - } - - /* package */ WebElement findElementById(final String locator) { - return findElement(LOCATOR_ID, locator); - } - - /* package */ WebElement findElementByLinkText(final String linkText) { - return findElement(LOCATOR_LINK_TEXT, linkText); - } - - /* package */ WebElement findElementByPartialLinkText( - final String linkText) { - return findElement(LOCATOR_PARTIAL_LINK_TEXT, linkText); - } - - /* package */ WebElement findElementByName(final String name) { - return findElement(LOCATOR_NAME, name); - } - - /* package */ WebElement findElementByClassName(final String className) { - return findElement(LOCATOR_CLASS_NAME, className); - } - - /* package */ WebElement findElementByCss(final String css) { - return findElement(LOCATOR_CSS, css); - } - - /* package */ WebElement findElementByTagName(final String tagName) { - return findElement(LOCATOR_TAG_NAME, tagName); - } - - /* package */ WebElement findElementByXPath(final String xpath) { - return findElement(LOCATOR_XPATH, xpath); - } - - /* package */ List<WebElement> findElementsById(final String locator) { - return findElements(LOCATOR_ID, locator); - } - - /* package */ List<WebElement> findElementsByLinkText(final String linkText) { - return findElements(LOCATOR_LINK_TEXT, linkText); - } - - /* package */ List<WebElement> findElementsByPartialLinkText( - final String linkText) { - return findElements(LOCATOR_PARTIAL_LINK_TEXT, linkText); - } - - /* package */ List<WebElement> findElementsByName(final String name) { - return findElements(LOCATOR_NAME, name); - } - - /* package */ List<WebElement> findElementsByClassName(final String className) { - return findElements(LOCATOR_CLASS_NAME, className); - } - - /* package */ List<WebElement> findElementsByCss(final String css) { - return findElements(LOCATOR_CSS, css); - } - - /* package */ List<WebElement> findElementsByTagName(final String tagName) { - return findElements(LOCATOR_TAG_NAME, tagName); - } - - /* package */ List<WebElement> findElementsByXPath(final String xpath) { - return findElements(LOCATOR_XPATH, xpath); - } - - private Object executeAtom(final String atom, final Object... args) { - String scriptArgs = mDriver.convertToJsArgs(args); - return mDriver.executeRawJavascript("(" + - atom + ")(" + scriptArgs + ")"); - } - - private List<WebElement> findElements(String strategy, String locator) { - String findElements = mDriver.getResourceAsString( - R.raw.find_elements_android); - if (mId.equals("")) { - return (List<WebElement>) executeAtom(findElements, - strategy, locator); - } else { - return (List<WebElement>) executeAtom(findElements, - strategy, locator, this); - } - } - - private WebElement findElement(String strategy, String locator) { - String findElement = mDriver.getResourceAsString( - R.raw.find_element_android); - WebElement el; - if (mId.equals("")) { - el = (WebElement) executeAtom(findElement, - strategy, locator); - } else { - el = (WebElement) executeAtom(findElement, - strategy, locator, this); - } - if (el == null) { - throw new WebElementNotFoundException("Could not find element " - + "with " + strategy + ": " + locator); - } - return el; - } -} diff --git a/core/java/android/webkit/webdriver/WebElementNotFoundException.java b/core/java/android/webkit/webdriver/WebElementNotFoundException.java deleted file mode 100644 index e66d279..0000000 --- a/core/java/android/webkit/webdriver/WebElementNotFoundException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.webkit.webdriver; - -/** - * Thrown when a {@link android.webkit.webdriver.WebElement} is not found in the - * DOM of the page. - * @hide - */ -public class WebElementNotFoundException extends RuntimeException { - - public WebElementNotFoundException() { - super(); - } - - public WebElementNotFoundException(String reason) { - super(reason); - } - - public WebElementNotFoundException(String reason, Throwable cause) { - super(reason, cause); - } - - public WebElementNotFoundException(Throwable cause) { - super(cause); - } -} diff --git a/core/java/android/webkit/webdriver/WebElementStaleException.java b/core/java/android/webkit/webdriver/WebElementStaleException.java deleted file mode 100644 index c59e794..0000000 --- a/core/java/android/webkit/webdriver/WebElementStaleException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.webkit.webdriver; - -/** - * Thrown when trying to access a {@link android.webkit.webdriver.WebElement} - * that is stale. This mean that the {@link android.webkit.webdriver.WebElement} - * is no longer present on the DOM of the page. - * @hide - */ -public class WebElementStaleException extends RuntimeException { - - public WebElementStaleException() { - super(); - } - - public WebElementStaleException(String reason) { - super(reason); - } - - public WebElementStaleException(String reason, Throwable cause) { - super(reason, cause); - } - - public WebElementStaleException(Throwable cause) { - super(cause); - } -} diff --git a/core/java/android/webkit/webdriver/WebViewClient.java b/core/java/android/webkit/webdriver/WebViewClient.java deleted file mode 100644 index c582b24..0000000 --- a/core/java/android/webkit/webdriver/WebViewClient.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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.webkit.webdriver; - -import android.graphics.Bitmap; -import android.net.http.SslError; -import android.os.Message; -import android.view.KeyEvent; -import android.webkit.HttpAuthHandler; -import android.webkit.SslErrorHandler; -import android.webkit.WebResourceResponse; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -/* package */ class WebViewClientWrapper extends WebViewClient { - private final WebViewClient mDelegate; - private final WebDriver mDriver; - - public WebViewClientWrapper(WebViewClient delegate, WebDriver driver) { - if (delegate == null) { - mDelegate = new WebViewClient(); - } else { - mDelegate = delegate; - } - this.mDriver = driver; - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - return mDelegate.shouldOverrideUrlLoading(view, url); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - mDriver.notifyPageStartedLoading(); - mDelegate.onPageStarted(view, url, favicon); - } - - @Override - public void onPageFinished(WebView view, String url) { - mDelegate.onPageFinished(view, url); - } - - @Override - public void onLoadResource(WebView view, String url) { - mDelegate.onLoadResource(view, url); - } - - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, - String url) { - return mDelegate.shouldInterceptRequest(view, url); - } - - @Override - public void onTooManyRedirects(WebView view, Message cancelMsg, - Message continueMsg) { - mDelegate.onTooManyRedirects(view, cancelMsg, continueMsg); - } - - @Override - public void onReceivedError(WebView view, int errorCode, String description, - String failingUrl) { - mDelegate.onReceivedError(view, errorCode, description, failingUrl); - } - - @Override - public void onFormResubmission(WebView view, Message dontResend, - Message resend) { - mDelegate.onFormResubmission(view, dontResend, resend); - } - - @Override - public void doUpdateVisitedHistory(WebView view, String url, - boolean isReload) { - mDelegate.doUpdateVisitedHistory(view, url, isReload); - } - - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, - SslError error) { - mDelegate.onReceivedSslError(view, handler, error); - } - - @Override - public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, - String host, String realm) { - mDelegate.onReceivedHttpAuthRequest(view, handler, host, realm); - } - - @Override - public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { - return mDelegate.shouldOverrideKeyEvent(view, event); - } - - @Override - public void onUnhandledKeyEvent(WebView view, KeyEvent event) { - mDelegate.onUnhandledKeyEvent(view, event); - } - - @Override - public void onScaleChanged(WebView view, float oldScale, float newScale) { - mDelegate.onScaleChanged(view, oldScale, newScale); - } - - @Override - public void onReceivedLoginRequest(WebView view, String realm, - String account, String args) { - mDelegate.onReceivedLoginRequest(view, realm, account, args); - } -} diff --git a/core/java/android/webkit/webdriver/WebchromeClientWrapper.java b/core/java/android/webkit/webdriver/WebchromeClientWrapper.java deleted file mode 100644 index a9e5d19..0000000 --- a/core/java/android/webkit/webdriver/WebchromeClientWrapper.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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.webkit.webdriver; - -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Message; -import android.view.View; -import android.webkit.ConsoleMessage; -import android.webkit.GeolocationPermissions; -import android.webkit.JsPromptResult; -import android.webkit.JsResult; -import android.webkit.ValueCallback; -import android.webkit.WebChromeClient; -import android.webkit.WebStorage; -import android.webkit.WebView; - -/* package */ class WebChromeClientWrapper extends WebChromeClient { - - private final WebChromeClient mDelegate; - private final WebDriver mDriver; - - public WebChromeClientWrapper(WebChromeClient delegate, WebDriver driver) { - if (delegate == null) { - this.mDelegate = new WebChromeClient(); - } else { - this.mDelegate = delegate; - } - this.mDriver = driver; - } - - @Override - public void onProgressChanged(WebView view, int newProgress) { - if (newProgress == 100) { - mDriver.notifyPageFinishedLoading(); - } - mDelegate.onProgressChanged(view, newProgress); - } - - @Override - public void onReceivedTitle(WebView view, String title) { - mDelegate.onReceivedTitle(view, title); - } - - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - mDelegate.onReceivedIcon(view, icon); - } - - @Override - public void onReceivedTouchIconUrl(WebView view, String url, - boolean precomposed) { - mDelegate.onReceivedTouchIconUrl(view, url, precomposed); - } - - @Override - public void onShowCustomView(View view, - CustomViewCallback callback) { - mDelegate.onShowCustomView(view, callback); - } - - @Override - public void onHideCustomView() { - mDelegate.onHideCustomView(); - } - - @Override - public boolean onCreateWindow(WebView view, boolean dialog, - boolean userGesture, Message resultMsg) { - return mDelegate.onCreateWindow(view, dialog, userGesture, resultMsg); - } - - @Override - public void onRequestFocus(WebView view) { - mDelegate.onRequestFocus(view); - } - - @Override - public void onCloseWindow(WebView window) { - mDelegate.onCloseWindow(window); - } - - @Override - public boolean onJsAlert(WebView view, String url, String message, - JsResult result) { - return mDelegate.onJsAlert(view, url, message, result); - } - - @Override - public boolean onJsConfirm(WebView view, String url, String message, - JsResult result) { - return mDelegate.onJsConfirm(view, url, message, result); - } - - @Override - public boolean onJsPrompt(WebView view, String url, String message, - String defaultValue, JsPromptResult result) { - return mDelegate.onJsPrompt(view, url, message, defaultValue, result); - } - - @Override - public boolean onJsBeforeUnload(WebView view, String url, String message, - JsResult result) { - return mDelegate.onJsBeforeUnload(view, url, message, result); - } - - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, - long currentQuota, long estimatedSize, long totalUsedQuota, - WebStorage.QuotaUpdater quotaUpdater) { - mDelegate.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota, - estimatedSize, totalUsedQuota, quotaUpdater); - } - - @Override - public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, - WebStorage.QuotaUpdater quotaUpdater) { - mDelegate.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota, - quotaUpdater); - } - - @Override - public void onGeolocationPermissionsShowPrompt(String origin, - GeolocationPermissions.Callback callback) { - mDelegate.onGeolocationPermissionsShowPrompt(origin, callback); - } - - @Override - public void onGeolocationPermissionsHidePrompt() { - mDelegate.onGeolocationPermissionsHidePrompt(); - } - - @Override - public boolean onJsTimeout() { - return mDelegate.onJsTimeout(); - } - - @Override - public void onConsoleMessage(String message, int lineNumber, - String sourceID) { - mDelegate.onConsoleMessage(message, lineNumber, sourceID); - } - - @Override - public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - return mDelegate.onConsoleMessage(consoleMessage); - } - - @Override - public Bitmap getDefaultVideoPoster() { - return mDelegate.getDefaultVideoPoster(); - } - - @Override - public View getVideoLoadingProgressView() { - return mDelegate.getVideoLoadingProgressView(); - } - - @Override - public void getVisitedHistory(ValueCallback<String[]> callback) { - mDelegate.getVisitedHistory(callback); - } - - @Override - public void openFileChooser(ValueCallback<Uri> uploadFile, - String acceptType) { - mDelegate.openFileChooser(uploadFile, acceptType); - } - - @Override - public void setInstallableWebApp() { - mDelegate.setInstallableWebApp(); - } - - @Override - public void setupAutoFill(Message msg) { - mDelegate.setupAutoFill(msg); - } -} diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 2621e64..df8eb05 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.ViewConfiguration; public abstract class AbsSeekBar extends ProgressBar { private Drawable mThumb; @@ -49,6 +50,10 @@ public abstract class AbsSeekBar extends ProgressBar { private static final int NO_ALPHA = 0xFF; private float mDisabledAlpha; + private int mScaledTouchSlop; + private float mTouchDownX; + private boolean mIsDragging; + public AbsSeekBar(Context context) { super(context); } @@ -74,6 +79,8 @@ public abstract class AbsSeekBar extends ProgressBar { com.android.internal.R.styleable.Theme, 0, 0); mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f); a.recycle(); + + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } /** @@ -324,20 +331,42 @@ public abstract class AbsSeekBar extends ProgressBar { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - setPressed(true); - onStartTrackingTouch(); - trackTouchEvent(event); + if (isInScrollingContainer()) { + mTouchDownX = event.getX(); + } else { + setPressed(true); + onStartTrackingTouch(); + trackTouchEvent(event); + attemptClaimDrag(); + } break; case MotionEvent.ACTION_MOVE: - trackTouchEvent(event); - attemptClaimDrag(); + if (mIsDragging) { + trackTouchEvent(event); + } else { + final float x = event.getX(); + if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) { + setPressed(true); + onStartTrackingTouch(); + trackTouchEvent(event); + attemptClaimDrag(); + } + } break; case MotionEvent.ACTION_UP: - trackTouchEvent(event); - onStopTrackingTouch(); - setPressed(false); + if (mIsDragging) { + trackTouchEvent(event); + onStopTrackingTouch(); + setPressed(false); + } else { + // Touch up when we never crossed the touch slop threshold should + // be interpreted as a tap-seek to that location. + onStartTrackingTouch(); + trackTouchEvent(event); + onStopTrackingTouch(); + } // ProgressBar doesn't know to repaint the thumb drawable // in its inactive state when the touch stops (because the // value has not apparently changed) @@ -345,8 +374,10 @@ public abstract class AbsSeekBar extends ProgressBar { break; case MotionEvent.ACTION_CANCEL: - onStopTrackingTouch(); - setPressed(false); + if (mIsDragging) { + onStopTrackingTouch(); + setPressed(false); + } invalidate(); // see above explanation break; } @@ -388,6 +419,7 @@ public abstract class AbsSeekBar extends ProgressBar { * This is called when the user has started touching this widget. */ void onStartTrackingTouch() { + mIsDragging = true; } /** @@ -395,6 +427,7 @@ public abstract class AbsSeekBar extends ProgressBar { * canceled. */ void onStopTrackingTouch() { + mIsDragging = false; } /** diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index dfee29b..c76728f 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -104,6 +104,7 @@ public class SeekBar extends AbsSeekBar { @Override void onStartTrackingTouch() { + super.onStartTrackingTouch(); if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStartTrackingTouch(this); } @@ -111,6 +112,7 @@ public class SeekBar extends AbsSeekBar { @Override void onStopTrackingTouch() { + super.onStopTrackingTouch(); if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onStopTrackingTouch(this); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index efcd8ab..1ab1a87 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -16,11 +16,6 @@ package android.widget; -import com.android.internal.util.FastMath; -import com.android.internal.widget.EditableInputConnection; - -import org.xmlpull.v1.XmlPullParserException; - import android.R; import android.content.ClipData; import android.content.ClipData.Item; @@ -139,6 +134,16 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.RemoteViews.RemoteView; +import com.android.internal.util.FastMath; +import com.android.internal.widget.EditableInputConnection; + +import org.xmlpull.v1.XmlPullParserException; + +import com.android.internal.util.FastMath; +import com.android.internal.widget.EditableInputConnection; + +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.lang.ref.WeakReference; import java.text.BreakIterator; @@ -220,6 +225,7 @@ import java.util.HashMap; * @attr ref android.R.styleable#TextView_imeActionLabel * @attr ref android.R.styleable#TextView_imeActionId * @attr ref android.R.styleable#TextView_editorExtras + * @attr ref android.R.styleable#TextView_suggestionsEnabled */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { @@ -2493,12 +2499,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_scrollHorizontally */ public void setHorizontallyScrolling(boolean whether) { - mHorizontallyScrolling = whether; + if (mHorizontallyScrolling != whether) { + mHorizontallyScrolling = whether; - if (mLayout != null) { - nullLayouts(); - requestLayout(); - invalidate(); + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } } } @@ -2699,13 +2707,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_lineSpacingMultiplier */ public void setLineSpacing(float add, float mult) { - mSpacingMult = mult; - mSpacingAdd = add; + if (mSpacingAdd != add || mSpacingMult != mult) { + mSpacingAdd = add; + mSpacingMult = mult; - if (mLayout != null) { - nullLayouts(); - requestLayout(); - invalidate(); + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } } } @@ -6276,12 +6286,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_includeFontPadding */ public void setIncludeFontPadding(boolean includepad) { - mIncludePad = includepad; + if (mIncludePad != includepad) { + mIncludePad = includepad; - if (mLayout != null) { - nullLayouts(); - requestLayout(); - invalidate(); + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } } } @@ -6605,7 +6617,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { // Dynamic width, so we have no choice but to request a new // view layout with a new text layout. - nullLayouts(); requestLayout(); invalidate(); @@ -7098,12 +7109,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_ellipsize */ public void setEllipsize(TextUtils.TruncateAt where) { - mEllipsize = where; + // TruncateAt is an enum. != comparison is ok between these singleton objects. + if (mEllipsize != where) { + mEllipsize = where; - if (mLayout != null) { - nullLayouts(); - requestLayout(); - invalidate(); + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } } } @@ -9410,7 +9424,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } void showSuggestions() { - if (!mSuggestionsEnabled || !isTextEditable()) return; + if (!isSuggestionsEnabled() || !isTextEditable()) return; if (mSuggestionsPopupWindow == null) { mSuggestionsPopupWindow = new SuggestionsPopupWindow(); @@ -9437,18 +9451,41 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * user double taps on these parts of the text. No suggestions are displayed when this value is * false. Use {@link #setSuggestionsEnabled(boolean)} to change this value. * + * Note that suggestions are only enabled for a subset of input types. In addition to setting + * this flag to <code>true</code> using {@link #setSuggestionsEnabled(boolean)} or the + * <code>android:suggestionsEnabled</code> xml attribute, this method will return + * <code>true</code> only if the class of your input type is {@link InputType#TYPE_CLASS_TEXT}. + * In addition, the type variation must also be one of + * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, + * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, + * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, + * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or + * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. + * * @return true if the suggestions popup window is enabled. * * @attr ref android.R.styleable#TextView_suggestionsEnabled */ public boolean isSuggestionsEnabled() { - return mSuggestionsEnabled; + if (!mSuggestionsEnabled) return false; + if ((mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false; + final int variation = + mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); + if (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL || + variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT || + variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE || + variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE || + variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) return true; + + return false; } /** * Enables or disables the suggestion popup. See {@link #isSuggestionsEnabled()}. * * @param enabled Whether or not suggestions are enabled. + * + * @attr ref android.R.styleable#TextView_suggestionsEnabled */ public void setSuggestionsEnabled(boolean enabled) { mSuggestionsEnabled = enabled; @@ -9720,10 +9757,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void show() { - mPasteTextView.setVisibility(canPaste() ? View.VISIBLE : View.GONE); - mReplaceTextView.setVisibility(mSuggestionsEnabled ? View.VISIBLE : View.GONE); + boolean canPaste = canPaste(); + boolean suggestionsEnabled = isSuggestionsEnabled(); + mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE); + mReplaceTextView.setVisibility(suggestionsEnabled ? View.VISIBLE : View.GONE); - if (!canPaste() && !mSuggestionsEnabled) return; + if (!canPaste && !suggestionsEnabled) return; super.show(); } diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 0df7bcc..31360e1 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -253,7 +253,7 @@ public class ActionBarImpl extends ActionBar { @Override public void setCustomView(int resId) { - setCustomView(LayoutInflater.from(mContext).inflate(resId, mActionView, false)); + setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false)); } @Override @@ -630,14 +630,14 @@ public class ActionBarImpl extends ActionBar { public ActionModeImpl(ActionMode.Callback callback) { mCallback = callback; - mMenu = new MenuBuilder(mActionView.getContext()) + mMenu = new MenuBuilder(getThemedContext()) .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); mMenu.setCallback(this); } @Override public MenuInflater getMenuInflater() { - return new MenuInflater(mContext); + return new MenuInflater(getThemedContext()); } @Override @@ -755,7 +755,7 @@ public class ActionBarImpl extends ActionBar { return true; } - new MenuPopupHelper(mContext, subMenu).show(); + new MenuPopupHelper(getThemedContext(), subMenu).show(); return true; } @@ -819,7 +819,8 @@ public class ActionBarImpl extends ActionBar { @Override public Tab setCustomView(int layoutResId) { - return setCustomView(LayoutInflater.from(mContext).inflate(layoutResId, null)); + return setCustomView(LayoutInflater.from(getThemedContext()) + .inflate(layoutResId, null)); } @Override diff --git a/core/java/com/android/internal/os/ProcessStats.java b/core/java/com/android/internal/os/ProcessStats.java index ea5ce09..e0e9a29 100644 --- a/core/java/com/android/internal/os/ProcessStats.java +++ b/core/java/com/android/internal/os/ProcessStats.java @@ -19,6 +19,7 @@ package com.android.internal.os; import static android.os.Process.*; import android.os.Process; +import android.os.StrictMode; import android.os.SystemClock; import android.util.Slog; @@ -798,6 +799,10 @@ public class ProcessStats { } private String readFile(String file, char endChar) { + // Permit disk reads here, as /proc/meminfo isn't really "on + // disk" and should be fast. TODO: make BlockGuard ignore + // /proc/ and /sys/ files perhaps? + StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); FileInputStream is = null; try { is = new FileInputStream(file); @@ -822,6 +827,7 @@ public class ProcessStats { } catch (java.io.IOException e) { } } + StrictMode.setThreadPolicy(savedPolicy); } return null; } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 06f753f..aaae691 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -162,7 +162,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter { final ActionMenuView menuView = (ActionMenuView) mMenuView; ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; actionItemView.setItemInvoker(menuView); - if (false) actionItemView.setExpandedFormat(menuView.isExpandedFormat()); } @Override @@ -174,7 +173,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter { public void updateMenuView(boolean cleared) { super.updateMenuView(cleared); - if (mReserveOverflow && mMenu.getNonActionItems().size() > 0) { + final boolean hasOverflow = mReserveOverflow && mMenu.getNonActionItems().size() > 0; + if (hasOverflow) { if (mOverflowButton == null) { mOverflowButton = new OverflowMenuButton(mContext); } @@ -189,6 +189,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter { } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) { ((ViewGroup) mMenuView).removeView(mOverflowButton); } + + ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow); } @Override diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java index bff621c..267221b 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuView.java @@ -115,17 +115,26 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo int maxChildHeight = 0; int maxCellsUsed = 0; int expandableItemCount = 0; + int visibleItemCount = 0; + boolean hasOverflow = false; - if (mReserveOverflow) cellsRemaining--; + // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64. + long smallestItemsAt = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); + if (child.getVisibility() == GONE) continue; + + visibleItemCount++; + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.expanded = false; lp.extraPixels = 0; lp.cellsUsed = 0; lp.expandable = false; + lp.leftMargin = 0; + lp.rightMargin = 0; // Overflow always gets 1 cell. No more, no less. final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining; @@ -135,16 +144,17 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo maxCellsUsed = Math.max(maxCellsUsed, cellsUsed); if (lp.expandable) expandableItemCount++; + if (lp.isOverflowButton) hasOverflow = true; cellsRemaining -= cellsUsed; maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); + if (cellsUsed == 1) smallestItemsAt |= (1 << i); } // Divide space for remaining cells if we have items that can expand. // Try distributing whole leftover cells to smaller items first. boolean needsExpansion = false; - long smallestExpandableItemsAt = 0; while (expandableItemCount > 0 && cellsRemaining > 0) { int minCells = Integer.MAX_VALUE; long minCellsAt = 0; // Bit locations are indices of relevant child views @@ -170,7 +180,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop. // Items that get expanded will always be in the set of smallest items when we're done. - smallestExpandableItemsAt |= minCellsAt; + smallestItemsAt |= minCellsAt; for (int i = 0; i < childCount; i++) { if ((minCellsAt & (1 << i)) == 0) continue; @@ -186,22 +196,58 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } // Divide any space left that wouldn't divide along cell boundaries - // evenly among the smallest multi-cell (expandable) items. + // evenly among the smallest items + + final boolean singleItem = !hasOverflow && visibleItemCount == 1; + if (cellsRemaining > 0 && smallestItemsAt != 0 && + (cellsRemaining < visibleItemCount - 1 || singleItem)) { + float expandCount = Long.bitCount(smallestItemsAt); + + if (!singleItem) { + // The items at the far edges may only expand by half in order to pin to either side. + if ((smallestItemsAt & 1) != 0) { + expandCount -= 0.5f; + } + if ((smallestItemsAt & (1 << (childCount - 1))) != 0) { + expandCount -= 0.5f; + } + } - if (cellsRemaining > 0 && smallestExpandableItemsAt != 0) { - final int expandCount = Long.bitCount(smallestExpandableItemsAt); - final int extraPixels = cellsRemaining * cellSize / expandCount; + final int extraPixels = (int) (cellsRemaining * cellSize / expandCount); for (int i = 0; i < childCount; i++) { - if ((smallestExpandableItemsAt & (1 << i)) == 0) continue; + if ((smallestItemsAt & (1 << i)) == 0) continue; final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.extraPixels = extraPixels; - lp.expanded = true; + if (child instanceof ActionMenuItemView) { + // If this is one of our views, expand and measure at the larger size. + lp.extraPixels = extraPixels; + lp.expanded = true; + if (i == 0) { + // First item gets part of its new padding pushed out of sight. + // The last item will get this implicitly from layout. + lp.leftMargin = -extraPixels / 2; + } + needsExpansion = true; + } else if (lp.isOverflowButton) { + lp.extraPixels = extraPixels; + lp.expanded = true; + lp.rightMargin = -extraPixels / 2; + needsExpansion = true; + } else { + // If we don't know what it is, give it some margins instead + // and let it center within its space. We still want to pin + // against the edges. + if (i != 0) { + lp.leftMargin = extraPixels / 2; + } + if (i != childCount - 1) { + lp.rightMargin = extraPixels / 2; + } + } } - needsExpansion = true; cellsRemaining = 0; } @@ -301,7 +347,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } int height = v.getMeasuredHeight(); - int r = getWidth() - getPaddingRight(); + int r = getWidth() - getPaddingRight() - p.rightMargin; int l = r - overflowWidth; int t = midVertical - (height / 2); int b = t + height; @@ -320,8 +366,20 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo } } + if (childCount == 1 && !hasOverflow) { + // Center a single child + final View v = getChildAt(0); + final int width = v.getMeasuredWidth(); + final int height = v.getMeasuredHeight(); + final int midHorizontal = (right - left) / 2; + final int l = midHorizontal - width / 2; + final int t = midVertical - height / 2; + v.layout(l, t, l + width, t + height); + return; + } + final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1); - final int spacerSize = spacerCount > 0 ? widthRemaining / spacerCount : 0; + final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0); int startLeft = getPaddingLeft(); for (int i = 0; i < childCount; i++) { @@ -334,7 +392,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo startLeft += lp.leftMargin; int width = v.getMeasuredWidth(); int height = v.getMeasuredHeight(); - int t = midVertical - (height / 2); + int t = midVertical - height / 2; v.layout(startLeft, t, startLeft + width, t + height); startLeft += width + lp.rightMargin + spacerSize; } diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index b4d2d72..fd9ee08 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -25,6 +25,7 @@ import android.util.AttributeSet; import android.view.ActionMode; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; /** @@ -109,7 +110,9 @@ public class ActionBarContainer extends FrameLayout { mTabContainer = tabView; if (tabView != null) { addView(tabView); - tabView.getLayoutParams().width = LayoutParams.MATCH_PARENT; + final ViewGroup.LayoutParams lp = tabView.getLayoutParams(); + lp.width = LayoutParams.MATCH_PARENT; + lp.height = LayoutParams.WRAP_CONTENT; tabView.setAllowCollapse(false); } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 3e3eeab..5645a6f 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -15,12 +15,18 @@ */ package com.android.internal.widget; +import com.android.internal.R; +import com.android.internal.view.menu.ActionMenuPresenter; +import com.android.internal.view.menu.ActionMenuView; +import com.android.internal.view.menu.MenuBuilder; + import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.ActionMode; @@ -30,11 +36,6 @@ import android.view.animation.DecelerateInterpolator; import android.widget.LinearLayout; import android.widget.TextView; -import com.android.internal.R; -import com.android.internal.view.menu.ActionMenuPresenter; -import com.android.internal.view.menu.ActionMenuView; -import com.android.internal.view.menu.MenuBuilder; - /** * @hide */ @@ -53,6 +54,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi private TextView mSubtitleView; private int mTitleStyleRes; private int mSubtitleStyleRes; + private Drawable mSplitBackground; private Animator mCurrentAnimation; private boolean mAnimateInOnLayout; @@ -83,6 +85,10 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi mContentHeight = a.getLayoutDimension( com.android.internal.R.styleable.ActionMode_height, 0); + + mSplitBackground = a.getDrawable( + com.android.internal.R.styleable.ActionMode_backgroundSplit); + a.recycle(); } @@ -175,6 +181,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi if (mSplitView == null) { menu.addMenuPresenter(mActionMenuPresenter); mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + mMenuView.setBackgroundDrawable(null); addView(mMenuView, layoutParams); } else { // Allow full screen width in split mode. @@ -187,6 +194,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi layoutParams.height = mContentHeight; menu.addMenuPresenter(mActionMenuPresenter); mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); + mMenuView.setBackgroundDrawable(mSplitBackground); mSplitView.addView(mMenuView, layoutParams); } @@ -256,7 +264,12 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi protected LayoutParams generateDefaultLayoutParams() { // Used by custom views if they don't supply layout params. Everything else // added to an ActionBarContextView should have them already. - return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); } @Override @@ -285,6 +298,8 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi if (mClose != null) { availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0); + MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); + availableWidth -= lp.leftMargin + lp.rightMargin; } if (mMenuView != null && mMenuView.getParent() == this) { @@ -327,7 +342,8 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi } private Animator makeInAnimation() { - mClose.setTranslationX(-mClose.getWidth()); + mClose.setTranslationX(-mClose.getWidth() - + ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0); buttonAnimator.setDuration(200); buttonAnimator.addListener(this); @@ -355,7 +371,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi private Animator makeOutAnimation() { ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", - -mClose.getWidth()); + -mClose.getWidth() - ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin); buttonAnimator.setDuration(200); buttonAnimator.addListener(this); buttonAnimator.setInterpolator(new DecelerateInterpolator()); @@ -387,7 +403,10 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi final int contentHeight = b - t - getPaddingTop() - getPaddingBottom(); if (mClose != null && mClose.getVisibility() != GONE) { + MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams(); + x += lp.leftMargin; x += positionChild(mClose, x, y, contentHeight); + x += lp.rightMargin; if (mAnimateInOnLayout) { mAnimationMode = ANIMATE_IN; diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 181958c..83f3294 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -462,8 +462,10 @@ public class ActionBarView extends AbsActionBarView { mTitle = title; if (mTitleView != null) { mTitleView.setText(title); - mTitleLayout.setVisibility(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle) ? - GONE : VISIBLE); + final boolean visible = mExpandedActionView == null && + (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0 && + (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle)); + mTitleLayout.setVisibility(visible ? VISIBLE : GONE); } if (mLogoNavItem != null) { mLogoNavItem.setTitle(title); @@ -479,8 +481,10 @@ public class ActionBarView extends AbsActionBarView { if (mSubtitleView != null) { mSubtitleView.setText(subtitle); mSubtitleView.setVisibility(subtitle != null ? VISIBLE : GONE); - mTitleLayout.setVisibility(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle) ? - GONE : VISIBLE); + final boolean visible = mExpandedActionView == null && + (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0 && + (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle)); + mTitleLayout.setVisibility(visible ? VISIBLE : GONE); } } @@ -575,7 +579,7 @@ public class ActionBarView extends AbsActionBarView { } public void setIcon(int resId) { - setIcon(mContext.getResources().getDrawableForDensity(resId, getPreferredIconDensity())); + setIcon(mContext.getResources().getDrawable(resId)); } public void setLogo(Drawable logo) { @@ -589,25 +593,6 @@ public class ActionBarView extends AbsActionBarView { setLogo(mContext.getResources().getDrawable(resId)); } - /** - * @return Drawable density to load that will best fit the available height. - */ - private int getPreferredIconDensity() { - final Resources res = mContext.getResources(); - final int availableHeight = getLayoutParams().height - - mHomeLayout.getVerticalIconPadding(); - int iconSize = res.getDimensionPixelSize(android.R.dimen.app_icon_size); - - if (iconSize * DisplayMetrics.DENSITY_LOW >= availableHeight) { - return DisplayMetrics.DENSITY_LOW; - } else if (iconSize * DisplayMetrics.DENSITY_MEDIUM >= availableHeight) { - return DisplayMetrics.DENSITY_MEDIUM; - } else if (iconSize * DisplayMetrics.DENSITY_HIGH >= availableHeight) { - return DisplayMetrics.DENSITY_HIGH; - } - return DisplayMetrics.DENSITY_XHIGH; - } - public void setNavigationMode(int mode) { final int oldMode = mNavigationMode; if (mode != oldMode) { @@ -739,7 +724,12 @@ public class ActionBarView extends AbsActionBarView { mTitleLayout.setEnabled(titleUp); } - addView(mTitleLayout); + addView(mTitleLayout, new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.MATCH_PARENT)); + if (mExpandedActionView != null) { + // Don't show while in expanded mode + mTitleLayout.setVisibility(GONE); + } } public void setContextView(ActionBarContextView view) { @@ -1342,6 +1332,7 @@ public class ActionBarView extends AbsActionBarView { removeView(mExpandedActionView); removeView(mExpandedHomeLayout); + mExpandedActionView = null; if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) { mHomeLayout.setVisibility(VISIBLE); } @@ -1361,7 +1352,6 @@ public class ActionBarView extends AbsActionBarView { if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { mCustomNavView.setVisibility(VISIBLE); } - mExpandedActionView = null; mExpandedHomeLayout.setIcon(null); mCurrentExpandedItem = null; requestLayout(); diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java index 0e4c9ef..71f9364 100644 --- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java +++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java @@ -68,6 +68,11 @@ public class ScrollingTabContainerView extends HorizontalScrollView super(context); setHorizontalScrollBarEnabled(false); + TypedArray a = getContext().obtainStyledAttributes(null, R.styleable.ActionBar, + com.android.internal.R.attr.actionBarStyle, 0); + setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0)); + a.recycle(); + mTabLayout = createTabLayout(); addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -91,16 +96,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView mMaxTabWidth = -1; } - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - if (heightMode != MeasureSpec.UNSPECIFIED) { - if (mContentHeight == 0 && heightMode == MeasureSpec.EXACTLY) { - // Use this as our content height. - mContentHeight = heightSize; - } - heightSize = Math.min(heightSize, mContentHeight); - heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); - } + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY); final boolean canCollapse = !lockedExpanded && mAllowCollapse; |
