* <?xml version="1.0" encoding="UTF-8"?>
- * <LinearLayout
+ * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation="vertical"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
- * android:paddingLeft="8"
- * android:paddingRight="8">
+ * android:paddingLeft="8dp"
+ * android:paddingRight="8dp">
*
- * <ExpandableListView id="android:list"
+ * <ExpandableListView android:id="@id/android:list"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
* android:background="#00FF00"
* android:layout_weight="1"
* android:drawSelectorOnTop="false"/>
*
- * <TextView id="android:empty"
+ * <TextView android:id="@id/android:empty"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
* android:background="#FF0000"
@@ -113,19 +113,19 @@ import java.util.Map;
*
*
* <?xml version="1.0" encoding="utf-8"?>
- * <LinearLayout
+ * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"
* android:orientation="vertical">
*
- * <TextView id="text1"
- * android:textSize="16"
+ * <TextView android:id="@+id/text1"
+ * android:textSize="16sp"
* android:textStyle="bold"
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"/>
*
- * <TextView id="text2"
- * android:textSize="16"
+ * <TextView android:id="@+id/text2"
+ * android:textSize="16sp"
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"/>
* </LinearLayout>
@@ -162,7 +162,8 @@ public class ExpandableListActivity extends Activity implements
/**
* Override this to populate the context menu when an item is long pressed. menuInfo
- * will contain a {@link AdapterContextMenuInfo} whose position is a packed position
+ * will contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo}
+ * whose packedPosition is a packed position
* that should be used with {@link ExpandableListView#getPackedPositionType(long)} and
* the other similar methods.
*
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
new file mode 100644
index 0000000000000..2b12a2a1304d1
--- /dev/null
+++ b/core/java/android/app/IntentService.java
@@ -0,0 +1,74 @@
+package android.app;
+
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * An abstract {@link Service} that serializes the handling of the Intents passed upon service
+ * start and handles them on a handler thread.
+ *
+ *
To use this class extend it and implement {@link #onHandleIntent}. The {@link Service} will
+ * automatically be stopped when the last enqueued {@link Intent} is handled.
+ */
+public abstract class IntentService extends Service {
+ private volatile Looper mServiceLooper;
+ private volatile ServiceHandler mServiceHandler;
+ private String mName;
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ onHandleIntent((Intent)msg.obj);
+ stopSelf(msg.arg1);
+ }
+ }
+
+ public IntentService(String name) {
+ super();
+ mName = name;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
+ thread.start();
+
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper);
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = intent;
+ mServiceHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onDestroy() {
+ mServiceLooper.quit();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Invoked on the Handler thread with the {@link Intent} that is passed to {@link #onStart}.
+ * Note that this will be invoked from a different thread than the one that handles the
+ * {@link #onStart} call.
+ */
+ protected abstract void onHandleIntent(Intent intent);
+}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 281893734b0d9..5523c185f21ae 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -53,22 +53,22 @@ import android.widget.ListView;
*
*
*
- * <?xml version="1.0" encoding="UTF-8"?>
- * <LinearLayout
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation="vertical"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
- * android:paddingLeft="8"
- * android:paddingRight="8">
+ * android:paddingLeft="8dp"
+ * android:paddingRight="8dp">
*
- * <ListView id="android:list"
+ * <ListView android:id="@id/android:list"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
* android:background="#00FF00"
* android:layout_weight="1"
* android:drawSelectorOnTop="false"/>
*
- * <TextView id="android:empty"
+ * <TextView id="@id/android:empty"
* android:layout_width="fill_parent"
* android:layout_height="fill_parent"
* android:background="#FF0000"
@@ -99,19 +99,19 @@ import android.widget.ListView;
*
*
* <?xml version="1.0" encoding="utf-8"?>
- * <LinearLayout
+ * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"
* android:orientation="vertical">
*
- * <TextView id="text1"
- * android:textSize="16"
+ * <TextView android:id="@+id/text1"
+ * android:textSize="16sp"
* android:textStyle="bold"
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"/>
*
- * <TextView id="text2"
- * android:textSize="16"
+ * <TextView android:id="@+id/text2"
+ * android:textSize="16sp"
* android:layout_width="fill_parent"
* android:layout_height="wrap_content"/>
* </LinearLayout>
@@ -142,8 +142,8 @@ import android.widget.ListView;
* public class MyListAdapter extends ListActivity {
*
* @Override
- * protected void onCreate(Bundle icicle){
- * super.onCreate(icicle);
+ * protected void onCreate(Bundle savedInstanceState){
+ * super.onCreate(savedInstanceState);
*
* // We'll define a custom screen layout here (the one shown above), but
* // typically, you could just use the standard ListActivity layout.
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index afb3827878ede..39edab70d1415 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -82,9 +82,7 @@ public class NotificationManager
* @param id An identifier for this notification unique within your
* application.
* @param notification A {@link Notification} object describing how to
- * notify the user, other than the view you're providing. If you
- * pass null, there will be no persistent notification and no
- * flashing, vibration, etc.
+ * notify the user, other than the view you're providing. Must not be null.
*/
public void notify(int id, Notification notification)
{
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 7b8256c46a008..d447eb22d5df1 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -448,6 +448,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
mSearchTextField.setInputType(inputType);
+ mSearchTextField.setImeOptions(mSearchable.getImeOptions());
}
}
@@ -793,7 +794,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// otherwise, dispatch an "edit view" key
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
if (event.getAction() == KeyEvent.ACTION_UP) {
v.cancelLongPress();
launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 2cc6de9515324..c1d66f4e7d7bc 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -747,6 +747,14 @@ import android.view.KeyEvent;
* inputType attribute.
* No
*
+ * android:imeOptions
+ * If provided, supplies additional options for the input method.
+ * For most searches, in which free form text is expected, this attribute
+ * need not be provided, and will default to "actionSearch".
+ * Suitable values for this attribute are described in the
+ * imeOptions attribute.
+ * No
+ *
*
*
*
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 56b231fa6b13f..1ba1c1e86f7b9 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -63,8 +63,10 @@ public class BluetoothDevice {
public static final int UNBOND_REASON_AUTH_CANCELED = 3;
/** A bond attempt failed because we could not contact the remote device */
public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
+ /** A bond attempt failed because a discovery is in progress */
+ public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
/** An existing bond was explicitly revoked */
- public static final int UNBOND_REASON_REMOVED = 5;
+ public static final int UNBOND_REASON_REMOVED = 6;
private static final String TAG = "BluetoothDevice";
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 3a64ceebc4fc6..25544deac10d1 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -18,6 +18,7 @@ package android.content;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.CursorToBulkCursorAdaptor;
@@ -162,6 +163,13 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.openFile(uri, mode);
}
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
+ else checkReadPermission(uri);
+ return ContentProvider.this.openAssetFile(uri, mode);
+ }
+
public ISyncAdapter getSyncAdapter() {
checkWritePermission(null);
return ContentProvider.this.getSyncAdapter().getISyncAdapter();
@@ -438,8 +446,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* of this method should create a new ParcelFileDescriptor for each call.
*
* @param uri The URI whose file is to be opened.
- * @param mode Access mode for the file. May be "r" for read-only access
- * or "rw" for read and write access.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "rw" for read and write access, or "rwt" for read and write access
+ * that truncates any existing file.
*
* @return Returns a new ParcelFileDescriptor which you can use to access
* the file.
@@ -448,19 +457,66 @@ public abstract class ContentProvider implements ComponentCallbacks {
* no file associated with the given URI or the mode is invalid.
* @throws SecurityException Throws SecurityException if the caller does
* not have permission to access the file.
- */
+ *
+ * @see #openAssetFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ */
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
throw new FileNotFoundException("No files supported by provider at "
+ uri);
}
+
+ /**
+ * This is like {@link #openFile}, but can be implemented by providers
+ * that need to be able to return sub-sections of files, often assets
+ * inside of their .apk. Note that when implementing this your clients
+ * must be able to deal with such files, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ *
+ * Note: if you are implementing this to return a full file, you
+ * should create the AssetFileDescriptor with
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+ * applications that can not handle sub-sections of files.
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ *
+ * @return Returns a new AssetFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the file.
+ *
+ * @see #openFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ */
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ ParcelFileDescriptor fd = openFile(uri, mode);
+ return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+ }
/**
* Convenience for subclasses that wish to implement {@link #openFile}
* by looking up a column named "_data" at the given URI.
*
* @param uri The URI to be opened.
- * @param mode The file mode.
+ * @param mode The file mode. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
*
* @return Returns a new ParcelFileDescriptor that can be used by the
* client to access the file.
@@ -489,16 +545,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
throw new FileNotFoundException("Column _data not found.");
}
- int modeBits;
- if ("r".equals(mode)) {
- modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
- } else if ("rw".equals(mode)) {
- modeBits = ParcelFileDescriptor.MODE_READ_WRITE
- | ParcelFileDescriptor.MODE_CREATE;
- } else {
- throw new FileNotFoundException("Bad mode for " + uri + ": "
- + mode);
- }
+ int modeBits = ContentResolver.modeToMode(uri, mode);
return ParcelFileDescriptor.open(new File(path), modeBits);
}
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index ede2c9bee6e99..e5e3f749a8d75 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.res.AssetFileDescriptor;
import android.database.BulkCursorNative;
import android.database.BulkCursorToCursorAdaptor;
import android.database.Cursor;
@@ -187,6 +188,25 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
return true;
}
+ case OPEN_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mode = data.readString();
+
+ AssetFileDescriptor fd;
+ fd = openAssetFile(url, mode);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case GET_SYNC_ADAPTER_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
@@ -413,6 +433,29 @@ final class ContentProviderProxy implements IContentProvider
return fd;
}
+ public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
+
+ mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
public ISyncAdapter getSyncAdapter() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 52f55b633727e..0d886ee23e8d1 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -17,6 +17,7 @@
package android.content;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -28,6 +29,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.text.TextUtils;
+import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -170,6 +172,143 @@ public abstract class ContentResolver {
*
See {@link #openAssetFileDescriptor(Uri, String)} for more information + * on these schemes. + * + * @param uri The desired URI. + * @return InputStream + * @throws FileNotFoundException if the provided URI could not be opened. + * @see #openAssetFileDescriptor(Uri, String) + */ + public final InputStream openInputStream(Uri uri) + throws FileNotFoundException { + String scheme = uri.getScheme(); + if (SCHEME_ANDROID_RESOURCE.equals(scheme)) { + // Note: left here to avoid breaking compatibility. May be removed + // with sufficient testing. + OpenResourceIdResult r = getResourceId(uri); + try { + InputStream stream = r.r.openRawResource(r.id); + return stream; + } catch (Resources.NotFoundException ex) { + throw new FileNotFoundException("Resource does not exist: " + uri); + } + } else if (SCHEME_FILE.equals(scheme)) { + // Note: left here to avoid breaking compatibility. May be removed + // with sufficient testing. + return new FileInputStream(uri.getPath()); + } else { + AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r"); + try { + return fd != null ? fd.createInputStream() : null; + } catch (IOException e) { + throw new FileNotFoundException("Unable to create stream"); + } + } + } + + /** + * Synonym for {@link #openOutputStream(Uri, String) + * openOutputStream(uri, "w")}. + * @throws FileNotFoundException if the provided URI could not be opened. + */ + public final OutputStream openOutputStream(Uri uri) + throws FileNotFoundException { + return openOutputStream(uri, "w"); + } + + /** + * Open a stream on to the content associated with a content URI. If there + * is no data associated with the URI, FileNotFoundException is thrown. + * + *
See {@link #openAssetFileDescriptor(Uri, String)} for more information + * on these schemes. + * + * @param uri The desired URI. + * @param mode May be "w", "wa", "rw", or "rwt". + * @return OutputStream + * @throws FileNotFoundException if the provided URI could not be opened. + * @see #openAssetFileDescriptor(Uri, String) + */ + public final OutputStream openOutputStream(Uri uri, String mode) + throws FileNotFoundException { + AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode); + try { + return fd != null ? fd.createOutputStream() : null; + } catch (IOException e) { + throw new FileNotFoundException("Unable to create stream"); + } + } + + /** + * Open a raw file descriptor to access data under a "content:" URI. This + * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the + * underlying {@link ContentProvider#openFile} + * ContentProvider.openFile()} method, so will not work with + * providers that return sub-sections of files. If at all possible, + * you should use {@link #openAssetFileDescriptor(Uri, String)}. You + * will receive a FileNotFoundException exception if the provider returns a + * sub-section of a file. + * + *
See {@link #openAssetFileDescriptor(Uri, String)} for more information + * on these schemes. + * + * @param uri The desired URI to open. + * @param mode The file mode to use, as per {@link ContentProvider#openFile + * ContentProvider.openFile}. + * @return Returns a new ParcelFileDescriptor pointing to the file. You + * own this descriptor and are responsible for closing it when done. + * @throws FileNotFoundException Throws FileNotFoundException of no + * file exists under the URI or the mode is invalid. + * @see #openAssetFileDescriptor(Uri, String) + */ + public final ParcelFileDescriptor openFileDescriptor(Uri uri, + String mode) throws FileNotFoundException { + AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode); + if (afd == null) { + return null; + } + + if (afd.getDeclaredLength() < 0) { + // This is a full file! + return afd.getParcelFileDescriptor(); + } + + // Client can't handle a sub-section of a file, so close what + // we got and bail with an exception. + try { + afd.close(); + } catch (IOException e) { + } + + throw new FileNotFoundException("Not a whole file"); + } + + /** + * Open a raw file descriptor to access data under a "content:" URI. This + * interacts with the underlying {@link ContentProvider#openAssetFile} + * ContentProvider.openAssetFile()} method of the provider associated with the + * given URI, to retrieve any file stored there. + * + *
* A Uri object can be used to reference a resource in an APK file. The @@ -193,129 +332,130 @@ public abstract class ContentResolver { *
Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");
*
*
- * @param uri The desired "content:" URI.
- * @return InputStream
- * @throws FileNotFoundException if the provided URI could not be opened.
- */
- public final InputStream openInputStream(Uri uri)
- throws FileNotFoundException {
- String scheme = uri.getScheme();
- if (SCHEME_CONTENT.equals(scheme)) {
- ParcelFileDescriptor fd = openFileDescriptor(uri, "r");
- return fd != null ? new ParcelFileDescriptor.AutoCloseInputStream(fd) : null;
- } else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- String authority = uri.getAuthority();
- Resources r;
- if (TextUtils.isEmpty(authority)) {
- throw new FileNotFoundException("No authority: " + uri);
- } else {
- try {
- r = mContext.getPackageManager().getResourcesForApplication(authority);
- } catch (NameNotFoundException ex) {
- throw new FileNotFoundException("No package found for authority: " + uri);
- }
- }
- ListThrows {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The name of the package to inspect.
+ *
+ * @return Returns either a fully-qualified Intent that can be used to
+ * launch the main activity in the package, or null if the package does
+ * not contain such an activity.
+ */
+ public abstract Intent getLaunchIntentForPackage(String packageName)
+ throws NameNotFoundException;
+
/**
* Return an array of all of the secondary group-ids that have been
* assigned to a package.
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 4a073f7566421..231e3e24a27c8 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,9 +16,13 @@
package android.content.res;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
/**
@@ -26,16 +30,32 @@ import java.io.IOException;
* opened FileDescriptor that can be used to read the data, as well as the
* offset and length of that entry's data in the file.
*/
-public class AssetFileDescriptor {
+public class AssetFileDescriptor implements Parcelable {
+ /**
+ * Length used with {@link #AssetFileDescriptor(ParcelFileDescriptor, long, long)}
+ * and {@link #getDeclaredLength} when a length has not been declared. This means
+ * the data extends to the end of the file.
+ */
+ public static final long UNKNOWN_LENGTH = -1;
+
private final ParcelFileDescriptor mFd;
private final long mStartOffset;
private final long mLength;
/**
* Create a new AssetFileDescriptor from the given values.
+ * @param fd The underlying file descriptor.
+ * @param startOffset The location within the file that the asset starts.
+ * This must be 0 if length is UNKNOWN_LENGTH.
+ * @param length The number of bytes of the asset, or
+ * {@link #UNKNOWN_LENGTH if it extends to the end of the file.
*/
public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
long length) {
+ if (length < 0 && startOffset != 0) {
+ throw new IllegalArgumentException(
+ "startOffset must be 0 when using UNKNOWN_LENGTH");
+ }
mFd = fd;
mStartOffset = startOffset;
mLength = length;
@@ -66,9 +86,33 @@ public class AssetFileDescriptor {
}
/**
- * Returns the total number of bytes of this asset entry's data.
+ * Returns the total number of bytes of this asset entry's data. May be
+ * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
+ * If the AssetFileDescriptor was constructed with {@link #UNKNOWN_LENGTH},
+ * this will use {@link ParcelFileDescriptor#getStatSize()
+ * ParcelFileDescriptor.getStatSize()} to find the total size of the file,
+ * returning that number if found or {@link #UNKNOWN_LENGTH} if it could
+ * not be determined.
+ *
+ * @see #getDeclaredLength()
*/
public long getLength() {
+ if (mLength >= 0) {
+ return mLength;
+ }
+ long len = mFd.getStatSize();
+ return len >= 0 ? len : UNKNOWN_LENGTH;
+ }
+
+ /**
+ * Return the actual number of bytes that were declared when the
+ * AssetFileDescriptor was constructed. Will be
+ * {@link #UNKNOWN_LENGTH} if the length was not declared, meaning data
+ * should be read to the end of the file.
+ *
+ * @see #getDeclaredLength()
+ */
+ public long getDeclaredLength() {
return mLength;
}
@@ -78,4 +122,227 @@ public class AssetFileDescriptor {
public void close() throws IOException {
mFd.close();
}
+
+ /**
+ * Create and return a new auto-close input stream for this asset. This
+ * will either return a full asset {@link AutoCloseInputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
+ * ParcelFileDescriptor.AutoCloseInputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileInputStream createInputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
+ }
+ return new AutoCloseInputStream(this);
+ }
+
+ /**
+ * Create and return a new auto-close output stream for this asset. This
+ * will either return a full asset {@link AutoCloseOutputStream}, or
+ * an underlying {@link ParcelFileDescriptor.AutoCloseOutputStream
+ * ParcelFileDescriptor.AutoCloseOutputStream} depending on whether the
+ * the object represents a complete file or sub-section of a file. You
+ * should only call this once for a particular asset.
+ */
+ public FileOutputStream createOutputStream() throws IOException {
+ if (mLength < 0) {
+ return new ParcelFileDescriptor.AutoCloseOutputStream(mFd);
+ }
+ return new AutoCloseOutputStream(this);
+ }
+
+ @Override
+ public String toString() {
+ return "{AssetFileDescriptor: " + mFd
+ + " start=" + mStartOffset + " len=" + mLength + "}";
+ }
+
+ /**
+ * An InputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescritor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseInputStream
+ extends ParcelFileDescriptor.AutoCloseInputStream {
+ private long mRemaining;
+
+ public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ super.skip(fd.getStartOffset());
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRemaining >= 0
+ ? (mRemaining < 0x7fffffff ? (int)mRemaining : 0x7fffffff)
+ : super.available();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int res = super.read();
+ if (res >= 0) mRemaining--;
+ return res;
+ }
+
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, offset, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ int res = super.read(buffer, 0, count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ return super.read(buffer);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = mRemaining;
+ long res = super.skip(count);
+ if (res >= 0) mRemaining -= res;
+ return res;
+ }
+
+ // TODO Auto-generated method stub
+ return super.skip(count);
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ if (mRemaining >= 0) {
+ return false;
+ }
+ return super.markSupported();
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.reset();
+ }
+ }
+
+ /**
+ * An OutputStream you can create on a ParcelFileDescriptor, which will
+ * take care of calling {@link ParcelFileDescriptor#close
+ * ParcelFileDescritor.close()} for you when the stream is closed.
+ */
+ public static class AutoCloseOutputStream
+ extends ParcelFileDescriptor.AutoCloseOutputStream {
+ private long mRemaining;
+
+ public AutoCloseOutputStream(AssetFileDescriptor fd) throws IOException {
+ super(fd.getParcelFileDescriptor());
+ if (fd.getParcelFileDescriptor().seekTo(fd.getStartOffset()) < 0) {
+ throw new IOException("Unable to seek");
+ }
+ mRemaining = (int)fd.getLength();
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int count) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer, offset, count);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer, offset, count);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ int count = buffer.length;
+ if (count > mRemaining) count = (int)mRemaining;
+ super.write(buffer);
+ mRemaining -= count;
+ return;
+ }
+
+ super.write(buffer);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return;
+ super.write(oneByte);
+ mRemaining--;
+ return;
+ }
+
+ super.write(oneByte);
+ }
+ }
+
+
+ /* Parcelable interface */
+ public int describeContents() {
+ return mFd.describeContents();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ mFd.writeToParcel(out, flags);
+ out.writeLong(mStartOffset);
+ out.writeLong(mLength);
+ }
+
+ AssetFileDescriptor(Parcel src) {
+ mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
+ mStartOffset = src.readLong();
+ mLength = src.readLong();
+ }
+
+ public static final Parcelable.Creator Type: INTEGER Type: INTEGER Type: INTEGER
* Normally the date is shown unless the start and end day are the same.
* If FORMAT_SHOW_DATE is set, then the date is always shown, even for
@@ -1120,24 +1144,28 @@ public class DateUtils
boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0;
boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
-
- Time startDate;
- Time endDate;
-
- if (useUTC) {
- startDate = new Time(Time.TIMEZONE_UTC);
- endDate = new Time(Time.TIMEZONE_UTC);
- } else {
- startDate = new Time();
- endDate = new Time();
- }
-
+
+ // If we're getting called with a single instant in time (from
+ // e.g. formatDateTime(), below), then we can skip a lot of
+ // computation below that'd otherwise be thrown out.
+ boolean isInstant = (startMillis == endMillis);
+
+ Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
startDate.set(startMillis);
- endDate.set(endMillis);
- int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
- int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
- int dayDistance = endJulianDay - startJulianDay;
-
+
+ Time endDate;
+ int dayDistance;
+ if (isInstant) {
+ endDate = startDate;
+ dayDistance = 0;
+ } else {
+ endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ endDate.set(endMillis);
+ int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
+ int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
+ dayDistance = endJulianDay - startJulianDay;
+ }
+
// If the end date ends at 12am at the beginning of a day,
// then modify it to make it look like it ends at midnight on
// the previous day. This will allow us to display "8pm - midnight",
@@ -1152,20 +1180,21 @@ public class DateUtils
// and an end date of Nov 12 at 00:00.
// If the start and end time are the same, then skip this and don't
// adjust the date.
- if ((endDate.hour | endDate.minute | endDate.second) == 0
- && (!showTime || dayDistance <= 1) && (startMillis != endMillis)) {
+ if (!isInstant
+ && (endDate.hour | endDate.minute | endDate.second) == 0
+ && (!showTime || dayDistance <= 1)) {
endDate.monthDay -= 1;
endDate.normalize(true /* ignore isDst */);
}
-
+
int startDay = startDate.monthDay;
int startMonthNum = startDate.month;
int startYear = startDate.year;
-
+
int endDay = endDate.monthDay;
int endMonthNum = endDate.month;
int endYear = endDate.year;
-
+
String startWeekDayString = "";
String endWeekDayString = "";
if (showWeekDay) {
@@ -1176,9 +1205,9 @@ public class DateUtils
weekDayFormat = WEEKDAY_FORMAT;
}
startWeekDayString = startDate.format(weekDayFormat);
- endWeekDayString = endDate.format(weekDayFormat);
+ endWeekDayString = isInstant ? startWeekDayString : endDate.format(weekDayFormat);
}
-
+
String startTimeString = "";
String endTimeString = "";
if (showTime) {
@@ -1204,7 +1233,7 @@ public class DateUtils
boolean capNoon = (flags & FORMAT_CAP_NOON) != 0;
boolean noMidnight = (flags & FORMAT_NO_MIDNIGHT) != 0;
boolean capMidnight = (flags & FORMAT_CAP_MIDNIGHT) != 0;
-
+
boolean startOnTheHour = startDate.minute == 0 && startDate.second == 0;
boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
if (abbrevTime && startOnTheHour) {
@@ -1220,20 +1249,41 @@ public class DateUtils
startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
- if (abbrevTime && endOnTheHour) {
- if (capAMPM) {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+
+ // Don't waste time on setting endTimeFormat when
+ // we're dealing with an instant, where we'll never
+ // need the end point. (It's the same as the start
+ // point)
+ if (!isInstant) {
+ if (abbrevTime && endOnTheHour) {
+ if (capAMPM) {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+ }
} else {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
+ if (capAMPM) {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+ }
}
- } else {
- if (capAMPM) {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
+
+ if (endDate.hour == 12 && endOnTheHour && !noNoon) {
+ if (capNoon) {
+ endTimeFormat = res.getString(com.android.internal.R.string.Noon);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.noon);
+ }
+ } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
+ if (capMidnight) {
+ endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
+ } else {
+ endTimeFormat = res.getString(com.android.internal.R.string.midnight);
+ }
}
}
-
+
if (startDate.hour == 12 && startOnTheHour && !noNoon) {
if (capNoon) {
startTimeFormat = res.getString(com.android.internal.R.string.Noon);
@@ -1243,37 +1293,32 @@ public class DateUtils
// Don't show the start time starting at midnight. Show
// 12am instead.
}
-
- if (endDate.hour == 12 && endOnTheHour && !noNoon) {
- if (capNoon) {
- endTimeFormat = res.getString(com.android.internal.R.string.Noon);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.noon);
- }
- } else if (endDate.hour == 0 && endOnTheHour && !noMidnight) {
- if (capMidnight) {
- endTimeFormat = res.getString(com.android.internal.R.string.Midnight);
- } else {
- endTimeFormat = res.getString(com.android.internal.R.string.midnight);
- }
- }
}
+
startTimeString = startDate.format(startTimeFormat);
- endTimeString = endDate.format(endTimeFormat);
+ endTimeString = isInstant ? startTimeString : endDate.format(endTimeFormat);
}
-
- // Get the current year
- long millis = System.currentTimeMillis();
- Time time = new Time();
- time.set(millis);
- int currentYear = time.year;
-
+
// Show the year if the user specified FORMAT_SHOW_YEAR or if
// the starting and end years are different from each other
// or from the current year. But don't show the year if the
- // user specified FORMAT_NO_YEAR;
- showYear = showYear || (!noYear && (startYear != endYear || startYear != currentYear));
-
+ // user specified FORMAT_NO_YEAR.
+ if (showYear) {
+ // No code... just a comment for clarity. Keep showYear
+ // on, as they enabled it with FORMAT_SHOW_YEAR. This
+ // takes precedence over them setting FORMAT_NO_YEAR.
+ } else if (noYear) {
+ // They explicitly didn't want a year.
+ showYear = false;
+ } else if (startYear != endYear) {
+ showYear = true;
+ } else {
+ // Show the year if it's not equal to the current year.
+ Time currentTime = new Time();
+ currentTime.setToNow();
+ showYear = startYear != currentTime.year;
+ }
+
String defaultDateFormat, fullFormat, dateRange;
if (numericDate) {
defaultDateFormat = res.getString(com.android.internal.R.string.numeric_date);
@@ -1306,7 +1351,7 @@ public class DateUtils
}
}
}
-
+
if (showWeekDay) {
if (showTime) {
fullFormat = res.getString(com.android.internal.R.string.wday1_date1_time1_wday2_date2_time2);
@@ -1320,20 +1365,20 @@ public class DateUtils
fullFormat = res.getString(com.android.internal.R.string.date1_date2);
}
}
-
+
if (noMonthDay && startMonthNum == endMonthNum) {
// Example: "January, 2008"
String startDateString = startDate.format(defaultDateFormat);
return startDateString;
}
-
+
if (startYear != endYear || noMonthDay) {
// Different year or we are not showing the month day number.
// Example: "December 31, 2007 - January 1, 2008"
// Or: "January - February, 2008"
String startDateString = startDate.format(defaultDateFormat);
String endDateString = endDate.format(defaultDateFormat);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1341,7 +1386,7 @@ public class DateUtils
endWeekDayString, endDateString, endTimeString);
return dateRange;
}
-
+
// Get the month, day, and year strings for the start and end dates
String monthFormat;
if (numericDate) {
@@ -1354,16 +1399,17 @@ public class DateUtils
String startMonthString = startDate.format(monthFormat);
String startMonthDayString = startDate.format(MONTH_DAY_FORMAT);
String startYearString = startDate.format(YEAR_FORMAT);
- String endMonthString = endDate.format(monthFormat);
- String endMonthDayString = endDate.format(MONTH_DAY_FORMAT);
- String endYearString = endDate.format(YEAR_FORMAT);
-
+
+ String endMonthString = isInstant ? null : endDate.format(monthFormat);
+ String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
+ String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
+
if (startMonthNum != endMonthNum) {
// Same year, different month.
// Example: "October 28 - November 3"
// or: "Wed, Oct 31 - Sat, Nov 3, 2007"
// or: "Oct 31, 8am - Sat, Nov 3, 2007, 5pm"
-
+
int index = 0;
if (showWeekDay) index = 1;
if (showYear) index += 2;
@@ -1371,7 +1417,7 @@ public class DateUtils
if (numericDate) index += 8;
int resId = sameYearTable[index];
fullFormat = res.getString(resId);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1381,7 +1427,7 @@ public class DateUtils
endYearString, endTimeString);
return dateRange;
}
-
+
if (startDay != endDay) {
// Same month, different day.
int index = 0;
@@ -1391,7 +1437,7 @@ public class DateUtils
if (numericDate) index += 8;
int resId = sameMonthTable[index];
fullFormat = res.getString(resId);
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat,
@@ -1401,19 +1447,19 @@ public class DateUtils
endYearString, endTimeString);
return dateRange;
}
-
+
// Same start and end day
boolean showDate = (flags & FORMAT_SHOW_DATE) != 0;
-
+
// If nothing was specified, then show the date.
if (!showTime && !showDate && !showWeekDay) showDate = true;
-
+
// Compute the time string (example: "10:00 - 11:00 am")
String timeString = "";
if (showTime) {
// If the start and end time are the same, then just show the
// start time.
- if (startMillis == endMillis) {
+ if (isInstant) {
// Same start and end time.
// Example: "10:15 AM"
timeString = startTimeString;
@@ -1423,7 +1469,7 @@ public class DateUtils
timeString = String.format(timeFormat, startTimeString, endTimeString);
}
}
-
+
// Figure out which full format to use.
fullFormat = "";
String dateString = "";
@@ -1457,7 +1503,7 @@ public class DateUtils
} else if (showTime) {
return timeString;
}
-
+
// The values that are used in a fullFormat string are specified
// by position.
dateRange = String.format(fullFormat, timeString, startWeekDayString, dateString);
diff --git a/core/java/android/text/method/NumberKeyListener.java b/core/java/android/text/method/NumberKeyListener.java
index e500faec4439c..9270ca58e4a6b 100644
--- a/core/java/android/text/method/NumberKeyListener.java
+++ b/core/java/android/text/method/NumberKeyListener.java
@@ -101,6 +101,11 @@ public abstract class NumberKeyListener extends BaseKeyListener
selEnd = Math.max(a, b);
}
+ if (selStart < 0 || selEnd < 0) {
+ selStart = selEnd = 0;
+ Selection.setSelection(content, 0);
+ }
+
int i = event != null ? lookup(event, content) : 0;
int repeatCount = event != null ? event.getRepeatCount() : 0;
if (repeatCount == 0) {
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 679c683cfeea2..e0231a7b99f45 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -113,20 +113,48 @@ public class GestureDetector {
}
/**
- * @hide pending API council
+ * The listener that is used to notify when a double-tap or a confirmed
+ * single-tap occur.
*/
public interface OnDoubleTapListener {
+ /**
+ * Notified when a single-tap occurs.
+ *
+ * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
+ * will only be called after the detector is confident that the user's
+ * first tap is not followed by a second tap leading to a double-tap
+ * gesture.
+ *
+ * @param e The down motion event of the single-tap.
+ * @return true if the event is consumed, else false
+ */
boolean onSingleTapConfirmed(MotionEvent e);
+
+ /**
+ * Notified when a double-tap occurs.
+ *
+ * @param e The down motion event of the first tap of the double-tap.
+ * @return true if the event is consumed, else false
+ */
+ boolean onDoubleTap(MotionEvent e);
+
+ /**
+ * Notified when an event within a double-tap gesture occurs, including
+ * the down, move, and up events.
+ *
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event is consumed, else false
+ */
boolean onDoubleTapEvent(MotionEvent e);
}
-
+
/**
- * A convenience class to extend when you only want to listen for a
- * subset of all the gestures. This implements all methods in the
- * {@link OnGestureListener} but does nothing and return {@code false}
- * for all applicable methods.
+ * A convenience class to extend when you only want to listen for a subset
+ * of all the gestures. This implements all methods in the
+ * {@link OnGestureListener} and {@link OnDoubleTapListener} but does
+ * nothing and return {@code false} for all applicable methods.
*/
- public static class SimpleOnGestureListener implements OnGestureListener {
+ public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@@ -150,13 +178,25 @@ public class GestureDetector {
public boolean onDown(MotionEvent e) {
return false;
}
+
+ public boolean onDoubleTap(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
}
// TODO: ViewConfiguration
private int mBiggerTouchSlopSquare = 20 * 20;
-
+
private int mTouchSlopSquare;
- private int mDoubleTapSlopSquare;
+ private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
@@ -164,7 +204,7 @@ public class GestureDetector {
// TODO make new double-tap timeout, and define its events (i.e. either time
// between down-down or time between up-down)
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
-
+
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
private static final int LONG_PRESS = 2;
@@ -181,13 +221,13 @@ public class GestureDetector {
private MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
-
+
/**
* True when the user is still touching for the second tap (down, move, and
* up events). Can only be true if there is a double tap listener attached.
*/
private boolean mIsDoubleTapping;
-
+
private float mLastMotionY;
private float mLastMotionX;
@@ -226,7 +266,7 @@ public class GestureDetector {
break;
default:
- throw new RuntimeException("Unknown message " + msg); //never
+ throw new RuntimeException("Unknown message " + msg); //never
}
}
}
@@ -303,6 +343,9 @@ public class GestureDetector {
mHandler = new GestureHandler();
}
mListener = listener;
+ if (listener instanceof OnDoubleTapListener) {
+ setOnDoubleTapListener((OnDoubleTapListener) listener);
+ }
init(context);
}
@@ -331,8 +374,11 @@ public class GestureDetector {
}
/**
- * @hide pending API council
- * @param onDoubleTapListener
+ * Sets the listener which will be called for double-tap and related
+ * gestures.
+ *
+ * @param onDoubleTapListener the listener invoked for all the callbacks, or
+ * null to stop listening for double-tap gestures.
*/
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
@@ -387,7 +433,10 @@ public class GestureDetector {
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
- handled = mDoubleTapListener.onDoubleTapEvent(ev);
+ // Give a callback with the first tap of the double-tap
+ handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
+ // Give a callback with down event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
@@ -418,7 +467,8 @@ public class GestureDetector {
final float scrollX = mLastMotionX - x;
final float scrollY = mLastMotionY - y;
if (mIsDoubleTapping) {
- handled = mDoubleTapListener.onDoubleTapEvent(ev);
+ // Give the move events of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (x - mCurrentDownEvent.getX());
final int deltaY = (int) (y - mCurrentDownEvent.getY());
@@ -446,7 +496,8 @@ public class GestureDetector {
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
- handled = mDoubleTapListener.onDoubleTapEvent(ev);
+ // Finally, give the up event of the double-tap
+ handled |= mDoubleTapListener.onDoubleTapEvent(ev);
mIsDoubleTapping = false;
break;
} else if (mInLongPress) {
@@ -495,7 +546,7 @@ public class GestureDetector {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
-
+
if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
return false;
}
@@ -504,7 +555,7 @@ public class GestureDetector {
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
-
+
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mInLongPress = true;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index d6ea91c4b5e37..430cc71de6108 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -228,6 +228,12 @@ public class KeyEvent implements Parcelable {
*/
public static final int FLAG_SOFT_KEYBOARD = 0x2;
+ /**
+ * This mask is set if we don't want the key event to cause us to leave
+ * touch mode.
+ */
+ public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
+
/**
* Returns the maximum keycode.
*/
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5ed3a7ea6471f..406fad8e312da 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -57,6 +57,7 @@ import com.android.internal.view.menu.MenuBuilder;
import java.util.ArrayList;
import java.util.Arrays;
+import java.lang.ref.SoftReference;
/**
*
@@ -1563,7 +1564,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private int[] mDrawableState = null;
- private Bitmap mDrawingCache;
+ private SoftReference IMPORTANT:
+ *
+ * By default, this will be a value that is derived from the max progress.
+ *
+ * @return The amount to increment or decrement when the user presses the
+ * arrow keys. This will be positive.
+ */
+ public int getKeyProgressIncrement() {
+ return mKeyProgressIncrement;
+ }
+
+ @Override
+ public synchronized void setMax(int max) {
+ super.setMax(max);
+
+ if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
+ // It will take the user too long to change this via keys, change it
+ // to something more reasonable
+ setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
+ }
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mThumb || super.verifyDrawable(who);
@@ -321,12 +360,12 @@ public abstract class AbsSeekBar extends ProgressBar {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (progress <= 0) break;
- setProgress(progress - 1, true);
+ setProgress(progress - mKeyProgressIncrement, true);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (progress >= getMax()) break;
- setProgress(progress + 1, true);
+ setProgress(progress + mKeyProgressIncrement, true);
return true;
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index cf9c588a21440..f847bc399b5a9 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -48,7 +48,6 @@ public class AnalogClock extends View {
private int mDialHeight;
private boolean mAttached;
- private long mLastTime;
private final Handler mHandler = new Handler();
private float mMinutes;
@@ -96,7 +95,6 @@ public class AnalogClock extends View {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- onTimeChanged();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();
@@ -107,6 +105,15 @@ public class AnalogClock extends View {
getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
+
+ // NOTE: It's safe to do these after registering the receiver since the receiver always runs
+ // in the main thread, therefore the receiver can't run before this method returns.
+
+ // The time zone may have changed while the receiver wasn't registered, so update the Time
+ mCalendar = new Time();
+
+ // Make sure we update to the current time
+ onTimeChanged();
}
@Override
@@ -212,9 +219,7 @@ public class AnalogClock extends View {
}
private void onTimeChanged() {
- long time = System.currentTimeMillis();
- mCalendar.set(time);
- mLastTime = time;
+ mCalendar.setToNow();
int hour = mCalendar.hour;
int minute = mCalendar.minute;
@@ -231,8 +236,6 @@ public class AnalogClock extends View {
if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
String tz = intent.getStringExtra("time-zone");
mCalendar = new Time(TimeZone.getTimeZone(tz).getID());
- } else {
- mCalendar = new Time();
}
onTimeChanged();
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 7a51676bf7681..0c1c72ab170c7 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -78,6 +78,8 @@ import com.android.internal.R;
* @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
* @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
*/
public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
static final boolean DEBUG = false;
@@ -96,6 +98,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private DropDownListView mDropDownList;
private int mDropDownVerticalOffset;
private int mDropDownHorizontalOffset;
+ private int mDropDownAnchorId;
+ private View mDropDownAnchorView; // view is retrieved lazily from id once needed
+ private int mDropDownWidth;
private Drawable mDropDownListHighlight;
@@ -147,6 +152,18 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
mDropDownHorizontalOffset = (int)
a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
+
+ // Get the anchor's id now, but the view won't be ready, so wait to actually get the
+ // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
+ // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
+ // this TextView, as a default anchoring point.
+ mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
+ View.NO_ID);
+
+ // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+ // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
+ mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
R.layout.simple_dropdown_hint);
@@ -187,6 +204,49 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void setCompletionHint(CharSequence hint) {
mHintText = hint;
}
+
+ /**
+ * Returns the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view. Sets the current width for the auto-complete drop down list. This can
+ * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view. Returns the id for the view that the auto-complete drop down list is anchored to. Sets the view to which the auto-complete drop down list should anchor. The view
+ * corresponding to this id will not be loaded until the next time it is needed to avoid
+ * loading a view which is not yet instantiated. Returns the number of characters the user must type before the drop
@@ -741,6 +801,18 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return result;
}
+
+ /**
+ * Used for lazy instantiation of the anchor view from the id we have. If the value of
+ * the id is NO_ID or we can't find a view for the given id, we return this TextView as
+ * the default anchoring point. Displays the drop down on screen. Creates a new asynchronous filter."com.example.myapp.SpecialMode=3". This field is can be
- * filled in from the {@link android.R.attr#editorPrivateContentType}
+ * filled in from the {@link android.R.attr#privateImeOptions}
* attribute of a TextView.
*/
- public String privateContentType = null;
+ public String privateImeOptions = null;
+
+ /**
+ * In some cases an IME may be able to display an arbitrary label for
+ * a command the user can perform, which you can specify here. You can
+ * not count on this being used.
+ */
+ public CharSequence actionLabel = null;
+
+ /**
+ * If {@link #actionLabel} has been given, this is the id for that command
+ * when the user presses its button that is delivered back with
+ * {@link InputConnection#performEditorAction(int)
+ * InputConnection.performEditorAction()}.
+ */
+ public int actionId = 0;
/**
* The text offset of the start of the selection at the time editing
@@ -106,7 +189,10 @@ public class EditorInfo implements InputType, Parcelable {
*/
public void dump(Printer pw, String prefix) {
pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType)
- + " privateContentType=" + privateContentType);
+ + " imeOptions=0x" + Integer.toHexString(imeOptions)
+ + " privateImeOptions=" + privateImeOptions);
+ pw.println(prefix + "actionLabel=" + actionLabel
+ + " actionId=" + actionId);
pw.println(prefix + "initialSelStart=" + initialSelStart
+ " initialSelEnd=" + initialSelEnd
+ " initialCapsMode=0x"
@@ -127,7 +213,10 @@ public class EditorInfo implements InputType, Parcelable {
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(inputType);
- dest.writeString(privateContentType);
+ dest.writeInt(imeOptions);
+ dest.writeString(privateImeOptions);
+ TextUtils.writeToParcel(actionLabel, dest, flags);
+ dest.writeInt(actionId);
dest.writeInt(initialSelStart);
dest.writeInt(initialSelEnd);
dest.writeInt(initialCapsMode);
@@ -146,7 +235,10 @@ public class EditorInfo implements InputType, Parcelable {
public EditorInfo createFromParcel(Parcel source) {
EditorInfo res = new EditorInfo();
res.inputType = source.readInt();
- res.privateContentType = source.readString();
+ res.imeOptions = source.readInt();
+ res.privateImeOptions = source.readString();
+ res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.actionId = source.readInt();
res.initialSelStart = source.readInt();
res.initialSelEnd = source.readInt();
res.initialCapsMode = source.readInt();
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 530127d1eceee..32cce35608d41 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -208,9 +208,23 @@ public interface InputConnection {
/**
* Set the selection of the text editor. To set the cursor position,
* start and end should have the same value.
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
*/
public boolean setSelection(int start, int end);
+ /**
+ * Have the editor perform an action it has said it can do.
+ *
+ * @param editorAction This must be one of the action constants for
+ * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
+ * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean performEditorAction(int editorAction);
+
/**
* Perform a context menu action on the field. The given id may be one of:
* {@link android.R.id#selectAll},
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 91fa2115d272a..e9e470304228b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -221,6 +221,11 @@ public final class InputMethodManager {
// -----------------------------------------------------------
+ /**
+ * This is the root view of the overall window that currently has input
+ * method focus.
+ */
+ View mCurRootView;
/**
* This is the view that should currently be served by an input method,
* regardless of the state of setting that up.
@@ -840,6 +845,13 @@ public final class InputMethodManager {
void focusInLocked(View view) {
if (DEBUG) Log.v(TAG, "focusIn: " + view);
+
+ if (mCurRootView != view.getRootView()) {
+ // This is a request from a window that isn't in the window with
+ // IME focus, so ignore it.
+ return;
+ }
+
// Okay we have a new view that is being served.
if (mServedView != view) {
mCurrentTextBoxAttribute = null;
@@ -913,7 +925,7 @@ public final class InputMethodManager {
}
/**
- * Called by ViewRoot the first time it gets window focus.
+ * Called by ViewRoot when its window gets input focus.
* @hide
*/
public void onWindowFocus(View rootView, View focusedView, int softInputMode,
@@ -946,9 +958,10 @@ public final class InputMethodManager {
}
/** @hide */
- public void startGettingWindowFocus() {
+ public void startGettingWindowFocus(View rootView) {
synchronized (mH) {
mWindowFocusedView = null;
+ mCurRootView = rootView;
}
}
@@ -1165,6 +1178,7 @@ public final class InputMethodManager {
+ " mBindSequence=" + mBindSequence
+ " mCurId=" + mCurId);
p.println(" mCurMethod=" + mCurMethod);
+ p.println(" mCurRootView=" + mCurRootView);
p.println(" mServedView=" + mServedView);
p.println(" mLastServedView=" + mLastServedView);
p.println(" mServedConnecting=" + mServedConnecting);
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 4f8e5e4fbcbeb..84aeb832d1e72 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -907,10 +907,12 @@ class CallbackProxy extends Handler {
}
public void onReceivedIcon(Bitmap icon) {
- if (Config.DEBUG && mBackForwardList.getCurrentItem() == null) {
- throw new AssertionError();
+ // The current item might be null if the icon was already stored in the
+ // database and this is a new WebView.
+ WebHistoryItem i = mBackForwardList.getCurrentItem();
+ if (i != null) {
+ i.setFavicon(icon);
}
- mBackForwardList.getCurrentItem().setFavicon(icon);
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mWebChromeClient == null) {
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index c2620a512e4f2..8a82411ec1936 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -25,8 +25,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
-import android.os.Handler;
-import android.os.Message;
import android.text.Editable;
import android.text.InputFilter;
import android.text.Selection;
@@ -43,7 +41,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewConfiguration;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
@@ -82,22 +79,6 @@ import java.util.ArrayList;
// FIXME: This can be replaced with TextView.NO_FILTERS if that
// is made public/protected.
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
- // The time of the last enter down, so we know whether to perform a long
- // press.
- private long mDownTime;
-
- private boolean mTrackballDown = false;
- private static int LONGPRESS = 1;
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- if (msg.what == LONGPRESS) {
- if (mTrackballDown) {
- performLongClick();
- mTrackballDown = false;
- }
- }
- }
- };
/**
* Create a new TextDialog.
@@ -134,6 +115,13 @@ import java.util.ArrayList;
setTextColor(Color.BLACK);
}
+ @Override
+ protected boolean shouldAdvanceFocusOnEnter() {
+ // In the browser, single line textfields use enter as a form submit,
+ // so we never want to advance the focus on enter.
+ return false;
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.isSystem()) {
@@ -152,43 +140,33 @@ import java.util.ArrayList;
return true;
}
- // For single-line textfields, return key should not be handled
- // here. Instead, the WebView is passed the key up, so it may fire a
- // submit/onClick.
- // Center key should always be passed to a potential onClick
- if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)
- || KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ if ((mSingle && KeyEvent.KEYCODE_ENTER == keyCode)) {
if (isPopupShowing()) {
- super.dispatchKeyEvent(event);
- return true;
+ return super.dispatchKeyEvent(event);
}
- if (down) {
- if (event.getRepeatCount() == 0) {
- mGotEnterDown = true;
- mDownTime = event.getEventTime();
- // Send the keydown when the up comes, so that we have
- // a chance to handle a long press.
- } else if (mGotEnterDown && event.getEventTime() - mDownTime >
- ViewConfiguration.getLongPressTimeout()) {
- performLongClick();
- mGotEnterDown = false;
- }
- } else if (mGotEnterDown) {
- mGotEnterDown = false;
- if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
- mWebView.shortPressOnTextField();
- return true;
- }
- // If we reached here, then this is a single line textfield, and
- // the user pressed ENTER. In this case, we want to hide the
- // soft input method.
+ if (!down) {
+ // Hide the keyboard, since the user has just submitted this
+ // form. The submission happens thanks to the two calls
+ // to sendDomEvent.
InputMethodManager.getInstance(mContext)
.hideSoftInputFromWindow(getWindowToken(), 0);
sendDomEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
sendDomEvent(event);
}
- return true;
+ return super.dispatchKeyEvent(event);
+ } else if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ // Note that this handles center key and trackball.
+ if (isPopupShowing()) {
+ return super.dispatchKeyEvent(event);
+ }
+ // Center key should be passed to a potential onClick
+ if (!down) {
+ mWebView.shortPressOnTextField();
+ }
+ // Pass to super to handle longpress.
+ return super.dispatchKeyEvent(event);
}
+
// Ensure there is a layout so arrow keys are handled properly.
if (getLayout() == null) {
measure(mWidthSpec, mHeightSpec);
@@ -225,9 +203,8 @@ import java.util.ArrayList;
case KeyEvent.KEYCODE_DPAD_DOWN:
isArrowKey = true;
break;
- case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- // For multi-line text boxes, newlines and dpad center will
+ // For multi-line text boxes, newlines will
// trigger onTextChanged for key down (which will send both
// key up and key down) but not key up.
mGotEnterDown = true;
@@ -269,7 +246,7 @@ import java.util.ArrayList;
// with WebCore's notion of the current selection, reset the selection
// to what it was before the key event.
Selection.setSelection(text, oldStart, oldEnd);
- // Ignore the key up event for newlines or dpad center. This prevents
+ // Ignore the key up event for newlines. This prevents
// multiple newlines in the native textarea.
if (mGotEnterDown && !down) {
return true;
@@ -391,27 +368,8 @@ import java.util.ArrayList;
if (isPopupShowing()) {
return super.onTrackballEvent(event);
}
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (!mTrackballDown) {
- mTrackballDown = true;
- mHandler.sendEmptyMessageDelayed(LONGPRESS,
- ViewConfiguration.getLongPressTimeout());
- }
- return true;
- case MotionEvent.ACTION_UP:
- if (mTrackballDown) {
- mWebView.shortPressOnTextField();
- mTrackballDown = false;
- mHandler.removeMessages(LONGPRESS);
- }
- return true;
- case MotionEvent.ACTION_CANCEL:
- mTrackballDown = false;
- return true;
- case MotionEvent.ACTION_MOVE:
- // fall through
+ if (event.getAction() != MotionEvent.ACTION_MOVE) {
+ return false;
}
Spannable text = (Spannable) getText();
MovementMethod move = getMovementMethod();
@@ -442,7 +400,6 @@ import java.util.ArrayList;
// hide the soft keyboard when the edit text is out of focus
InputMethodManager.getInstance(mContext).hideSoftInputFromWindow(
getWindowToken(), 0);
- mHandler.removeMessages(LONGPRESS);
mWebView.removeView(this);
mWebView.requestFocus();
mScrollToAccommodateCursor = false;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index c59a5fcfbf8d7..5126ef06fbd56 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -71,6 +71,7 @@ import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.Toast;
+import android.widget.ZoomButtonsController;
import android.widget.ZoomControls;
import android.widget.ZoomRingController;
import android.widget.FrameLayout;
@@ -284,8 +285,10 @@ public class WebView extends AbsoluteLayout
/**
* Customizable constant
*/
- // pre-computed square of ViewConfiguration.getTouchSlop()
+ // pre-computed square of ViewConfiguration.getScaledTouchSlop()
private int mTouchSlopSquare;
+ // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
+ private int mDoubleTapSlopSquare;
// 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.
@@ -321,6 +324,8 @@ public class WebView extends AbsoluteLayout
private int mContentHeight; // cache of value from WebViewCore
static int MAX_FLOAT_CONTENT_WIDTH = 480;
+ // the calculated minimum content width for calculating the minimum scale.
+ // If it is 0, it means don't use it.
private int mMinContentWidth;
// Need to have the separate control for horizontal and vertical scrollbar
@@ -553,7 +558,9 @@ public class WebView extends AbsoluteLayout
return mExtra;
}
}
-
+
+ private ZoomButtonsController mZoomButtonsController;
+
private ZoomRingController mZoomRingController;
private ImageView mZoomRingOverview;
private Animation mZoomRingOverviewExitAnimation;
@@ -617,6 +624,9 @@ public class WebView extends AbsoluteLayout
/ ZOOM_RING_STEPS;
}
mZoomRingController.setThumbAngle(angle * MAX_ZOOM_RING_ANGLE);
+
+ // Don't show a thumb if the user cannot zoom
+ mZoomRingController.setThumbVisible(mMinZoomScale != mMaxZoomScale);
// Show the zoom overview tab on the ring
setZoomOverviewVisible(true);
@@ -733,6 +743,26 @@ public class WebView extends AbsoluteLayout
mZoomRingController.setPannerAcceleration(160);
mZoomRingController.setPannerStartAcceleratingDuration(700);
createZoomRingOverviewTab();
+ mZoomButtonsController = new ZoomButtonsController(context, this);
+ mZoomButtonsController.setOverviewVisible(true);
+ mZoomButtonsController.setCallback(new ZoomButtonsController.OnZoomListener() {
+ public void onCenter(int x, int y) {
+ mZoomListener.onCenter(x, y);
+ }
+
+ public void onOverview() {
+ mZoomButtonsController.setVisible(false);
+ zoomScrollOut();
+ }
+
+ public void onVisibilityChanged(boolean visible) {
+ mZoomListener.onVisibilityChanged(visible);
+ }
+
+ public void onZoom(boolean zoomIn) {
+ mZoomListener.onSimpleZoom(zoomIn);
+ }
+ });
}
private void init() {
@@ -745,6 +775,9 @@ public class WebView extends AbsoluteLayout
final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mTouchSlopSquare = slop * slop;
mMinLockSnapReverseDistance = slop;
+ final int doubleTapslop = ViewConfiguration.get(getContext())
+ .getScaledDoubleTapSlop();
+ mDoubleTapSlopSquare = doubleTapslop * doubleTapslop;
}
private void createZoomRingOverviewTab() {
@@ -763,7 +796,7 @@ public class WebView extends AbsoluteLayout
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER);
// TODO: magic constant that's based on the zoom ring radius + some offset
- lp.topMargin = 208;
+ lp.topMargin = 200;
mZoomRingOverview.setLayoutParams(lp);
mZoomRingOverview.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
@@ -2305,8 +2338,19 @@ public class WebView extends AbsoluteLayout
/**
* Use this function to bind an object to Javascript so that the
* methods can be accessed from Javascript.
- * IMPORTANT, the object that is bound runs in another thread and
- * not in the thread that it was constructed in.
+ *
+ *
+ * Do not use addJavascriptInterface() unless all of the HTML in this
+ * WebView was written by you.