summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-02-19 10:57:31 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-02-19 10:57:31 -0800
commit3001a035439d8134a7d70d796376d1dfbff3cdcd (patch)
tree343ccdba15a594ff6e50c874a145232753315a30 /core/java/android
parentda996f390e17e16f2dfa60e972e7ebc4f868f37e (diff)
downloadframeworks_base-3001a035439d8134a7d70d796376d1dfbff3cdcd.zip
frameworks_base-3001a035439d8134a7d70d796376d1dfbff3cdcd.tar.gz
frameworks_base-3001a035439d8134a7d70d796376d1dfbff3cdcd.tar.bz2
auto import from //branches/cupcake/...@132276
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/PendingIntent.java13
-rw-r--r--core/java/android/app/SearchDialog.java9
-rw-r--r--core/java/android/app/Service.java8
-rw-r--r--core/java/android/bluetooth/ScoSocket.java19
-rw-r--r--core/java/android/content/BroadcastReceiver.java4
-rw-r--r--core/java/android/content/ContentProvider.java64
-rw-r--r--core/java/android/content/Intent.java31
-rw-r--r--core/java/android/content/package.html7
-rw-r--r--core/java/android/content/pm/PackageInfo.java18
-rw-r--r--core/java/android/content/pm/PackageParser.java7
-rw-r--r--core/java/android/database/DatabaseUtils.java1
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java7
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java76
-rwxr-xr-xcore/java/android/inputmethodservice/Keyboard.java60
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java149
-rw-r--r--core/java/android/os/BatteryStats.java135
-rw-r--r--core/java/android/os/ICheckinService.aidl8
-rw-r--r--core/java/android/os/IPowerManager.aidl1
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java2
-rw-r--r--core/java/android/provider/Checkin.java1
-rw-r--r--core/java/android/provider/Settings.java7
-rw-r--r--core/java/android/server/BluetoothDeviceService.java50
-rw-r--r--core/java/android/server/BluetoothEventLoop.java2
-rw-r--r--core/java/android/speech/srec/MicrophoneInputStream.java12
-rw-r--r--core/java/android/text/format/Time.java1
-rw-r--r--core/java/android/text/style/ImageSpan.java5
-rw-r--r--core/java/android/util/SparseIntArray.java13
-rw-r--r--core/java/android/view/GestureDetector.java24
-rwxr-xr-xcore/java/android/view/OrientationEventListener.java156
-rw-r--r--core/java/android/view/OrientationListener.java99
-rw-r--r--core/java/android/view/RemotableViewMethod.java (renamed from core/java/android/os/HandlerInterface.java)24
-rw-r--r--core/java/android/view/View.java11
-rw-r--r--core/java/android/view/ViewConfiguration.java19
-rw-r--r--core/java/android/view/ViewGroup.java35
-rw-r--r--core/java/android/view/ViewRoot.java94
-rw-r--r--core/java/android/view/ViewTreeObserver.java66
-rw-r--r--core/java/android/view/WindowManager.java30
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java162
-rw-r--r--core/java/android/view/animation/Animation.java7
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java15
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java20
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java51
-rw-r--r--core/java/android/webkit/BrowserFrame.java2
-rw-r--r--core/java/android/webkit/LoadListener.java2
-rw-r--r--core/java/android/webkit/TextDialog.java22
-rw-r--r--core/java/android/webkit/WebView.java294
-rw-r--r--core/java/android/webkit/WebViewCore.java99
-rw-r--r--core/java/android/webkit/gears/HttpRequestAndroid.java745
-rw-r--r--core/java/android/webkit/gears/UrlInterceptHandlerGears.java6
-rw-r--r--core/java/android/widget/AbsListView.java73
-rw-r--r--core/java/android/widget/Chronometer.java48
-rw-r--r--core/java/android/widget/FrameLayout.java2
-rw-r--r--core/java/android/widget/ImageView.java7
-rw-r--r--core/java/android/widget/LinearLayout.java6
-rw-r--r--core/java/android/widget/ListView.java4
-rw-r--r--core/java/android/widget/PopupWindow.java91
-rw-r--r--core/java/android/widget/ProgressBar.java4
-rw-r--r--core/java/android/widget/RelativeLayout.java4
-rw-r--r--core/java/android/widget/RemoteViews.java664
-rw-r--r--core/java/android/widget/TabHost.java6
-rw-r--r--core/java/android/widget/TextView.java65
-rw-r--r--core/java/android/widget/ViewFlipper.java2
-rw-r--r--core/java/android/widget/ZoomRing.java687
-rw-r--r--core/java/android/widget/ZoomRingController.java471
64 files changed, 2906 insertions, 1921 deletions
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index b59e9dc..1bed706 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -426,13 +426,9 @@ public final class PendingIntent implements Parcelable {
*/
@Override
public boolean equals(Object otherObj) {
- if (otherObj == null) {
- return false;
- }
- try {
+ if (otherObj instanceof PendingIntent) {
return mTarget.asBinder().equals(((PendingIntent)otherObj)
.mTarget.asBinder());
- } catch (ClassCastException e) {
}
return false;
}
@@ -442,6 +438,13 @@ public final class PendingIntent implements Parcelable {
return mTarget.asBinder().hashCode();
}
+ @Override
+ public String toString() {
+ return "PendingIntent{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " target " + (mTarget != null ? mTarget.asBinder() : null) + "}";
+ }
+
public int describeContents() {
return 0;
}
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 5744ddc..7b8256c 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -31,8 +31,6 @@ import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -64,7 +62,6 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import java.lang.ref.WeakReference;
-import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -538,9 +535,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
testIntent = mVoiceAppSearchIntent;
}
if (testIntent != null) {
- List<ResolveInfo> list = getContext().getPackageManager().
- queryIntentActivities(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
- if (list.size() > 0) {
+ ResolveInfo ri = getContext().getPackageManager().
+ resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (ri != null) {
visibility = View.VISIBLE;
}
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 72692f4..a6a436f 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -327,11 +327,15 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
}
/**
- * Print the Service's state into the given stream.
+ * Print the Service's state into the given stream. This gets invoked if
+ * you run "adb shell dumpsys activity service <yourservicename>".
+ * This is distinct from "dumpsys <servicename>", which only works for
+ * named system services and which invokes the {@link IBinder#dump} method
+ * on the {@link IBinder} interface registered with ServiceManager.
*
* @param fd The raw file descriptor that the dump is being sent to.
* @param writer The PrintWriter to which you should dump your state. This will be
- * closed for you after you return.
+ * closed for you after you return.
* @param args additional arguments to the dump request.
*/
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
index 75b3329..a43a08b 100644
--- a/core/java/android/bluetooth/ScoSocket.java
+++ b/core/java/android/bluetooth/ScoSocket.java
@@ -16,17 +16,12 @@
package android.bluetooth;
-import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
-import java.io.IOException;
-import java.lang.Thread;
-
-
/**
* The Android Bluetooth API is not finalized, and *will* change. Use at your
* own risk.
@@ -56,7 +51,7 @@ public class ScoSocket {
private int mConnectedCode;
private int mClosedCode;
- private WakeLock mWakeLock; // held while STATE_CONNECTING or STATE_CONNECTED
+ private WakeLock mWakeLock; // held while in STATE_CONNECTING
static {
classInitNative();
@@ -130,6 +125,7 @@ public class ScoSocket {
public synchronized void close() {
if (DBG) log(this + " SCO OBJECT close() mState = " + mState);
+ acquireWakeLock();
mState = STATE_CLOSED;
closeNative();
releaseWakeLock();
@@ -152,19 +148,16 @@ public class ScoSocket {
mState = STATE_CLOSED;
}
mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget();
- if (result < 0) {
- releaseWakeLock();
- }
+ releaseWakeLock();
}
private synchronized void onAccepted(int result) {
if (VDBG) log("onAccepted() " + this);
if (mState != STATE_ACCEPT) {
- if (DBG) log("Strange state" + this);
+ if (DBG) log("Strange state " + this);
return;
}
if (result >= 0) {
- acquireWakeLock();
mState = STATE_CONNECTED;
} else {
mState = STATE_CLOSED;
@@ -184,13 +177,13 @@ public class ScoSocket {
private void acquireWakeLock() {
if (!mWakeLock.isHeld()) {
mWakeLock.acquire();
- if (VDBG) log("mWakeLock.acquire()" + this);
+ if (VDBG) log("mWakeLock.acquire() " + this);
}
}
private void releaseWakeLock() {
if (mWakeLock.isHeld()) {
- if (VDBG) log("mWakeLock.release()" + this);
+ if (VDBG) log("mWakeLock.release() " + this);
mWakeLock.release();
}
}
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index ee08eea..08f6191 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -79,7 +79,7 @@ import android.util.Log;
* <p>The BroadcastReceiver class (when launched as a component through
* a manifest's {@link android.R.styleable#AndroidManifestReceiver &lt;receiver&gt;}
* tag) is an important part of an
- * <a href="{@docRoot}intro/lifecycle.html">application's overall lifecycle</a>.</p>
+ * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
*
* <p>Topics covered here:
* <ol>
@@ -135,7 +135,7 @@ import android.util.Log;
* tag in their <code>AndroidManifest.xml</code>) will be able to send an
* Intent to the receiver.
*
- * <p>See the <a href="{@docRoot}devel/security.html">Security Model</a>
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
* document for more information on permissions and security in general.
*
* <a name="ProcessLifecycle"></a>
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 226c5ab..3a64cee 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -41,8 +41,8 @@ import java.io.FileNotFoundException;
* multiple applications you can use a database directly via
* {@link android.database.sqlite.SQLiteDatabase}.
*
- * <p>See <a href="{@docRoot}devel/data/contentproviders.html">this page</a> for more information on
- * content providers.</p>
+ * <p>For more information, read <a href="{@docRoot}guide/topics/providers/content-providers.html">Content
+ * Providers</a>.</p>
*
* <p>When a request is made via
* a {@link ContentResolver} the system inspects the authority of the given URI and passes the
@@ -226,9 +226,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Return the name of the permission required for read-only access to
* this content provider. This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*/
public final String getReadPermission() {
return mReadPermission;
@@ -248,9 +248,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Return the name of the permission required for read/write access to
* this content provider. This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*/
public final String getWritePermission() {
return mWritePermission;
@@ -273,9 +273,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* Receives a query request from a client in a local process, and
* returns a Cursor. This is called internally by the {@link ContentResolver}.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
* <p>
* Example client call:<p>
* <pre>// Request a specific record.
@@ -330,9 +330,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* <code>vnd.android.cursor.item</code> for a single record,
* or <code>vnd.android.cursor.dir/</code> for multiple items.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @param uri the URI to query.
* @return a MIME type string, or null if there is no type.
@@ -344,9 +344,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after inserting.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
* @param uri The content:// URI of the insertion request.
* @param values A set of column_name/value pairs to add to the database.
* @return The URI for the newly inserted item.
@@ -359,9 +359,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after inserting.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @param uri The content:// URI of the insertion request.
* @param values An array of sets of column_name/value pairs to add to the database.
@@ -382,9 +382,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()}
* after deleting.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* <p>The implementation is responsible for parsing out a row ID at the end
* of the URI, if a specific row is being deleted. That is, the client would
@@ -405,9 +405,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after updating.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @param uri The URI to query. This can potentially have a record ID if this
* is an update request for a specific record.
@@ -422,9 +422,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Open a file blob associated with a content URI.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of the
- * Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* <p>Returns a
* ParcelFileDescriptor, from which you can obtain a
@@ -507,9 +507,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
* This is intended for use by the sync system. If null then this
* content provider is considered not syncable.
* This method can be called from multiple
- * threads, as described in the
- * <a href="{@docRoot}intro/appmodel.html#Threads">Threading section of
- * the Application Model overview</a>.
+ * threads, as described in
+ * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+ * Processes and Threads</a>.
*
* @return the SyncAdapter that is to be used by this ContentProvider, or null
* if this ContentProvider is not syncable
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c4d3f9d..c1c3b49 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1766,9 +1766,9 @@ public class Intent implements Parcelable {
* next task activity) defines an atomic group of activities that the
* user can move to. Tasks can be moved to the foreground and background;
* all of the activities inside of a particular task always remain in
- * the same order. See the
- * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for more details on tasks.
+ * the same order. See
+ * <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
*
* <p>This flag is generally used by activities that want
* to present a "launcher" style behavior: they give the user a list of
@@ -1801,9 +1801,8 @@ public class Intent implements Parcelable {
* <p>This flag is ignored if
* {@link #FLAG_ACTIVITY_NEW_TASK} is not set.
*
- * <p>See the
- * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for more details on tasks.
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
*/
public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000;
/**
@@ -1831,9 +1830,8 @@ public class Intent implements Parcelable {
* especially useful, for example, when launching an activity from the
* notification manager.
*
- * <p>See the
- * <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for more details on tasks.
+ * <p>See <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> for more details on tasks.
*/
public static final int FLAG_ACTIVITY_CLEAR_TOP = 0x04000000;
/**
@@ -3919,8 +3917,8 @@ public class Intent implements Parcelable {
* FLAG_RECEIVER_* flags are all for use with
* {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
*
- * <p>See the <a href="{@docRoot}intro/appmodel.html">Application Model</a>
- * documentation for important information on how some of these options impact
+ * <p>See the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals:
+ * Activities and Tasks</a> documentation for important information on how some of these options impact
* the behavior of your application.
*
* @param flags The desired flags.
@@ -4195,14 +4193,11 @@ public class Intent implements Parcelable {
@Override
public boolean equals(Object obj) {
- Intent other;
- try {
- other = ((FilterComparison)obj).mIntent;
- } catch (ClassCastException e) {
- return false;
+ if (obj instanceof FilterComparison) {
+ Intent other = ((FilterComparison)obj).mIntent;
+ return mIntent.filterEquals(other);
}
-
- return mIntent.filterEquals(other);
+ return false;
}
@Override
diff --git a/core/java/android/content/package.html b/core/java/android/content/package.html
index 7b3e8cf..dd5360f 100644
--- a/core/java/android/content/package.html
+++ b/core/java/android/content/package.html
@@ -50,9 +50,9 @@ an application's resources and transfer data between applications.</p>
<p>This topic includes a terminology list associated with resources, and a series
of examples of using resources in code. For a complete guide on creating and
- using resources, see the document on <a href="{@docRoot}devel/resources-i18n.html">Resources
+ using resources, see the document on <a href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources
and Internationalization</a>. For a reference on the supported Android resource types,
- see <a href="{@docRoot}reference/available-resources.html">Available Resource Types</a>.</p>
+ see <a href="{@docRoot}guide/topics/resources/available-resources.html">Available Resource Types</a>.</p>
<p>The Android resource system keeps track of all non-code
assets associated with an application. You use the
{@link android.content.res.Resources Resources} class to access your
@@ -175,7 +175,8 @@ download files with new appearances.</p>
<p>This section gives a few quick examples you can use to make your own resources.
For more details on how to define and use resources, see <a
- href="{@docRoot}devel/resources-i18n.html">Resources</a>. </p>
+ href="{@docRoot}guide/topics/resources/resources-i18n.html">Resources and
+ Internationalization</a>. </p>
<a name="UsingSystemResources"></a>
<h4>Using System Resources</h4>
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 994afc8..d9326f2 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -29,6 +29,20 @@ public class PackageInfo implements Parcelable {
public String versionName;
/**
+ * The shared user ID name of this package, as specified by the &lt;manifest&gt;
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserId sharedUserId}
+ * attribute.
+ */
+ public String sharedUserId;
+
+ /**
+ * The shared user ID label of this package, as specified by the &lt;manifest&gt;
+ * tag's {@link android.R.styleable#AndroidManifest_sharedUserLabel sharedUserLabel}
+ * attribute.
+ */
+ public int sharedUserLabel;
+
+ /**
* Information collected from the &lt;application&gt; tag, or null if
* there was none.
*/
@@ -130,6 +144,8 @@ public class PackageInfo implements Parcelable {
dest.writeString(packageName);
dest.writeInt(versionCode);
dest.writeString(versionName);
+ dest.writeString(sharedUserId);
+ dest.writeInt(sharedUserLabel);
if (applicationInfo != null) {
dest.writeInt(1);
applicationInfo.writeToParcel(dest, parcelableFlags);
@@ -163,6 +179,8 @@ public class PackageInfo implements Parcelable {
packageName = source.readString();
versionCode = source.readInt();
versionName = source.readString();
+ sharedUserId = source.readString();
+ sharedUserLabel = source.readInt();
int hasApp = source.readInt();
if (hasApp != 0) {
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e08f1d1..4ae8b08 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -101,6 +101,8 @@ public class PackageParser {
pi.packageName = p.packageName;
pi.versionCode = p.mVersionCode;
pi.versionName = p.mVersionName;
+ pi.sharedUserId = p.mSharedUserId;
+ pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = p.applicationInfo;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
@@ -585,6 +587,8 @@ public class PackageParser {
return null;
}
pkg.mSharedUserId = str.intern();
+ pkg.mSharedUserLabel = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
}
sa.recycle();
@@ -2045,6 +2049,9 @@ public class PackageParser {
// The shared user id that this package wants to use.
public String mSharedUserId;
+ // The shared user label that this package wants to use.
+ public int mSharedUserLabel;
+
// Signatures that were read from the package.
public Signature mSignatures[];
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 2ff7294..10f3806 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -84,6 +84,7 @@ public class DatabaseUtils {
code = 9;
} else {
reply.writeException(e);
+ Log.e(TAG, "Writing exception to parcel", e);
return;
}
reply.writeInt(code);
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index d9f10a9..52f8209 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -102,7 +102,7 @@ public class ExtractEditText extends EditText {
* highlight and cursor will be displayed.
*/
@Override public boolean hasWindowFocus() {
- return true;
+ return this.isEnabled() ? true : false;
}
/**
@@ -110,7 +110,7 @@ public class ExtractEditText extends EditText {
* highlight and cursor will be displayed.
*/
@Override public boolean isFocused() {
- return true;
+ return this.isEnabled() ? true : false;
}
/**
@@ -118,7 +118,6 @@ public class ExtractEditText extends EditText {
* highlight and cursor will be displayed.
*/
@Override public boolean hasFocus() {
- return true;
+ return this.isEnabled() ? true : false;
}
-
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c884120..4be1fc7 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -229,9 +229,11 @@ public class InputMethodService extends AbstractInputMethodService {
InputConnection mStartedInputConnection;
EditorInfo mInputEditorInfo;
+ int mShowInputFlags;
boolean mShowInputRequested;
boolean mLastShowInputRequested;
int mCandidatesVisibility;
+ CompletionInfo[] mCurCompletions;
boolean mShowInputForced;
@@ -328,6 +330,7 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public void hideSoftInput() {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
+ mShowInputFlags = 0;
mShowInputRequested = false;
mShowInputForced = false;
hideWindow();
@@ -338,7 +341,10 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public void showSoftInput(int flags) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
- onShowRequested(flags);
+ mShowInputFlags = 0;
+ if (onShowInputRequested(flags, false)) {
+ showWindow(true);
+ }
}
}
@@ -364,6 +370,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (!isEnabled()) {
return;
}
+ mCurCompletions = completions;
onDisplayCompletions(completions);
}
@@ -557,8 +564,9 @@ public class InputMethodService extends AbstractInputMethodService {
super.onConfigurationChanged(newConfig);
boolean visible = mWindowVisible;
+ int showFlags = mShowInputFlags;
boolean showingInput = mShowInputRequested;
- boolean showingForced = mShowInputForced;
+ CompletionInfo[] completions = mCurCompletions;
initViews();
mInputViewStarted = false;
mCandidatesViewStarted = false;
@@ -567,19 +575,24 @@ public class InputMethodService extends AbstractInputMethodService {
getCurrentInputEditorInfo(), true);
}
if (visible) {
- if (showingForced) {
- // If we are showing the full soft keyboard, then go through
- // this path to take care of current decisions about fullscreen
- // etc.
- onShowRequested(InputMethod.SHOW_FORCED|InputMethod.SHOW_EXPLICIT);
- } else if (showingInput) {
- // If we are showing the full soft keyboard, then go through
- // this path to take care of current decisions about fullscreen
- // etc.
- onShowRequested(InputMethod.SHOW_EXPLICIT);
- } else {
- // Otherwise just put it back for its candidates.
+ if (showingInput) {
+ // If we were last showing the soft keyboard, try to do so again.
+ if (onShowInputRequested(showFlags, true)) {
+ showWindow(true);
+ if (completions != null) {
+ mCurCompletions = completions;
+ onDisplayCompletions(completions);
+ }
+ } else {
+ hideWindow();
+ }
+ } else if (mCandidatesVisibility == View.VISIBLE) {
+ // If the candidates are currently visible, make sure the
+ // window is shown for them.
showWindow(false);
+ } else {
+ // Otherwise hide the window.
+ hideWindow();
}
}
}
@@ -1065,36 +1078,42 @@ public class InputMethodService extends AbstractInputMethodService {
* The system has decided that it may be time to show your input method.
* This is called due to a corresponding call to your
* {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}
- * method. The default implementation simply calls
- * {@link #showWindow(boolean)}, except if the
- * {@link InputMethod#SHOW_EXPLICIT InputMethod.SHOW_EXPLICIT} flag is
- * not set and the input method is running in fullscreen mode.
+ * method. The default implementation uses
+ * {@link #onEvaluateInputViewShown()}, {@link #onEvaluateFullscreenMode()},
+ * and the current configuration to decide whether the input view should
+ * be shown at this point.
*
* @param flags Provides additional information about the show request,
* as per {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}.
+ * @param configChange This is true if we are re-showing due to a
+ * configuration change.
+ * @return Returns true to indicate that the window should be shown.
*/
- public void onShowRequested(int flags) {
+ public boolean onShowInputRequested(int flags, boolean configChange) {
if (!onEvaluateInputViewShown()) {
- return;
+ return false;
}
if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {
- if (onEvaluateFullscreenMode()) {
+ if (!configChange && onEvaluateFullscreenMode()) {
// Don't show if this is not explicitly requested by the user and
// the input method is fullscreen. That would be too disruptive.
- return;
+ // However, we skip this change for a config change, since if
+ // the IME is already shown we do want to go into fullscreen
+ // mode at this point.
+ return false;
}
Configuration config = getResources().getConfiguration();
if (config.keyboard != Configuration.KEYBOARD_NOKEYS) {
// And if the device has a hard keyboard, even if it is
// currently hidden, don't show the input method implicitly.
// These kinds of devices don't need it that much.
- return;
+ return false;
}
}
if ((flags&InputMethod.SHOW_FORCED) != 0) {
mShowInputForced = true;
}
- showWindow(true);
+ return true;
}
public void showWindow(boolean showInput) {
@@ -1106,7 +1125,6 @@ public class InputMethodService extends AbstractInputMethodService {
+ " mInputStarted=" + mInputStarted);
boolean doShowInput = false;
boolean wasVisible = mWindowVisible;
- boolean wasCreated = mWindowCreated;
mWindowVisible = true;
if (!mShowInputRequested) {
if (mInputStarted) {
@@ -1240,6 +1258,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
mInputStarted = false;
mStartedInputConnection = null;
+ mCurCompletions = null;
}
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
@@ -1550,7 +1569,11 @@ public class InputMethodService extends AbstractInputMethodService {
eet.setInputType(inputType);
eet.setHint(mInputEditorInfo.hintText);
if (mExtractedText != null) {
+ eet.setEnabled(true);
eet.setExtractedText(mExtractedText);
+ } else {
+ eet.setEnabled(false);
+ eet.setText("");
}
} finally {
eet.finishInternalChanges();
@@ -1586,7 +1609,8 @@ public class InputMethodService extends AbstractInputMethodService {
p.println(" mShowInputRequested=" + mShowInputRequested
+ " mLastShowInputRequested=" + mLastShowInputRequested
- + " mShowInputForced=" + mShowInputForced);
+ + " mShowInputForced=" + mShowInputForced
+ + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags));
p.println(" mCandidatesVisibility=" + mCandidatesVisibility
+ " mFullscreenApplied=" + mFullscreenApplied
+ " mIsFullscreen=" + mIsFullscreen);
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index 228acbe..6a560ce 100755
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -132,7 +132,19 @@ public class Keyboard {
/** Keyboard mode, or zero, if none. */
private int mKeyboardMode;
+
+ // Variables for pre-computing nearest keys.
+ private static final int GRID_WIDTH = 10;
+ private static final int GRID_HEIGHT = 5;
+ private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
+ private int mCellWidth;
+ private int mCellHeight;
+ private int[][] mGridNeighbors;
+ private int mProximityThreshold;
+ /** Number of key widths from current touch point to search for nearest keys. */
+ private static float SEARCH_DISTANCE = 1.4f;
+
/**
* Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
* Some of the key size defaults can be overridden per row from what the {@link Keyboard}
@@ -637,6 +649,52 @@ public class Keyboard {
public int getShiftKeyIndex() {
return mShiftKeyIndex;
}
+
+ private void computeNearestNeighbors() {
+ // Round-up so we don't have any pixels outside the grid
+ mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
+ mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
+ mGridNeighbors = new int[GRID_SIZE][];
+ int[] indices = new int[mKeys.size()];
+ final int gridWidth = GRID_WIDTH * mCellWidth;
+ final int gridHeight = GRID_HEIGHT * mCellHeight;
+ for (int x = 0; x < gridWidth; x += mCellWidth) {
+ for (int y = 0; y < gridHeight; y += mCellHeight) {
+ int count = 0;
+ for (int i = 0; i < mKeys.size(); i++) {
+ final Key key = mKeys.get(i);
+ if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
+ key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
+ key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
+ < mProximityThreshold ||
+ key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
+ indices[count++] = i;
+ }
+ }
+ int [] cell = new int[count];
+ System.arraycopy(indices, 0, cell, 0, count);
+ mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
+ }
+ }
+ }
+
+ /**
+ * Returns the indices of the keys that are closest to the given point.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return the array of integer indices for the nearest keys to the given point. If the given
+ * point is out of range, then an array of size zero is returned.
+ */
+ public int[] getNearestKeys(int x, int y) {
+ if (mGridNeighbors == null) computeNearestNeighbors();
+ if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
+ int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
+ if (index < GRID_SIZE) {
+ return mGridNeighbors[index];
+ }
+ }
+ return new int[0];
+ }
protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
return new Row(res, this, parser);
@@ -738,6 +796,8 @@ public class Keyboard {
mDefaultVerticalGap = getDimensionOrFraction(a,
com.android.internal.R.styleable.Keyboard_verticalGap,
mDisplayHeight, 0);
+ mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
+ mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
a.recycle();
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index b8bd10d..886e688 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -159,6 +159,9 @@ public class KeyboardView extends View implements View.OnClickListener {
private static final int MSG_REPEAT = 3;
private static final int MSG_LONGPRESS = 4;
+ private static final int DELAY_BEFORE_PREVIEW = 70;
+ private static final int DELAY_AFTER_PREVIEW = 60;
+
private int mVerticalCorrection;
private int mProximityThreshold;
@@ -219,7 +222,7 @@ public class KeyboardView extends View implements View.OnClickListener {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SHOW_PREVIEW:
- mPreviewText.setVisibility(VISIBLE);
+ showKey(msg.arg1);
break;
case MSG_REMOVE_PREVIEW:
mPreviewText.setVisibility(INVISIBLE);
@@ -234,7 +237,6 @@ public class KeyboardView extends View implements View.OnClickListener {
openPopupIfRequired((MotionEvent) msg.obj);
break;
}
-
}
};
@@ -533,10 +535,10 @@ public class KeyboardView extends View implements View.OnClickListener {
dimensionSum += Math.min(key.width, key.height) + key.gap;
}
if (dimensionSum < 0 || length == 0) return;
- mProximityThreshold = (int) (dimensionSum * 1.5f / length);
+ mProximityThreshold = (int) (dimensionSum * 1.4f / length);
mProximityThreshold *= mProximityThreshold; // Square it
}
-
+
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -647,9 +649,10 @@ public class KeyboardView extends View implements View.OnClickListener {
int closestKey = NOT_A_KEY;
int closestKeyDist = mProximityThreshold + 1;
java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
- final int keyCount = keys.length;
+ int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+ final int keyCount = nearestKeyIndices.length;
for (int i = 0; i < keyCount; i++) {
- final Key key = keys[i];
+ final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
boolean isInside = key.isInside(x,y);
if (((mProximityCorrectOn
@@ -660,7 +663,7 @@ public class KeyboardView extends View implements View.OnClickListener {
final int nCodes = key.codes.length;
if (dist < closestKeyDist) {
closestKeyDist = dist;
- closestKey = i;
+ closestKey = nearestKeyIndices[i];
}
if (allKeys == null) continue;
@@ -674,9 +677,6 @@ public class KeyboardView extends View implements View.OnClickListener {
allKeys.length - j - nCodes);
for (int c = 0; c < nCodes; c++) {
allKeys[j + c] = key.codes[c];
- if (shifted) {
- //allKeys[j + c] = Character.toUpperCase(key.codes[c]);
- }
mDistances[j + c] = dist;
}
break;
@@ -685,7 +685,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
if (isInside) {
- primaryIndex = i;
+ primaryIndex = nearestKeyIndices[i];
}
}
if (primaryIndex == NOT_A_KEY) {
@@ -696,7 +696,7 @@ public class KeyboardView extends View implements View.OnClickListener {
private void detectAndSendKey(int x, int y, long eventTime) {
int index = mCurrentKey;
- if (index != NOT_A_KEY) {
+ if (index != NOT_A_KEY && index < mKeys.length) {
final Key key = mKeys[index];
if (key.text != null) {
for (int i = 0; i < key.text.length(); i++) {
@@ -763,70 +763,83 @@ public class KeyboardView extends View implements View.OnClickListener {
if (previewPopup.isShowing()) {
if (keyIndex == NOT_A_KEY) {
mHandler.sendMessageDelayed(mHandler
- .obtainMessage(MSG_REMOVE_PREVIEW), 60);
+ .obtainMessage(MSG_REMOVE_PREVIEW),
+ DELAY_AFTER_PREVIEW);
}
}
if (keyIndex != NOT_A_KEY) {
- Key key = keys[keyIndex];
- if (key.icon != null) {
- mPreviewText.setCompoundDrawables(null, null, null,
- key.iconPreview != null ? key.iconPreview : key.icon);
- mPreviewText.setText(null);
- } else {
- mPreviewText.setCompoundDrawables(null, null, null, null);
- mPreviewText.setText(getPreviewText(key));
- if (key.label.length() > 1 && key.codes.length < 2) {
- mPreviewText.setTextSize(mLabelTextSize);
- mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
- } else {
- mPreviewText.setTextSize(mPreviewTextSizeLarge);
- mPreviewText.setTypeface(Typeface.DEFAULT);
- }
- }
- mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
- + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
- final int popupHeight = mPreviewHeight;
- LayoutParams lp = mPreviewText.getLayoutParams();
- if (lp != null) {
- lp.width = popupWidth;
- lp.height = popupHeight;
- }
- if (!mPreviewCentered) {
- mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
- mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
- } else {
- // TODO: Fix this if centering is brought back
- mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
- mPopupPreviewY = - mPreviewText.getMeasuredHeight();
- }
- mHandler.removeMessages(MSG_REMOVE_PREVIEW);
- if (mOffsetInWindow == null) {
- mOffsetInWindow = new int[2];
- getLocationInWindow(mOffsetInWindow);
- mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
- }
- // Set the preview background state
- mPreviewText.getBackground().setState(
- key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- if (previewPopup.isShowing()) {
- previewPopup.update(mPopupPreviewX + mOffsetInWindow[0],
- mPopupPreviewY + mOffsetInWindow[1],
- popupWidth, popupHeight);
+ if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+ // Show right away, if it's already visible and finger is moving around
+ showKey(keyIndex);
} else {
- previewPopup.setWidth(popupWidth);
- previewPopup.setHeight(popupHeight);
- previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
- mPopupPreviewX + mOffsetInWindow[0],
- mPopupPreviewY + mOffsetInWindow[1]);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
+ DELAY_BEFORE_PREVIEW);
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
- ViewConfiguration.getTapTimeout());
}
}
}
+
+ private void showKey(final int keyIndex) {
+ final PopupWindow previewPopup = mPreviewPopup;
+ final Key[] keys = mKeys;
+ Key key = keys[keyIndex];
+ if (key.icon != null) {
+ mPreviewText.setCompoundDrawables(null, null, null,
+ key.iconPreview != null ? key.iconPreview : key.icon);
+ mPreviewText.setText(null);
+ } else {
+ mPreviewText.setCompoundDrawables(null, null, null, null);
+ mPreviewText.setText(getPreviewText(key));
+ if (key.label.length() > 1 && key.codes.length < 2) {
+ mPreviewText.setTextSize(mLabelTextSize);
+ mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+ } else {
+ mPreviewText.setTextSize(mPreviewTextSizeLarge);
+ mPreviewText.setTypeface(Typeface.DEFAULT);
+ }
+ }
+ mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
+ + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+ final int popupHeight = mPreviewHeight;
+ LayoutParams lp = mPreviewText.getLayoutParams();
+ if (lp != null) {
+ lp.width = popupWidth;
+ lp.height = popupHeight;
+ }
+ if (!mPreviewCentered) {
+ mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + mPaddingLeft;
+ mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
+ } else {
+ // TODO: Fix this if centering is brought back
+ mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
+ mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+ }
+ mHandler.removeMessages(MSG_REMOVE_PREVIEW);
+ if (mOffsetInWindow == null) {
+ mOffsetInWindow = new int[2];
+ getLocationInWindow(mOffsetInWindow);
+ mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
+ mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
+ }
+ // Set the preview background state
+ mPreviewText.getBackground().setState(
+ key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+ if (previewPopup.isShowing()) {
+ previewPopup.update(mPopupPreviewX + mOffsetInWindow[0],
+ mPopupPreviewY + mOffsetInWindow[1],
+ popupWidth, popupHeight);
+ } else {
+ previewPopup.setWidth(popupWidth);
+ previewPopup.setHeight(popupHeight);
+ previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+ mPopupPreviewX + mOffsetInWindow[0],
+ mPopupPreviewY + mOffsetInWindow[1]);
+ }
+ mPreviewText.setVisibility(VISIBLE);
+ }
private void invalidateKey(int keyIndex) {
if (keyIndex < 0 || keyIndex >= mKeys.length) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 017b14d..6f9d6c6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1,18 +1,20 @@
package android.os;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Formatter;
import java.util.Map;
+import android.util.Log;
+import android.util.Printer;
import android.util.SparseArray;
/**
* A class providing access to battery usage statistics, including information on
* wakelocks, processes, packages, and services. All times are represented in microseconds
* except where indicated otherwise.
+ * @hide
*/
-public abstract class BatteryStats {
+public abstract class BatteryStats implements Parcelable {
/**
* A constant indicating a partial wake lock timer.
@@ -96,6 +98,11 @@ public abstract class BatteryStats {
* @return a time in microseconds
*/
public abstract long getTotalTime(long now, int which);
+
+ /**
+ * Temporary for debugging.
+ */
+ public abstract void logState();
}
/**
@@ -154,10 +161,10 @@ public abstract class BatteryStats {
public abstract long getTcpBytesSent(int which);
public static abstract class Sensor {
- /**
- * {@hide}
- */
- public abstract String getName();
+ // Magic sensor number for the GPS.
+ public static final int GPS = -10000;
+
+ public abstract int getHandle();
public abstract Timer getSensorTime();
}
@@ -260,6 +267,11 @@ public abstract class BatteryStats {
public abstract long getPluggedScreenOnTime();
/**
+ * Return whether we are currently running on battery.
+ */
+ public abstract boolean getIsOnBattery();
+
+ /**
* Returns a SparseArray containing the statistics for each uid.
*/
public abstract SparseArray<? extends Uid> getUidStats();
@@ -457,7 +469,7 @@ public abstract class BatteryStats {
* @param pw
* @param which
*/
- private final void dumpCheckinLocked(FileDescriptor fd, PrintWriter pw, int which) {
+ private final void dumpCheckinLocked(PrintWriter pw, int which) {
long uSecTime = SystemClock.elapsedRealtime() * 1000;
final long uSecNow = getBatteryUptime(uSecTime);
@@ -578,35 +590,18 @@ public abstract class BatteryStats {
}
@SuppressWarnings("unused")
- private final void dumpLocked(FileDescriptor fd, PrintWriter pw, String prefix, int which) {
+ private final void dumpLocked(Printer pw, String prefix, int which) {
long uSecTime = SystemClock.elapsedRealtime() * 1000;
final long uSecNow = getBatteryUptime(uSecTime);
StringBuilder sb = new StringBuilder(128);
- switch (which) {
- case STATS_TOTAL:
- pw.println(prefix + "Current and Historic Battery Usage Statistics:");
- pw.println(prefix + " System starts: " + getStartCount());
- break;
- case STATS_LAST:
- pw.println(prefix + "Last Battery Usage Statistics:");
- break;
- case STATS_UNPLUGGED:
- pw.println(prefix + "Last Unplugged Battery Usage Statistics:");
- break;
- case STATS_CURRENT:
- pw.println(prefix + "Current Battery Usage Statistics:");
- break;
- default:
- throw new IllegalArgumentException("which = " + which);
- }
long batteryUptime = computeBatteryUptime(uSecNow, which);
long batteryRealtime = computeBatteryRealtime(getBatteryRealtime(uSecTime), which);
long elapsedRealtime = computeRealtime(uSecTime, which);
long uptime = computeUptime(SystemClock.uptimeMillis() * 1000, which);
pw.println(prefix
- + " On battery: " + formatTimeMs(batteryUptime / 1000) + "("
+ + " Time on battery: " + formatTimeMs(batteryUptime / 1000) + "("
+ formatRatioLocked(batteryUptime, elapsedRealtime)
+ ") uptime, "
+ formatTimeMs(batteryRealtime / 1000) + "("
@@ -618,7 +613,7 @@ public abstract class BatteryStats {
+ "uptime, "
+ formatTimeMs(elapsedRealtime / 1000)
+ "realtime");
-
+
pw.println(" ");
SparseArray<? extends Uid> uidStats = getUidStats();
@@ -629,8 +624,12 @@ public abstract class BatteryStats {
pw.println(prefix + " #" + uid + ":");
boolean uidActivity = false;
- pw.println(prefix + " Network: " + u.getTcpBytesReceived(which) + " bytes received, "
- + u.getTcpBytesSent(which) + " bytes sent");
+ long tcpReceived = u.getTcpBytesReceived(which);
+ long tcpSent = u.getTcpBytesSent(which);
+ if (tcpReceived != 0 || tcpSent != 0) {
+ pw.println(prefix + " Network: " + tcpReceived + " bytes received, "
+ + tcpSent + " bytes sent");
+ }
Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
if (wakelocks.size() > 0) {
@@ -648,7 +647,9 @@ public abstract class BatteryStats {
"partial", which, linePrefix);
linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), uSecNow,
"window", which, linePrefix);
- if (linePrefix.equals(": ")) {
+ if (!linePrefix.equals(": ")) {
+ sb.append(" realtime");
+ } else {
sb.append(": (nothing executed)");
}
pw.println(sb.toString());
@@ -665,23 +666,30 @@ public abstract class BatteryStats {
sb.setLength(0);
sb.append(prefix);
sb.append(" Sensor ");
- sb.append(sensorNumber);
+ int handle = se.getHandle();
+ if (handle == Uid.Sensor.GPS) {
+ sb.append("GPS");
+ } else {
+ sb.append(handle);
+ }
+ sb.append(": ");
Timer timer = se.getSensorTime();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
long totalTime = (timer.getTotalTime(uSecNow, which) + 500) / 1000;
int count = timer.getCount(which);
+ //timer.logState();
if (totalTime != 0) {
- sb.append(": ");
sb.append(formatTimeMs(totalTime));
- sb.append(' ');
- sb.append('(');
+ sb.append("realtime (");
sb.append(count);
sb.append(" times)");
+ } else {
+ sb.append("(not used)");
}
} else {
- sb.append(": (none used)");
+ sb.append("(not used)");
}
pw.println(sb.toString());
@@ -734,8 +742,9 @@ public abstract class BatteryStats {
int launches = ss.getLaunches(which);
if (startTime != 0 || starts != 0 || launches != 0) {
pw.println(prefix + " Service " + sent.getKey() + ":");
- pw.println(prefix + " Time spent started: "
- + formatTimeMs(startTime / 1000));
+ pw.println(prefix + " Created for: "
+ + formatTimeMs(startTime / 1000)
+ + " uptime");
pw.println(prefix + " Starts: " + starts
+ ", launches: " + launches);
apkActivity = true;
@@ -757,36 +766,30 @@ public abstract class BatteryStats {
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
- * @param fd a FileDescriptor, currently unused.
- * @param pw a PrintWriter to receive the dump output.
- * @param args an array of Strings, currently unused.
+ * @param pw a Printer to receive the dump output.
*/
@SuppressWarnings("unused")
- public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
- boolean isCheckin = false;
- if (args != null) {
- for (String arg : args) {
- if ("-c".equals(arg)) {
- isCheckin = true;
- break;
- }
- }
- }
- synchronized (this) {
- if (isCheckin) {
- dumpCheckinLocked(fd, pw, STATS_TOTAL);
- dumpCheckinLocked(fd, pw, STATS_LAST);
- dumpCheckinLocked(fd, pw, STATS_UNPLUGGED);
- dumpCheckinLocked(fd, pw, STATS_CURRENT);
- } else {
- dumpLocked(fd, pw, "", STATS_TOTAL);
- pw.println("");
- dumpLocked(fd, pw, "", STATS_LAST);
- pw.println("");
- dumpLocked(fd, pw, "", STATS_UNPLUGGED);
- pw.println("");
- dumpLocked(fd, pw, "", STATS_CURRENT);
- }
- }
+ public void dumpLocked(Printer pw) {
+ pw.println("Total Statistics (Current and Historic):");
+ pw.println(" System starts: " + getStartCount()
+ + ", currently on battery: " + getIsOnBattery());
+ dumpLocked(pw, "", STATS_TOTAL);
+ pw.println("");
+ pw.println("Last Run Statistics (Previous run of system):");
+ dumpLocked(pw, "", STATS_LAST);
+ pw.println("");
+ pw.println("Current Battery Statistics (Currently running system):");
+ dumpLocked(pw, "", STATS_CURRENT);
+ pw.println("");
+ pw.println("Unplugged Statistics (Since last unplugged from power):");
+ dumpLocked(pw, "", STATS_UNPLUGGED);
+ }
+
+ @SuppressWarnings("unused")
+ public void dumpCheckinLocked(PrintWriter pw, String[] args) {
+ dumpCheckinLocked(pw, STATS_TOTAL);
+ dumpCheckinLocked(pw, STATS_LAST);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ dumpCheckinLocked(pw, STATS_CURRENT);
}
}
diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
index 11becc4..e56b55d 100644
--- a/core/java/android/os/ICheckinService.aidl
+++ b/core/java/android/os/ICheckinService.aidl
@@ -26,7 +26,13 @@ import android.os.IParentalControlCallback;
* {@hide}
*/
interface ICheckinService {
- /** Synchronously attempt a checkin with the server, return true on success. */
+ /** Synchronously attempt a checkin with the server, return true
+ * on success.
+ * @throws IllegalStateException whenever an error occurs. The
+ * cause of the exception will be the real exception:
+ * IOException for network errors, JSONException for invalid
+ * server responses, etc.
+ */
boolean checkin();
/** Direct submission of crash data; returns after writing the crash. */
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index e48f152..5486920 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -29,4 +29,5 @@ interface IPowerManager
void setStayOnSetting(int val);
long getScreenOnTime();
void preventScreenOn(boolean prevent);
+ void setScreenBrightnessOverride(int brightness);
}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 02ab1da..14c0054 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -242,7 +242,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
mHasReturnedViewTypeCount = true;
}
- return mPreferenceClassNames.size();
+ return Math.max(1, mPreferenceClassNames.size());
}
}
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
index 5767c65..688e19a 100644
--- a/core/java/android/provider/Checkin.java
+++ b/core/java/android/provider/Checkin.java
@@ -97,6 +97,7 @@ public final class Checkin {
SETUP_RETRIES_EXHAUSTED,
SETUP_SERVER_ERROR,
SETUP_SERVER_TIMEOUT,
+ SETUP_NO_DATA_NETWORK,
SYSTEM_APP_NOT_RESPONDING,
SYSTEM_BOOT,
SYSTEM_LAST_KMSG,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c6a7b40..4a784c8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2321,6 +2321,13 @@ public final class Settings {
public static final String GMAIL_SEND_IMMEDIATELY = "gmail_send_immediately";
/**
+ * Controls whether gmail buffers server responses. Possible values are "memory", for a
+ * memory-based buffer, or "file", for a temp-file-based buffer. All other values
+ * (including not set) disable buffering.
+ */
+ public static final String GMAIL_BUFFER_SERVER_RESPONSE = "gmail_buffer_server_response";
+
+ /**
* Hostname of the GTalk server.
*/
public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
index 7c15045..86d5a1e 100644
--- a/core/java/android/server/BluetoothDeviceService.java
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -111,9 +111,19 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native int isEnabledNative();
/**
- * Disable bluetooth. Returns true on success.
+ * Bring down bluetooth and disable BT in settings. Returns true on success.
*/
- public synchronized boolean disable() {
+ public boolean disable() {
+ return disable(true);
+ }
+
+ /**
+ * Bring down bluetooth. Returns true on success.
+ *
+ * @param saveSetting If true, disable BT in settings
+ *
+ */
+ public synchronized boolean disable(boolean saveSetting) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -137,7 +147,9 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
mIsEnabled = false;
- persistBluetoothOnSetting(false);
+ if (saveSetting) {
+ persistBluetoothOnSetting(false);
+ }
mIsDiscovering = false;
intent = new Intent(BluetoothIntent.DISABLED_ACTION);
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
@@ -145,13 +157,27 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
/**
+ * Bring up bluetooth, asynchronously, and enable BT in settings.
+ * This turns on/off the underlying hardware.
+ *
+ * @return True on success (so far), guaranteeing the callback with be
+ * notified when complete.
+ */
+ public boolean enable(IBluetoothDeviceCallback callback) {
+ return enable(callback, true);
+ }
+
+ /**
* Enable this Bluetooth device, asynchronously.
* This turns on/off the underlying hardware.
*
- * @return True on success (so far), guarenteeing the callback with be
+ * @param saveSetting If true, enable BT in settings
+ *
+ * @return True on success (so far), guaranteeing the callback with be
* notified when complete.
*/
- public synchronized boolean enable(IBluetoothDeviceCallback callback) {
+ public synchronized boolean enable(IBluetoothDeviceCallback callback,
+ boolean saveSetting) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -165,7 +191,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
if (mEnableThread != null && mEnableThread.isAlive()) {
return false;
}
- mEnableThread = new EnableThread(callback);
+ mEnableThread = new EnableThread(callback, saveSetting);
mEnableThread.start();
return true;
}
@@ -189,8 +215,10 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private class EnableThread extends Thread {
private final IBluetoothDeviceCallback mEnableCallback;
- public EnableThread(IBluetoothDeviceCallback callback) {
+ private final boolean mSaveSetting;
+ public EnableThread(IBluetoothDeviceCallback callback, boolean saveSetting) {
mEnableCallback = callback;
+ mSaveSetting = saveSetting;
}
public void run() {
boolean res = (enableNative() == 0);
@@ -208,7 +236,9 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
if (res) {
mIsEnabled = true;
- persistBluetoothOnSetting(true);
+ if (mSaveSetting) {
+ persistBluetoothOnSetting(true);
+ }
mIsDiscovering = false;
Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION);
mBondState.loadBondState();
@@ -952,9 +982,9 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
// If bluetooth is currently expected to be on, then enable or disable bluetooth
if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
if (enabled) {
- enable(null);
+ enable(null, false);
} else {
- disable();
+ disable(false);
}
}
}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index b5e4090..187ec2c 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -359,10 +359,10 @@ class BluetoothEventLoop {
private void onGetRemoteServiceChannelResult(String address, int channel) {
IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
if (callback != null) {
+ mGetRemoteServiceChannelCallbacks.remove(address);
try {
callback.onGetRemoteServiceChannelResult(address, channel);
} catch (RemoteException e) {}
- mGetRemoteServiceChannelCallbacks.remove(address);
}
}
diff --git a/core/java/android/speech/srec/MicrophoneInputStream.java b/core/java/android/speech/srec/MicrophoneInputStream.java
index 160a003..fab77a9 100644
--- a/core/java/android/speech/srec/MicrophoneInputStream.java
+++ b/core/java/android/speech/srec/MicrophoneInputStream.java
@@ -1,5 +1,5 @@
/*---------------------------------------------------------------------------*
- * MicrophoneInputStream.java *
+ * MicrophoneInputStream.java *
* *
* Copyright 2007 Nuance Communciations, Inc. *
* *
@@ -45,8 +45,12 @@ public final class MicrophoneInputStream extends InputStream {
*/
public MicrophoneInputStream(int sampleRate, int fifoDepth) throws IOException {
mAudioRecord = AudioRecordNew(sampleRate, fifoDepth);
- if (mAudioRecord == 0) throw new IllegalStateException("not open");
- AudioRecordStart(mAudioRecord);
+ if (mAudioRecord == 0) throw new IOException("AudioRecord constructor failed - busy?");
+ int status = AudioRecordStart(mAudioRecord);
+ if (status != 0) {
+ close();
+ throw new IOException("AudioRecord start failed: " + status);
+ }
}
@Override
@@ -99,7 +103,7 @@ public final class MicrophoneInputStream extends InputStream {
// AudioRecord JNI interface
//
private static native int AudioRecordNew(int sampleRate, int fifoDepth);
- private static native void AudioRecordStart(int audioRecord);
+ private static native int AudioRecordStart(int audioRecord);
private static native int AudioRecordRead(int audioRecord, byte[] b, int offset, int length) throws IOException;
private static native void AudioRecordStop(int audioRecord) throws IOException;
private static native void AudioRecordDelete(int audioRecord) throws IOException;
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index 5bf9b20..daa99c2 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -394,6 +394,7 @@ public class Time {
*
* @param s the string to parse
* @return true if the resulting time value is in UTC time
+ * @throws android.util.TimeFormatException if s cannot be parsed.
*/
public boolean parse(String s) {
if (nativeParse(s)) {
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index 2eebc0d..efb88a0 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -44,8 +44,9 @@ public class ImageSpan extends DynamicDrawableSpan {
public ImageSpan(Bitmap b, int verticalAlignment) {
super(verticalAlignment);
mDrawable = new BitmapDrawable(b);
- mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(),
- mDrawable.getIntrinsicHeight());
+ int width = mDrawable.getIntrinsicWidth();
+ int height = mDrawable.getIntrinsicHeight();
+ mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
}
public ImageSpan(Drawable d) {
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index 610cfd4..9ab3b53 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -73,13 +73,20 @@ public class SparseIntArray {
int i = binarySearch(mKeys, 0, mSize, key);
if (i >= 0) {
- System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
- System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1));
- mSize--;
+ removeAt(i);
}
}
/**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+ System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+ mSize--;
+ }
+
+ /**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
* was one.
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index a472689..679c683 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -163,7 +163,7 @@ public class GestureDetector {
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
// 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.getJumpTapTimeout();
+ private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
@@ -174,11 +174,13 @@ public class GestureDetector {
private final OnGestureListener mListener;
private OnDoubleTapListener mDoubleTapListener;
+ private boolean mStillDown;
private boolean mInLongPress;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
private MotionEvent mCurrentDownEvent;
+ private MotionEvent mPreviousUpEvent;
/**
* True when the user is still touching for the second tap (down, move, and
@@ -217,7 +219,8 @@ public class GestureDetector {
break;
case TAP:
- if (mDoubleTapListener != null) {
+ // If the user's finger is still down, do not count it as a tap
+ if (mDoubleTapListener != null && !mStillDown) {
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
}
break;
@@ -378,8 +381,10 @@ public class GestureDetector {
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
- mHandler.removeMessages(TAP);
- if (mCurrentDownEvent != null && isConsideredDoubleTap(mCurrentDownEvent, ev)) {
+ boolean hadTapMessage = mHandler.hasMessages(TAP);
+ if (hadTapMessage) mHandler.removeMessages(TAP);
+ if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
+ isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
handled = mDoubleTapListener.onDoubleTapEvent(ev);
@@ -394,6 +399,7 @@ public class GestureDetector {
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
+ mStillDown = true;
mInLongPress = false;
if (mIsLongpressEnabled) {
@@ -422,6 +428,7 @@ public class GestureDetector {
mLastMotionX = x;
mLastMotionY = y;
mAlwaysInTapRegion = false;
+ mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
@@ -436,6 +443,7 @@ public class GestureDetector {
break;
case MotionEvent.ACTION_UP:
+ mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mIsDoubleTapping) {
handled = mDoubleTapListener.onDoubleTapEvent(ev);
@@ -461,6 +469,7 @@ public class GestureDetector {
handled = mListener.onFling(mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
}
}
+ mPreviousUpEvent = MotionEvent.obtain(ev);
mVelocityTracker.recycle();
mVelocityTracker = null;
mHandler.removeMessages(SHOW_PRESS);
@@ -472,6 +481,7 @@ public class GestureDetector {
mHandler.removeMessages(TAP);
mVelocityTracker.recycle();
mVelocityTracker = null;
+ mStillDown = false;
if (mInLongPress) {
mInLongPress = false;
break;
@@ -480,12 +490,13 @@ public class GestureDetector {
return handled;
}
- private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent secondDown) {
+ private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
+ MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
- if (secondDown.getEventTime() - firstDown.getEventTime() > DOUBLE_TAP_TIMEOUT) {
+ if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
return false;
}
@@ -495,6 +506,7 @@ public class GestureDetector {
}
private void dispatchLongPress() {
+ mHandler.removeMessages(TAP);
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java
new file mode 100755
index 0000000..cddec11
--- /dev/null
+++ b/core/java/android/view/OrientationEventListener.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * Helper class for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ */
+public abstract class OrientationEventListener {
+ private static final String TAG = "OrientationEventListener";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private int mOrientation = ORIENTATION_UNKNOWN;
+ private SensorManager mSensorManager;
+ private boolean mEnabled = false;
+ private int mRate;
+ private Sensor mSensor;
+ private SensorEventListener mSensorEventListener;
+ private OrientationListener mOldListener;
+
+ /**
+ * Returned from onOrientationChanged when the device orientation cannot be determined
+ * (typically when the device is in a close to flat position).
+ *
+ * @see #onOrientationChanged
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+
+ /**
+ * Creates a new OrientationEventListener.
+ *
+ * @param context for the OrientationEventListener.
+ */
+ public OrientationEventListener(Context context) {
+ this(context, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ /**
+ * Creates a new OrientationEventListener.
+ *
+ * @param context for the OrientationEventListener.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ */
+ public OrientationEventListener(Context context, int rate) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
+ mSensorEventListener = new SensorEventListenerImpl();
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ }
+
+ void registerListener(OrientationListener lis) {
+ mOldListener = lis;
+ }
+
+ /**
+ * Enables the OrientationEventListener so it will monitor the sensor and call
+ * {@link #onOrientationChanged} when the device orientation changes.
+ */
+ public void enable() {
+ if (mEnabled == false) {
+ if (localLOGV) Log.d(TAG, "OrientationEventListener enabled");
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+ mEnabled = true;
+ }
+ }
+
+ /**
+ * Disables the OrientationEventListener.
+ */
+ public void disable() {
+ if (mEnabled == true) {
+ if (localLOGV) Log.d(TAG, "OrientationEventListener disabled");
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mEnabled = false;
+ }
+ }
+
+ class SensorEventListenerImpl implements SensorEventListener {
+ private static final int _DATA_X = 0;
+ private static final int _DATA_Y = 1;
+ private static final int _DATA_Z = 2;
+
+ public void onSensorChanged(SensorEvent event) {
+ float[] values = event.values;
+ int orientation = ORIENTATION_UNKNOWN;
+ float X = -values[_DATA_X];
+ float Y = -values[_DATA_Y];
+ float Z = -values[_DATA_Z];
+ float magnitude = X*X + Y*Y;
+ // Don't trust the angle if the magnitude is small compared to the y value
+ if (magnitude * 4 >= Z*Z) {
+ float OneEightyOverPi = 57.29577957855f;
+ float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
+ orientation = 90 - (int)Math.round(angle);
+ // normalize to 0 - 359 range
+ while (orientation >= 360) {
+ orientation -= 360;
+ }
+ while (orientation < 0) {
+ orientation += 360;
+ }
+ }
+ if (mOldListener != null) {
+ mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values);
+ }
+ if (orientation != mOrientation) {
+ mOrientation = orientation;
+ onOrientationChanged(orientation);
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ }
+
+ /**
+ * Called when the orientation of the device has changed.
+ * orientation parameter is in degrees, ranging from 0 to 359.
+ * orientation is 0 degrees when the device is oriented in its natural position,
+ * 90 degrees when its left side is at the top, 180 degrees when it is upside down,
+ * and 270 degrees when its right side is to the top.
+ * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+ * and the orientation cannot be determined.
+ *
+ * @param orientation The new orientation of the device.
+ *
+ * @see #ORIENTATION_UNKNOWN
+ */
+ abstract public void onOrientationChanged(int orientation);
+}
diff --git a/core/java/android/view/OrientationListener.java b/core/java/android/view/OrientationListener.java
index 974c2e8..ce8074e 100644
--- a/core/java/android/view/OrientationListener.java
+++ b/core/java/android/view/OrientationListener.java
@@ -18,23 +18,16 @@ package android.view;
import android.content.Context;
import android.hardware.SensorListener;
-import android.hardware.SensorManager;
-import android.util.Config;
-import android.util.Log;
/**
* Helper class for receiving notifications from the SensorManager when
* the orientation of the device has changed.
+ * @deprecated use {@link android.view.OrientationEventListener} instead.
+ * This class internally uses the OrientationEventListener.
*/
+@Deprecated
public abstract class OrientationListener implements SensorListener {
-
- private static final String TAG = "OrientationListener";
- private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
- private SensorManager mSensorManager;
- private int mOrientation = ORIENTATION_UNKNOWN;
- private boolean mEnabled = false;
- private int mRate;
+ private OrientationEventListener mOrientationEventLis;
/**
* Returned from onOrientationChanged when the device orientation cannot be determined
@@ -42,7 +35,7 @@ public abstract class OrientationListener implements SensorListener {
*
* @see #onOrientationChanged
*/
- public static final int ORIENTATION_UNKNOWN = -1;
+ public static final int ORIENTATION_UNKNOWN = OrientationEventListener.ORIENTATION_UNKNOWN;
/**
* Creates a new OrientationListener.
@@ -50,8 +43,7 @@ public abstract class OrientationListener implements SensorListener {
* @param context for the OrientationListener.
*/
public OrientationListener(Context context) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mRate = SensorManager.SENSOR_DELAY_NORMAL;
+ mOrientationEventLis = new OrientationEventListenerInternal(context);
}
/**
@@ -64,78 +56,55 @@ public abstract class OrientationListener implements SensorListener {
* SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
*/
public OrientationListener(Context context, int rate) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mRate = rate;
+ mOrientationEventLis = new OrientationEventListenerInternal(context, rate);
}
-
+
+ class OrientationEventListenerInternal extends OrientationEventListener {
+ OrientationEventListenerInternal(Context context) {
+ super(context);
+ }
+
+ OrientationEventListenerInternal(Context context, int rate) {
+ super(context, rate);
+ // register so that onSensorChanged gets invoked
+ registerListener(OrientationListener.this);
+ }
+
+ public void onOrientationChanged(int orientation) {
+ OrientationListener.this.onOrientationChanged(orientation);
+ }
+ }
+
/**
* Enables the OrientationListener so it will monitor the sensor and call
* {@link #onOrientationChanged} when the device orientation changes.
*/
public void enable() {
- if (mEnabled == false) {
- if (localLOGV) Log.d(TAG, "OrientationListener enabled");
- mSensorManager.registerListener(this, SensorManager.SENSOR_ACCELEROMETER, mRate);
- mEnabled = true;
- }
+ mOrientationEventLis.enable();
}
/**
* Disables the OrientationListener.
*/
public void disable() {
- if (mEnabled == true) {
- if (localLOGV) Log.d(TAG, "OrientationListener disabled");
- mSensorManager.unregisterListener(this);
- mEnabled = false;
- }
+ mOrientationEventLis.disable();
}
-
- /**
- *
- */
+
+ public void onAccuracyChanged(int sensor, int accuracy) {
+ }
+
public void onSensorChanged(int sensor, float[] values) {
- int orientation = ORIENTATION_UNKNOWN;
- float X = values[SensorManager.RAW_DATA_X];
- float Y = values[SensorManager.RAW_DATA_Y];
- float Z = values[SensorManager.RAW_DATA_Z];
- float magnitude = X*X + Y*Y;
- // Don't trust the angle if the magnitude is small compared to the y value
- if (magnitude * 4 >= Z*Z) {
- float OneEightyOverPi = 57.29577957855f;
- float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
- orientation = 90 - (int)Math.round(angle);
- // normalize to 0 - 359 range
- while (orientation >= 360) {
- orientation -= 360;
- }
- while (orientation < 0) {
- orientation += 360;
- }
- }
-
- if (orientation != mOrientation) {
- mOrientation = orientation;
- onOrientationChanged(orientation);
- }
+ // just ignore the call here onOrientationChanged is invoked anyway
}
- public void onAccuracyChanged(int sensor, int accuracy) {
- // TODO Auto-generated method stub
- }
/**
- * Called when the orientation of the device has changed.
- * orientation parameter is in degrees, ranging from 0 to 359.
- * orientation is 0 degrees when the device is oriented in its natural position,
- * 90 degrees when its left side is at the top, 180 degrees when it is upside down,
- * and 270 degrees when its right side is to the top.
- * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
- * and the orientation cannot be determined.
- *
+ * Look at {@link android.view.OrientationEventListener#onOrientationChanged}
+ * for method description and usage
* @param orientation The new orientation of the device.
*
* @see #ORIENTATION_UNKNOWN
*/
abstract public void onOrientationChanged(int orientation);
+
}
diff --git a/core/java/android/os/HandlerInterface.java b/core/java/android/view/RemotableViewMethod.java
index 62dc273..4318290 100644
--- a/core/java/android/os/HandlerInterface.java
+++ b/core/java/android/view/RemotableViewMethod.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2007 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.
@@ -14,14 +14,22 @@
* limitations under the License.
*/
-package android.os;
+package android.view;
-/**
- * @hide
- * @deprecated
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @hide
+ * This annotation indicates that a method on a subclass of View
+ * is alllowed to be used with the {@link android.widget.RemoteViews} mechanism.
*/
-public interface HandlerInterface
-{
- void handleMessage(Message msg);
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RemotableViewMethod {
}
+
+
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1d5e7cd..5ed3a7e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2674,6 +2674,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
* @attr ref android.R.styleable#View_visibility
*/
+ @RemotableViewMethod
public void setVisibility(int visibility) {
setFlags(visibility, VISIBILITY_MASK);
if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false);
@@ -4016,6 +4017,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
mBackgroundSizeChanged = true;
+
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ai.mViewScrollChanged = true;
+ }
}
/**
@@ -7948,6 +7954,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
boolean mViewVisibilityChanged;
/**
+ * Set to true if a view has been scrolled.
+ */
+ boolean mViewScrollChanged;
+
+ /**
* Global to the view hierarchy used as a temporary for dealing with
* x/y points in the transparent region computations.
*/
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 7153ea1..2f7b0d1 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -67,6 +67,13 @@ public class ViewConfiguration {
* considered to be a tap.
*/
private static final int JUMP_TAP_TIMEOUT = 500;
+
+ /**
+ * Defines the duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ */
+ private static final int DOUBLE_TAP_TIMEOUT = 300;
/**
* Defines the duration in milliseconds we want to display zoom controls in response
@@ -82,7 +89,7 @@ public class ViewConfiguration {
/**
* Distance a touch can wander before we think the user is scrolling in pixels
*/
- private static final int TOUCH_SLOP = 12;
+ private static final int TOUCH_SLOP = 25;
/**
* Distance between the first touch and second touch to still be considered a double tap
@@ -257,6 +264,16 @@ public class ViewConfiguration {
}
/**
+ * @return Defines the duration in milliseconds between the first tap's up event and
+ * the second tap's down event for an interaction to be considered a
+ * double-tap.
+ * @hide pending API council
+ */
+ public static int getDoubleTapTimeout() {
+ return DOUBLE_TAP_TIMEOUT;
+ }
+
+ /**
* @return Inset in pixels to look for touchable content when the user touches the edge of the
* screen
*
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c758662..70cc2a9 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.RectF;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
@@ -1023,6 +1024,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
void dispatchDetachedFromWindow() {
+ // If we still have a motion target, we are still in the process of
+ // dispatching motion events to a child; we need to get rid of that
+ // child to avoid dispatching events to it after the window is torn
+ // down. To make sure we keep the child in a consistent state, we
+ // first send it an ACTION_CANCEL motion event.
+ if (mMotionTarget != null) {
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ mMotionTarget.dispatchTouchEvent(event);
+ event.recycle();
+ mMotionTarget = null;
+ }
+
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
@@ -1331,6 +1346,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final Animation a = child.getAnimation();
boolean concatMatrix = false;
+ final int childWidth = cr - cl;
+ final int childHeight = cb - ct;
+
if (a != null) {
if (mInvalidateRegion == null) {
mInvalidateRegion = new RectF();
@@ -1339,8 +1357,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final boolean initialized = a.isInitialized();
if (!initialized) {
- a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
- a.initializeInvalidateRegion(cl, ct, cr, cb);
+ a.initialize(childWidth, childHeight, getWidth(), getHeight());
+ a.initializeInvalidateRegion(0, 0, childWidth, childHeight);
child.onAnimationStart();
}
@@ -1364,7 +1382,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
invalidate(cl, ct, cr, cb);
}
} else {
- a.getInvalidateRegion(cl, ct, cr, cb, region, transformToApply);
+ a.getInvalidateRegion(0, 0, childWidth, childHeight, region, transformToApply);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
@@ -1372,8 +1390,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Enlarge the invalidate region to account for rounding errors
// in Animation#getInvalidateRegion(); Using 0.5f is unfortunately
// not enough for some types of animations (e.g. scale down.)
- invalidate((int) (region.left - 1.0f), (int) (region.top - 1.0f),
- (int) (region.right + 1.0f), (int) (region.bottom + 1.0f));
+ final int left = cl + (int) (region.left - 1.0f);
+ final int top = ct + (int) (region.top - 1.0f);
+ invalidate(left, top,
+ left + (int) (region.width() + 1.0f),
+ top + (int) (region.height() + 1.0f));
}
}
} else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
@@ -1453,9 +1474,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (hasNoCache) {
- canvas.clipRect(sx, sy, sx + cr - cl, sy + cb - ct);
+ canvas.clipRect(sx, sy, sx + childWidth, sy + childHeight);
} else {
- canvas.clipRect(0, 0, cr - cl, cb - ct);
+ canvas.clipRect(0, 0, childWidth, childHeight);
}
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index ccfa6bf..db8829f 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -33,6 +33,7 @@ import android.util.Config;
import android.util.Log;
import android.util.EventLog;
import android.util.SparseArray;
+import android.util.DisplayMetrics;
import android.view.View.MeasureSpec;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
@@ -184,6 +185,7 @@ public final class ViewRoot extends Handler implements ViewParent,
*/
AudioManager mAudioManager;
+ private final float mDensity;
public ViewRoot(Context context) {
super();
@@ -226,6 +228,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mAdded = false;
mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
mViewConfiguration = ViewConfiguration.get(context);
+ mDensity = context.getResources().getDisplayMetrics().density;
}
@Override
@@ -1077,6 +1080,11 @@ public final class ViewRoot extends Handler implements ViewParent,
}
scrollToRectOrFocus(null, false);
+
+ if (mAttachInfo.mViewScrollChanged) {
+ mAttachInfo.mViewScrollChanged = false;
+ mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
+ }
int yoff;
final boolean scrolling = mScroller != null
@@ -1090,7 +1098,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mCurScrollY = yoff;
fullRedrawNeeded = true;
}
-
+
Rect dirty = mDirty;
if (mUseGL) {
if (!dirty.isEmpty()) {
@@ -1126,7 +1134,6 @@ public final class ViewRoot extends Handler implements ViewParent,
return;
}
-
if (fullRedrawNeeded)
dirty.union(0, 0, mWidth, mHeight);
@@ -1138,22 +1145,22 @@ public final class ViewRoot extends Handler implements ViewParent,
+ surface + " surface.isValid()=" + surface.isValid());
}
- if (!dirty.isEmpty()) {
- Canvas canvas;
- try {
- canvas = surface.lockCanvas(dirty);
- // TODO: Do this in native
- canvas.setDensityScale(mView.getResources().getDisplayMetrics().density);
- } catch (Surface.OutOfResourcesException e) {
- Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
- // TODO: we should ask the window manager to do something!
- // for now we just do nothing
- return;
- }
+ Canvas canvas;
+ try {
+ canvas = surface.lockCanvas(dirty);
+ // TODO: Do this in native
+ canvas.setDensityScale(mDensity);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
+ // TODO: we should ask the window manager to do something!
+ // for now we just do nothing
+ return;
+ }
- long startTime;
+ try {
+ if (!dirty.isEmpty()) {
+ long startTime;
- try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
@@ -1169,7 +1176,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
if (!canvas.isOpaque()) {
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ canvas.drawColor(0xff0000ff, PorterDuff.Mode.CLEAR);
} else if (yoff != 0) {
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
@@ -1192,35 +1199,18 @@ public final class ViewRoot extends Handler implements ViewParent,
sDrawTime = now;
}
- } finally {
- surface.unlockCanvasAndPost(canvas);
- }
-
- if (PROFILE_DRAWING) {
- EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
- }
-
- if (LOCAL_LOGV) {
- Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
+ if (PROFILE_DRAWING) {
+ EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+ }
}
- } else if (mWidth == 0 || mHeight == 0) {
- // This is a special case where a window dimension is 0 -- we
- // normally wouldn't draw anything because we have an empty
- // dirty rect, but the surface flinger may be waiting for us to
- // draw the window before it stops freezing the screen, so we
- // need to diddle it like this to keep it from getting stuck.
- Canvas canvas;
- try {
- canvas = surface.lockCanvas(dirty);
- } catch (Surface.OutOfResourcesException e) {
- Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
- // TODO: we should ask the window manager to do something!
- // for now we just do nothing
- return;
- }
+ } finally {
surface.unlockCanvasAndPost(canvas);
}
+
+ if (LOCAL_LOGV) {
+ Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
+ }
if (scrolling) {
mFullRedrawNeeded = true;
@@ -1401,14 +1391,22 @@ public final class ViewRoot extends Handler implements ViewParent,
void dispatchDetachedFromWindow() {
if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface);
+
if (mView != null) {
mView.dispatchDetachedFromWindow();
}
+
mView = null;
mAttachInfo.mRootView = null;
+
if (mUseGL) {
destroyGL();
}
+
+ try {
+ sWindowSession.remove(mWindow);
+ } catch (RemoteException e) {
+ }
}
/**
@@ -1609,16 +1607,20 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
if (mView != null) {
+ if (hasWindowFocus && imm != null) {
+ imm.startGettingWindowFocus();
+ }
mView.dispatchWindowFocusChanged(hasWindowFocus);
}
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
- InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
- imm.onWindowFocus(mView.findFocus(),
+ imm.onWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
@@ -2289,10 +2291,6 @@ public final class ViewRoot extends Handler implements ViewParent,
}
if (mAdded) {
mAdded = false;
- try {
- sWindowSession.remove(mWindow);
- } catch (RemoteException e) {
- }
if (immediate) {
dispatchDetachedFromWindow();
} else if (mView != null) {
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 05f5fa2..47b52e4 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -35,6 +35,7 @@ public final class ViewTreeObserver {
private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
private ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+ private ArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
private boolean mAlive = true;
@@ -99,6 +100,20 @@ public final class ViewTreeObserver {
}
/**
+ * Interface definition for a callback to be invoked when
+ * something in the view tree has been scrolled.
+ *
+ * @hide pending API council approval
+ */
+ public interface OnScrollChangedListener {
+ /**
+ * Callback method to be invoked when something in the view tree
+ * has been scrolled.
+ */
+ public void onScrollChanged();
+ }
+
+ /**
* Parameters used with OnComputeInternalInsetsListener.
* {@hide pending API Council approval}
*/
@@ -361,6 +376,44 @@ public final class ViewTreeObserver {
}
/**
+ * Register a callback to be invoked when a view has been scrolled.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @hide pending API council approval
+ */
+ public void addOnScrollChangedListener(OnScrollChangedListener listener) {
+ checkIsAlive();
+
+ if (mOnScrollChangedListeners == null) {
+ mOnScrollChangedListeners = new ArrayList<OnScrollChangedListener>();
+ }
+
+ mOnScrollChangedListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed scroll-changed callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnScrollChangedListener(OnScrollChangedListener)
+ *
+ * @hide pending API council approval
+ */
+ public void removeOnScrollChangedListener(OnScrollChangedListener victim) {
+ checkIsAlive();
+ if (mOnScrollChangedListeners == null) {
+ return;
+ }
+ mOnScrollChangedListeners.remove(victim);
+ }
+
+ /**
* Register a callback to be invoked when the invoked when the touch mode changes.
*
* @param listener The callback to add
@@ -525,6 +578,19 @@ public final class ViewTreeObserver {
}
/**
+ * Notifies registered listeners that something has scrolled.
+ */
+ final void dispatchOnScrollChanged() {
+ final ArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+
+ if (listeners != null) {
+ for (OnScrollChangedListener scl : mOnScrollChangedListeners) {
+ scl.onScrollChanged();
+ }
+ }
+ }
+
+ /**
* Returns whether there are listeners for computing internal insets.
*/
final boolean hasComputeInternalInsetsListeners() {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 406af3e3..b87cc42 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -513,26 +513,33 @@ public interface WindowManager extends ViewManager {
/**
* Visibility state for {@link #softInputMode}: please hide any soft input
- * area.
+ * area when normally appropriate (when the user is navigating
+ * forward to your window).
*/
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
/**
+ * Visibility state for {@link #softInputMode}: please always hide any
+ * soft input area when this window receives focus.
+ */
+ public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
+
+ /**
* Visibility state for {@link #softInputMode}: please show the soft
* input area when normally appropriate (when the user is navigating
* forward to your window).
*/
- public static final int SOFT_INPUT_STATE_VISIBLE = 3;
+ public static final int SOFT_INPUT_STATE_VISIBLE = 4;
/**
* Visibility state for {@link #softInputMode}: please always make the
* soft input area visible when this window receives input focus.
*/
- public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 4;
+ public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
/**
* Mask for {@link #softInputMode} of the bits that determine the
- * way that the window should be adjusted to accomodate the soft
+ * way that the window should be adjusted to accommodate the soft
* input window.
*/
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
@@ -634,6 +641,14 @@ public interface WindowManager extends ViewManager {
public float dimAmount = 1.0f;
/**
+ * This can be used to override the user's preferred brightness of
+ * the screen. A value of less than 0, the default, means to use the
+ * preferred screen brightness. 0 to 1 adjusts the brightness from
+ * dark to full bright.
+ */
+ public float screenBrightness = -1.0f;
+
+ /**
* Identifier for this window. This will usually be filled in for
* you.
*/
@@ -729,6 +744,7 @@ public interface WindowManager extends ViewManager {
out.writeInt(windowAnimations);
out.writeFloat(alpha);
out.writeFloat(dimAmount);
+ out.writeFloat(screenBrightness);
out.writeStrongBinder(token);
out.writeString(packageName);
TextUtils.writeToParcel(mTitle, out, parcelableFlags);
@@ -763,6 +779,7 @@ public interface WindowManager extends ViewManager {
windowAnimations = in.readInt();
alpha = in.readFloat();
dimAmount = in.readFloat();
+ screenBrightness = in.readFloat();
token = in.readStrongBinder();
packageName = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -780,6 +797,7 @@ public interface WindowManager extends ViewManager {
public static final int MEMORY_TYPE_CHANGED = 1<<8;
public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
+ public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
public final int copyFrom(LayoutParams o) {
int changes = 0;
@@ -874,6 +892,10 @@ public interface WindowManager extends ViewManager {
dimAmount = o.dimAmount;
changes |= DIM_AMOUNT_CHANGED;
}
+ if (screenBrightness != o.screenBrightness) {
+ screenBrightness = o.screenBrightness;
+ changes |= SCREEN_BRIGHTNESS_CHANGED;
+ }
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
new file mode 100755
index 0000000..4aa3f7a
--- /dev/null
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.Config;
+import android.util.Log;
+
+/**
+ * A special helper class used by the WindowManager
+ * for receiving notifications from the SensorManager when
+ * the orientation of the device has changed.
+ * @hide
+ */
+public abstract class WindowOrientationListener {
+ private static final String TAG = "WindowOrientationListener";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private int mOrientation = ORIENTATION_UNKNOWN;
+ private SensorManager mSensorManager;
+ private boolean mEnabled = false;
+ private int mRate;
+ private Sensor mSensor;
+ private SensorEventListener mSensorEventListener;
+
+ /**
+ * Returned from onOrientationChanged when the device orientation cannot be determined
+ * (typically when the device is in a close to flat position).
+ *
+ * @see #onOrientationChanged
+ */
+ public static final int ORIENTATION_UNKNOWN = -1;
+ /*
+ * Returned when the device is almost lying flat on a surface
+ */
+ public static final int ORIENTATION_FLAT = -2;
+
+ /**
+ * Creates a new WindowOrientationListener.
+ *
+ * @param context for the WindowOrientationListener.
+ */
+ public WindowOrientationListener(Context context) {
+ this(context, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ /**
+ * Creates a new WindowOrientationListener.
+ *
+ * @param context for the WindowOrientationListener.
+ * @param rate at which sensor events are processed (see also
+ * {@link android.hardware.SensorManager SensorManager}). Use the default
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ */
+ public WindowOrientationListener(Context context, int rate) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
+ mSensorEventListener = new SensorEventListenerImpl();
+ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ }
+
+ /**
+ * Enables the WindowOrientationListener so it will monitor the sensor and call
+ * {@link #onOrientationChanged} when the device orientation changes.
+ */
+ public void enable() {
+ if (mEnabled == false) {
+ if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled");
+ mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
+ mEnabled = true;
+ }
+ }
+
+ /**
+ * Disables the WindowOrientationListener.
+ */
+ public void disable() {
+ if (mEnabled == true) {
+ if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled");
+ mSensorManager.unregisterListener(mSensorEventListener);
+ mEnabled = false;
+ }
+ }
+
+ class SensorEventListenerImpl implements SensorEventListener {
+ private static final int _DATA_X = 0;
+ private static final int _DATA_Y = 1;
+ private static final int _DATA_Z = 2;
+
+ public void onSensorChanged(SensorEvent event) {
+ float[] values = event.values;
+ int orientation = ORIENTATION_UNKNOWN;
+ float X = values[_DATA_X];
+ float Y = values[_DATA_Y];
+ float Z = values[_DATA_Z];
+ float OneEightyOverPi = 57.29577957855f;
+ float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z);
+ float zyangle = Math.abs((float)Math.asin(Z/gravity)*OneEightyOverPi);
+ // The device is considered flat if the angle is more than 75
+ // if the angle is less than 40, its considered too flat to switch
+ // orientation. if the angle is between 40 - 75, the orientation is unknown
+ if (zyangle < 40) {
+ // Check orientation only if the phone is flat enough
+ // Don't trust the angle if the magnitude is small compared to the y value
+ float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi;
+ orientation = 90 - (int)Math.round(angle);
+ // normalize to 0 - 359 range
+ while (orientation >= 360) {
+ orientation -= 360;
+ }
+ while (orientation < 0) {
+ orientation += 360;
+ }
+ } else if (zyangle >= 75){
+ orientation = ORIENTATION_FLAT;
+ }
+
+ if (orientation != mOrientation) {
+ mOrientation = orientation;
+ onOrientationChanged(orientation);
+ }
+ }
+
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+ }
+
+ /**
+ * Called when the orientation of the device has changed.
+ * orientation parameter is in degrees, ranging from 0 to 359.
+ * orientation is 0 degrees when the device is oriented in its natural position,
+ * 90 degrees when its left side is at the top, 180 degrees when it is upside down,
+ * and 270 degrees when its right side is to the top.
+ * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat
+ * and the orientation cannot be determined.
+ *
+ * @param orientation The new orientation of the device.
+ *
+ * @see #ORIENTATION_UNKNOWN
+ */
+ abstract public void onOrientationChanged(int orientation);
+}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index c96b3e5..b9c8ec3 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -179,6 +179,7 @@ public abstract class Animation implements Cloneable {
private boolean mOneMoreTime = true;
RectF mPreviousRegion = new RectF();
+ RectF mRegion = new RectF();
Transformation mTransformation = new Transformation();
Transformation mPreviousTransformation = new Transformation();
@@ -226,6 +227,7 @@ public abstract class Animation implements Cloneable {
protected Animation clone() throws CloneNotSupportedException {
final Animation animation = (Animation) super.clone();
animation.mPreviousRegion = new RectF();
+ animation.mRegion = new RectF();
animation.mTransformation = new Transformation();
animation.mPreviousTransformation = new Transformation();
return animation;
@@ -799,14 +801,15 @@ public abstract class Animation implements Cloneable {
public void getInvalidateRegion(int left, int top, int right, int bottom,
RectF invalidate, Transformation transformation) {
+ final RectF tempRegion = mRegion;
final RectF previousRegion = mPreviousRegion;
invalidate.set(left, top, right, bottom);
transformation.getMatrix().mapRect(invalidate);
+ tempRegion.set(invalidate);
invalidate.union(previousRegion);
- previousRegion.set(left, top, right, bottom);
- transformation.getMatrix().mapRect(previousRegion);
+ previousRegion.set(tempRegion);
final Transformation tempTransformation = mTransformation;
final Transformation previousTransformation = mPreviousTransformation;
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 9509b15..6fbc174 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -525,9 +525,6 @@ public class BaseInputConnection implements InputConnection {
setComposingSpans(sp);
}
- // Adjust newCursorPosition to be relative the start of the text.
- newCursorPosition += a;
-
if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \""
+ text + "\", composing=" + composing
+ ", type=" + text.getClass().getCanonicalName());
@@ -540,11 +537,21 @@ public class BaseInputConnection implements InputConnection {
TextUtils.dumpSpans(text, lp, " ");
}
- content.replace(a, b, text);
+ // Position the cursor appropriately, so that after replacing the
+ // desired range of text it will be located in the correct spot.
+ // This allows us to deal with filters performing edits on the text
+ // we are providing here.
+ if (newCursorPosition > 0) {
+ newCursorPosition += b - 1;
+ } else {
+ newCursorPosition += a;
+ }
if (newCursorPosition < 0) newCursorPosition = 0;
if (newCursorPosition > content.length())
newCursorPosition = content.length();
Selection.setSelection(content, newCursorPosition);
+
+ content.replace(a, b, text);
if (DEBUG) {
LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 13173f6..530127d 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -148,8 +148,14 @@ public interface InputConnection {
* object to the text. {#link android.text.SpannableString} and
* {#link android.text.SpannableStringBuilder} are two
* implementations of the interface {#link android.text.Spanned}.
- * @param newCursorPosition The new cursor position within the
- * <var>text</var>.
+ * @param newCursorPosition The new cursor position around the text. If
+ * > 0, this is relative to the end of the text - 1; if <= 0, this
+ * is relative to the start of the text. So a value of 1 will
+ * always advance you to the position after the full text being
+ * inserted. Note that this means you can't position the cursor
+ * within the text, because the editor can make modifications to
+ * the text you are providing so it is not possible to correctly
+ * specify locations there.
*
* @return Returns true on success, false if the input connection is no longer
* valid.
@@ -170,8 +176,14 @@ public interface InputConnection {
* automatically.
*
* @param text The committed text.
- * @param newCursorPosition The new cursor position within the
- * <var>text</var>.
+ * @param newCursorPosition The new cursor position around the text. If
+ * > 0, this is relative to the end of the text - 1; if <= 0, this
+ * is relative to the start of the text. So a value of 1 will
+ * always advance you to the position after the full text being
+ * inserted. Note that this means you can't position the cursor
+ * within the text, because the editor can make modifications to
+ * the text you are providing so it is not possible to correctly
+ * specify locations there.
*
*
* @return Returns true on success, false if the input connection is no longer
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index fe14166..91fa211 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -226,6 +226,10 @@ public final class InputMethodManager {
* regardless of the state of setting that up.
*/
View mServedView;
+ /*
+ * Keep track of the view that was set when our window gained focus.
+ */
+ View mWindowFocusedView;
/**
* For evaluating the state after a focus change, this is the view that
* had focus.
@@ -466,8 +470,8 @@ public final class InputMethodManager {
}
/** @hide */
- public void setFullscreenMode(boolean enabled) {
- mFullscreenMode = true;
+ public void setFullscreenMode(boolean fullScreen) {
+ mFullscreenMode = fullScreen;
}
/**
@@ -828,19 +832,23 @@ public final class InputMethodManager {
*/
public void focusIn(View view) {
synchronized (mH) {
- if (DEBUG) Log.v(TAG, "focusIn: " + view);
- // Okay we have a new view that is being served.
- if (mServedView != view) {
- mCurrentTextBoxAttribute = null;
- }
- mServedView = view;
- mCompletions = null;
- mServedConnecting = true;
+ focusInLocked(view);
}
startInputInner();
}
+ void focusInLocked(View view) {
+ if (DEBUG) Log.v(TAG, "focusIn: " + view);
+ // Okay we have a new view that is being served.
+ if (mServedView != view) {
+ mCurrentTextBoxAttribute = null;
+ }
+ mServedView = view;
+ mCompletions = null;
+ mServedConnecting = true;
+ }
+
/**
* Call this when a view loses focus.
* @hide
@@ -908,16 +916,28 @@ public final class InputMethodManager {
* Called by ViewRoot the first time it gets window focus.
* @hide
*/
- public void onWindowFocus(View focusedView, int softInputMode,
+ public void onWindowFocus(View rootView, View focusedView, int softInputMode,
boolean first, int windowFlags) {
+ boolean needStartInput = false;
synchronized (mH) {
if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
+ " softInputMode=" + softInputMode
+ " first=" + first + " flags=#"
+ Integer.toHexString(windowFlags));
+ if (mWindowFocusedView == null) {
+ focusInLocked(focusedView != null ? focusedView : rootView);
+ needStartInput = true;
+ }
+ }
+
+ if (needStartInput) {
+ startInputInner();
+ }
+
+ synchronized (mH) {
try {
final boolean isTextEditor = focusedView != null &&
- focusedView.onCheckIsTextEditor();
+ focusedView.onCheckIsTextEditor();
mService.windowGainedFocus(mClient, focusedView != null,
isTextEditor, softInputMode, first, windowFlags);
} catch (RemoteException e) {
@@ -925,6 +945,13 @@ public final class InputMethodManager {
}
}
+ /** @hide */
+ public void startGettingWindowFocus() {
+ synchronized (mH) {
+ mWindowFocusedView = null;
+ }
+ }
+
/**
* Report the current selection range.
*/
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 1dd37be..451af6d 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -51,7 +51,7 @@ class BrowserFrame extends Handler {
private final Context mContext;
private final WebViewDatabase mDatabase;
private final WebViewCore mWebViewCore;
- private boolean mLoadInitFromJava;
+ /* package */ boolean mLoadInitFromJava;
private int mLoadType;
private boolean mFirstLayoutDone = true;
private boolean mCommitted = true;
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 3694969..dfae17d 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -575,7 +575,7 @@ class LoadListener extends Handler implements EventHandler {
mRequestHandle.getMethod().equals("POST")) {
sendMessageInternal(obtainMessage(
MSG_LOCATION_CHANGED_REQUEST));
- } else if (mMethod.equals("POST")) {
+ } else if (mMethod != null && mMethod.equals("POST")) {
sendMessageInternal(obtainMessage(
MSG_LOCATION_CHANGED_REQUEST));
} else {
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index 9af30c5..c2620a5 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -291,6 +291,25 @@ import java.util.ArrayList;
}
/**
+ * Create a fake touch up event at (x,y) with respect to this TextDialog.
+ * This is used by WebView to act as though a touch event which happened
+ * before we placed the TextDialog actually hit it, so that it can place
+ * the cursor accordingly.
+ */
+ /* package */ void fakeTouchEvent(float x, float y) {
+ // We need to ensure that there is a Layout, since the Layout is used
+ // in determining where to place the cursor.
+ if (getLayout() == null) {
+ measure(mWidthSpec, mHeightSpec);
+ }
+ // Create a fake touch up, which is used to place the cursor.
+ MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
+ x, y, 0);
+ onTouchEvent(ev);
+ ev.recycle();
+ }
+
+ /**
* Determine whether this TextDialog currently represents the node
* represented by ptr.
* @param ptr Pointer to a node to compare to.
@@ -461,9 +480,8 @@ import java.util.ArrayList;
*/
public void setAdapterCustom(AutoCompleteAdapter adapter) {
if (adapter != null) {
- adapter.setTextView(this);
- } else {
setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+ adapter.setTextView(this);
}
super.setAdapter(adapter);
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index bdbf38a..4d9a8fb 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -45,6 +45,8 @@ import android.util.AttributeSet;
import android.util.Config;
import android.util.Log;
import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -234,6 +236,9 @@ public class WebView extends AbsoluteLayout
*/
VelocityTracker mVelocityTracker;
+ private static boolean mShowZoomRingTutorial = true;
+ private static final int ZOOM_RING_TUTORIAL_DURATION = 3000;
+
/**
* Touch mode
*/
@@ -284,9 +289,7 @@ public class WebView extends AbsoluteLayout
// In the browser, if it switches out of tap too soon, jump tap won't work.
private static final int TAP_TIMEOUT = 200;
// The duration in milliseconds we will wait to see if it is a double tap.
- // With a limited survey, the time between the first tap up and the second
- // tap down in the double tap case is around 70ms - 120ms.
- private static final int DOUBLE_TAP_TIMEOUT = 200;
+ private static final int DOUBLE_TAP_TIMEOUT = 250;
// 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.
@@ -315,6 +318,9 @@ public class WebView extends AbsoluteLayout
private int mContentWidth; // cache of value from WebViewCore
private int mContentHeight; // cache of value from WebViewCore
+ static int MAX_FLOAT_CONTENT_WIDTH = 480;
+ private int mMinContentWidth;
+
// Need to have the separate control for horizontal and vertical scrollbar
// style than the View's single scrollbar style
private boolean mOverlayHorizontalScrollbar = true;
@@ -348,6 +354,7 @@ public class WebView extends AbsoluteLayout
private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6;
private static final int SWITCH_TO_ENTER = 7;
private static final int RESUME_WEBCORE_UPDATE = 8;
+ private static final int DISMISS_ZOOM_RING_TUTORIAL = 9;
//! arg1=x, arg2=y
static final int SCROLL_TO_MSG_ID = 10;
@@ -370,12 +377,46 @@ public class WebView extends AbsoluteLayout
static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
// obj=Rect in doc coordinates
static final int INVAL_RECT_MSG_ID = 26;
+
+ static final String[] HandlerDebugString = {
+ "REMEMBER_PASSWORD", // = 1;
+ "NEVER_REMEMBER_PASSWORD", // = 2;
+ "SWITCH_TO_SHORTPRESS", // = 3;
+ "SWITCH_TO_LONGPRESS", // = 4;
+ "RELEASE_SINGLE_TAP", // = 5;
+ "UPDATE_TEXT_ENTRY_ADAPTER", // = 6;
+ "SWITCH_TO_ENTER", // = 7;
+ "RESUME_WEBCORE_UPDATE", // = 8;
+ "9",
+ "SCROLL_TO_MSG_ID", // = 10;
+ "SCROLL_BY_MSG_ID", // = 11;
+ "SPAWN_SCROLL_TO_MSG_ID", // = 12;
+ "SYNC_SCROLL_TO_MSG_ID", // = 13;
+ "NEW_PICTURE_MSG_ID", // = 14;
+ "UPDATE_TEXT_ENTRY_MSG_ID", // = 15;
+ "WEBCORE_INITIALIZED_MSG_ID", // = 16;
+ "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17;
+ "DID_FIRST_LAYOUT_MSG_ID", // = 18;
+ "RECOMPUTE_FOCUS_MSG_ID", // = 19;
+ "NOTIFY_FOCUS_SET_MSG_ID", // = 20;
+ "MARK_NODE_INVALID_ID", // = 21;
+ "UPDATE_CLIPBOARD", // = 22;
+ "LONG_PRESS_ENTER", // = 23;
+ "PREVENT_TOUCH_ID", // = 24;
+ "WEBCORE_NEED_TOUCH_EVENTS", // = 25;
+ "INVAL_RECT_MSG_ID" // = 26;
+ };
// width which view is considered to be fully zoomed out
static final int ZOOM_OUT_WIDTH = 1024;
- private static final float DEFAULT_MAX_ZOOM_SCALE = 4;
- private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f;
+ private static final float MAX_ZOOM_RING_ANGLE = (float) (Math.PI * 2 / 3);
+ private static final int ZOOM_RING_STEPS = 4;
+ private static final float ZOOM_RING_ANGLE_UNIT = MAX_ZOOM_RING_ANGLE
+ / ZOOM_RING_STEPS;
+
+ private static final float DEFAULT_MAX_ZOOM_SCALE = 2;
+ private static final float DEFAULT_MIN_ZOOM_SCALE = (float) 1/3;
// scale limit, which can be set through viewport meta tag in the web page
private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
@@ -505,6 +546,8 @@ public class WebView extends AbsoluteLayout
}
private ZoomRingController mZoomRingController;
+ private ImageView mZoomRingOverview;
+ private Animation mZoomRingOverviewExitAnimation;
// These keep track of the center point of the zoom ring. They are used to
// determine the point around which we should zoom.
@@ -519,51 +562,83 @@ public class WebView extends AbsoluteLayout
// in this callback
}
+ public void onBeginPan() {
+ setZoomOverviewVisible(false);
+ }
+
public boolean onPan(int deltaX, int deltaY) {
return pinScrollBy(deltaX, deltaY, false, 0);
}
+ public void onEndPan() {
+ }
+
public void onVisibilityChanged(boolean visible) {
if (visible) {
- mZoomControls.show(false, canZoomScrollOut());
- } else {
- mZoomControls.hide();
+ switchOutDrawHistory();
+ float angle = 0f;
+ if (mActualScale > 1) {
+ angle = -(float) Math.round(ZOOM_RING_STEPS
+ * (mActualScale - 1) / (mMaxZoomScale - 1))
+ / ZOOM_RING_STEPS;
+ } else if (mActualScale < 1) {
+ angle = (float) Math.round(ZOOM_RING_STEPS
+ * (1 - mActualScale) / (1 - mMinZoomScale))
+ / ZOOM_RING_STEPS;
+ }
+ mZoomRingController.setThumbAngle(angle * MAX_ZOOM_RING_ANGLE);
+
+ // Show the zoom overview tab on the ring
+ setZoomOverviewVisible(true);
}
}
-
- public void onBeginDrag(float startAngle) {
+
+ public void onBeginDrag() {
mPreviewZoomOnly = true;
+ setZoomOverviewVisible(false);
}
- public void onEndDrag(float endAngle) {
+ public void onEndDrag() {
mPreviewZoomOnly = false;
setNewZoomScale(mActualScale, true);
}
public boolean onDragZoom(int deltaZoomLevel, int centerX,
int centerY, float startAngle, float curAngle) {
-
- if (mZoomScale == mMinZoomScale && deltaZoomLevel < 0 ||
- mZoomScale == mMaxZoomScale && deltaZoomLevel > 0 ||
- deltaZoomLevel == 0) {
+ if (deltaZoomLevel < 0
+ && Math.abs(mActualScale - mMinZoomScale) < 0.01f
+ || deltaZoomLevel > 0
+ && Math.abs(mActualScale - mMaxZoomScale) < 0.01f
+ || deltaZoomLevel == 0) {
return false;
}
mZoomCenterX = (float) centerX;
mZoomCenterY = (float) centerY;
- while (deltaZoomLevel != 0) {
- if (deltaZoomLevel > 0) {
- if (!zoomIn()) return false;
- deltaZoomLevel--;
+ float scale = 1.0f;
+ if (curAngle > (float) Math.PI)
+ curAngle -= (float) 2 * Math.PI;
+ if (curAngle > 0) {
+ if (curAngle >= MAX_ZOOM_RING_ANGLE) {
+ scale = mMinZoomScale;
+ } else {
+ scale = 1 - (float) Math.round(curAngle
+ / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS
+ * (1 - mMinZoomScale);
+ }
+ } else if (curAngle < 0) {
+ if (curAngle <= -MAX_ZOOM_RING_ANGLE) {
+ scale = mMaxZoomScale;
} else {
- if (!zoomOut()) return false;
- deltaZoomLevel++;
+ scale = 1 + (float) Math.round(-curAngle
+ / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS
+ * (mMaxZoomScale - 1);
}
}
-
+ zoomWithPreview(scale);
return true;
}
-
+
public void onSimpleZoom(boolean zoomIn) {
if (zoomIn) {
zoomIn();
@@ -571,6 +646,7 @@ public class WebView extends AbsoluteLayout
zoomOut();
}
}
+
};
/**
@@ -610,7 +686,14 @@ public class WebView extends AbsoluteLayout
mFocusData.mY = 0;
mScroller = new Scroller(context);
mZoomRingController = new ZoomRingController(context, this);
+ mZoomRingController.setResetThumbAutomatically(false);
+ mZoomRingController.setThumbClockwiseBound(
+ (float) (2 * Math.PI - MAX_ZOOM_RING_ANGLE));
+ mZoomRingController.setThumbCounterclockwiseBound(MAX_ZOOM_RING_ANGLE);
mZoomRingController.setCallback(mZoomListener);
+ mZoomRingController.setZoomRingTrack(
+ com.android.internal.R.drawable.zoom_ring_track_absolute);
+ createZoomRingOverviewTab();
}
private void init() {
@@ -625,6 +708,63 @@ public class WebView extends AbsoluteLayout
mMinLockSnapReverseDistance = slop;
}
+ private void createZoomRingOverviewTab() {
+ Context context = getContext();
+
+ mZoomRingOverviewExitAnimation = AnimationUtils.loadAnimation(context,
+ com.android.internal.R.anim.fade_out);
+
+ mZoomRingOverview = new ImageView(context);
+ mZoomRingOverview.setBackgroundResource(
+ com.android.internal.R.drawable.zoom_ring_overview_tab);
+ mZoomRingOverview.setImageResource(com.android.internal.R.drawable.btn_zoom_page);
+
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER);
+ // TODO: magic constant that's based on the zoom ring radius + some offset
+ lp.topMargin = 208;
+ mZoomRingOverview.setLayoutParams(lp);
+ mZoomRingOverview.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // Hide the zoom ring
+ mZoomRingController.setVisible(false);
+ zoomScrollOut();
+ }});
+
+ // Measure the overview View to figure out its height
+ mZoomRingOverview.forceLayout();
+ mZoomRingOverview.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+
+ ViewGroup container = mZoomRingController.getContainer();
+ // Find the index of the zoom ring in the container
+ View zoomRing = container.findViewById(mZoomRingController.getZoomRingId());
+ int zoomRingIndex;
+ for (zoomRingIndex = container.getChildCount() - 1; zoomRingIndex >= 0; zoomRingIndex--) {
+ if (container.getChildAt(zoomRingIndex) == zoomRing) break;
+ }
+ // Add the overview tab below the zoom ring (so we don't steal its events)
+ container.addView(mZoomRingOverview, zoomRingIndex);
+ // Since we use margins to adjust the vertical placement of the tab, the widget
+ // ends up getting clipped off. Ensure the container is big enough for
+ // us.
+ int myHeight = mZoomRingOverview.getMeasuredHeight() + lp.topMargin / 2;
+ // Multiplied by 2 b/c the zoom ring needs to be centered on the screen
+ container.setMinimumHeight(myHeight * 2);
+ }
+
+ private void setZoomOverviewVisible(boolean visible) {
+ int newVisibility = visible ? View.VISIBLE : View.INVISIBLE;
+ if (mZoomRingOverview.getVisibility() == newVisibility) return;
+
+ if (!visible) {
+ mZoomRingOverview.startAnimation(mZoomRingOverviewExitAnimation);
+ }
+ mZoomRingOverview.setVisibility(newVisibility);
+ }
+
/* package */ boolean onSavePassword(String schemePlusHost, String username,
String password, final Message resumeMsg) {
boolean rVal = false;
@@ -1653,7 +1793,8 @@ public class WebView extends AbsoluteLayout
* @return true if new values were sent
*/
private boolean sendViewSizeZoom() {
- int newWidth = Math.round(getViewWidth() * mInvActualScale);
+ int viewWidth = getViewWidth();
+ int newWidth = Math.round(viewWidth * mInvActualScale);
int newHeight = Math.round(getViewHeight() * mInvActualScale);
/*
* Because the native side may have already done a layout before the
@@ -1669,7 +1810,7 @@ public class WebView extends AbsoluteLayout
// Avoid sending another message if the dimensions have not changed.
if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED,
- newWidth, newHeight, new Float(mActualScale));
+ newWidth, newHeight, new Integer(viewWidth));
mLastWidthSent = newWidth;
mLastHeightSent = newHeight;
return true;
@@ -1968,7 +2109,7 @@ public class WebView extends AbsoluteLayout
// Scale from content to view coordinates, and pin.
// Also called by jni webview.cpp
- private void setContentScrollBy(int cx, int cy) {
+ private void setContentScrollBy(int cx, int cy, boolean animate) {
if (mDrawHistory) {
// disallow WebView to change the scroll position as History Picture
// is used in the view system.
@@ -1992,10 +2133,10 @@ public class WebView extends AbsoluteLayout
// vertical scroll?
// Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
if (cy == 0 && cx != 0) {
- pinScrollBy(cx, 0, true, 0);
+ pinScrollBy(cx, 0, animate, 0);
}
} else {
- pinScrollBy(cx, cy, true, 0);
+ pinScrollBy(cx, cy, animate, 0);
}
}
@@ -2205,7 +2346,8 @@ public class WebView extends AbsoluteLayout
// state.
// If mNativeClass is 0, we should not reach here, so we do not
// need to check it again.
- nativeRecordButtons(mTouchMode == TOUCH_SHORTPRESS_START_MODE
+ nativeRecordButtons(hasFocus() && hasWindowFocus(),
+ mTouchMode == TOUCH_SHORTPRESS_START_MODE
|| mTrackballDown || mGotEnterDown, false);
drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
}
@@ -2254,6 +2396,8 @@ public class WebView extends AbsoluteLayout
invalidate();
} else {
zoomScale = mZoomScale;
+ // set mZoomScale to be 0 as we have done animation
+ mZoomScale = 0;
}
float scale = (mActualScale - zoomScale) * mInvActualScale;
float tx = scale * (mZoomCenterX + mScrollX);
@@ -2775,6 +2919,17 @@ public class WebView extends AbsoluteLayout
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mTextEntry, 0);
mTextEntry.enableScrollOnScreen(true);
+ // Now we need to fake a touch event to place the cursor where the
+ // user touched.
+ AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams)
+ mTextEntry.getLayoutParams();
+ if (lp != null) {
+ // Take the last touch and adjust for the location of the
+ // TextDialog.
+ float x = mLastTouchX - lp.x;
+ float y = mLastTouchY - lp.y;
+ mTextEntry.fakeTouchEvent(x, y);
+ }
}
private void updateTextEntry() {
@@ -2987,7 +3142,9 @@ public class WebView extends AbsoluteLayout
mGotEnterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
- nativeRecordButtons(true, true);
+ // Already checked mNativeClass, so we do not need to check it
+ // again.
+ nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
return true;
}
// Bubble up the key event as WebView doesn't handle it
@@ -3198,6 +3355,9 @@ public class WebView extends AbsoluteLayout
ViewGroup p = (ViewGroup) parent;
p.setOnHierarchyChangeListener(null);
}
+
+ // Clean up the zoom ring
+ mZoomRingController.setVisible(false);
}
// Implementation for OnHierarchyChangeListener
@@ -3234,16 +3394,25 @@ public class WebView extends AbsoluteLayout
if (mNeedsUpdateTextEntry) {
updateTextEntry();
}
+ if (mNativeClass != 0) {
+ nativeRecordButtons(true, false, true);
+ }
} else {
// If our window gained focus, but we do not have it, do not
// draw the focus ring.
mDrawFocusRing = false;
+ // We do not call nativeRecordButtons here because we assume
+ // that when we lost focus, or window focus, it got called with
+ // false for the first parameter
}
} else {
// If our window has lost focus, stop drawing the focus ring
mDrawFocusRing = false;
mGotKeyDown = false;
mShiftIsPressed = false;
+ if (mNativeClass != 0) {
+ nativeRecordButtons(false, false, true);
+ }
}
invalidate();
super.onWindowFocusChanged(hasWindowFocus);
@@ -3264,12 +3433,22 @@ public class WebView extends AbsoluteLayout
updateTextEntry();
mNeedsUpdateTextEntry = false;
}
+ if (mNativeClass != 0) {
+ nativeRecordButtons(true, false, true);
+ }
+ //} else {
+ // The WebView has gained focus while we do not have
+ // windowfocus. When our window lost focus, we should have
+ // called nativeRecordButtons(false...)
}
} else {
// When we lost focus, unless focus went to the TextView (which is
// true if we are in editing mode), stop drawing the focus ring.
if (!inEditingMode()) {
mDrawFocusRing = false;
+ if (mNativeClass != 0) {
+ nativeRecordButtons(false, false, true);
+ }
}
mGotKeyDown = false;
}
@@ -3285,6 +3464,22 @@ public class WebView extends AbsoluteLayout
// the new zoom ring controller
mZoomCenterX = getViewWidth() * .5f;
mZoomCenterY = getViewHeight() * .5f;
+
+ // update mMinZoomScale
+ if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) {
+ boolean atMin = Math.abs(mActualScale - mMinZoomScale) < 0.01f;
+ mMinZoomScale = (float) getViewWidth() / mMinContentWidth;
+ if (atMin) {
+ // if the WebView was at the minimum zoom scale, keep it. e,g.,
+ // the WebView was at the minimum zoom scale at the portrait
+ // mode, rotate it to the landscape modifying the scale to the
+ // new minimum zoom scale, when rotating back, we would like to
+ // keep the minimum zoom scale instead of keeping the same scale
+ // as normally we do.
+ mActualScale = mMinZoomScale;
+ }
+ }
+
// we always force, in case our height changed, in which case we still
// want to send the notification over to webkit
setNewZoomScale(mActualScale, true);
@@ -3340,6 +3535,14 @@ public class WebView extends AbsoluteLayout
return false;
}
+ if (mShowZoomRingTutorial && mMinZoomScale < mMaxZoomScale) {
+ ZoomRingController.showZoomTutorialOnce(mContext);
+ mShowZoomRingTutorial = false;
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(DISMISS_ZOOM_RING_TUTORIAL),
+ ZOOM_RING_TUTORIAL_DURATION);
+ }
+
if (LOGV_ENABLED) {
Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode="
+ mTouchMode);
@@ -3749,7 +3952,7 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(SWITCH_TO_ENTER);
mTrackballDown = true;
if (mNativeClass != 0) {
- nativeRecordButtons(true, true);
+ nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
}
if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
&& !mLastFocusBounds.equals(nativeGetFocusRingBounds())) {
@@ -4148,6 +4351,9 @@ public class WebView extends AbsoluteLayout
});
zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() {
public void onClick(View v) {
+ // Hide the zoom ring
+ mZoomRingController.setVisible(false);
+
mPrivateHandler.removeCallbacks(mZoomControlRunnable);
mPrivateHandler.postDelayed(mZoomControlRunnable,
ZOOM_CONTROLS_TIMEOUT);
@@ -4200,6 +4406,12 @@ public class WebView extends AbsoluteLayout
}
}
+ // Called by JNI to handle a touch on a node representing an email address,
+ // address, or phone number
+ private void overrideLoading(String url) {
+ mCallbackProxy.uiOverrideUrlLoading(url);
+ }
+
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
boolean result = false;
@@ -4372,6 +4584,11 @@ public class WebView extends AbsoluteLayout
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
+ > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
+ : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
+ }
switch (msg.what) {
case REMEMBER_PASSWORD: {
mDatabase.setUsernamePassword(
@@ -4413,7 +4630,7 @@ public class WebView extends AbsoluteLayout
, KeyEvent.KEYCODE_ENTER));
break;
case SCROLL_BY_MSG_ID:
- setContentScrollBy(msg.arg1, msg.arg2);
+ setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj);
break;
case SYNC_SCROLL_TO_MSG_ID:
if (mUserScroll) {
@@ -4451,6 +4668,11 @@ public class WebView extends AbsoluteLayout
0, 0);
}
}
+ mMinContentWidth = msg.arg1;
+ if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) {
+ mMinZoomScale = (float) getViewWidth()
+ / mMinContentWidth;
+ }
// We update the layout (i.e. request a layout from the
// view system) if the last view size that we sent to
// WebCore matches the view size of the picture we just
@@ -4638,6 +4860,10 @@ public class WebView extends AbsoluteLayout
}
break;
+ case DISMISS_ZOOM_RING_TUTORIAL:
+ mZoomRingController.finishZoomTutorial();
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -5018,8 +5244,8 @@ public class WebView extends AbsoluteLayout
private native void nativeRecomputeFocus();
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
- private native void nativeRecordButtons(boolean pressed,
- boolean invalidate);
+ private native void nativeRecordButtons(boolean focused,
+ boolean pressed, boolean invalidate);
private native void nativeResetFocus();
private native void nativeResetNavClipBounds();
private native void nativeSelectBestAt(Rect rect);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index b979032..45113ab 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -271,6 +271,11 @@ final class WebViewCore {
static native String nativeFindAddress(String addr);
/**
+ * Rebuild the nav cache if the dom changed.
+ */
+ private native void nativeCheckNavCache();
+
+ /**
* Empty the picture set.
*/
private native void nativeClearContent();
@@ -317,7 +322,7 @@ final class WebViewCore {
should this be called nativeSetViewPortSize?
*/
private native void nativeSetSize(int width, int height, int screenWidth,
- float scale);
+ float scale, int realScreenWidth, int screenHeight);
private native int nativeGetContentMinPrefWidth();
@@ -501,6 +506,51 @@ final class WebViewCore {
int mY;
}
+ static final String[] HandlerDebugString = {
+ "LOAD_URL", // = 100;
+ "STOP_LOADING", // = 101;
+ "RELOAD", // = 102;
+ "KEY_DOWN", // = 103;
+ "KEY_UP", // = 104;
+ "VIEW_SIZE_CHANGED", // = 105;
+ "GO_BACK_FORWARD", // = 106;
+ "SET_SCROLL_OFFSET", // = 107;
+ "RESTORE_STATE", // = 108;
+ "PAUSE_TIMERS", // = 109;
+ "RESUME_TIMERS", // = 110;
+ "CLEAR_CACHE", // = 111;
+ "CLEAR_HISTORY", // = 112;
+ "SET_SELECTION", // = 113;
+ "REPLACE_TEXT", // = 114;
+ "PASS_TO_JS", // = 115;
+ "SET_GLOBAL_BOUNDS", // = 116;
+ "UPDATE_CACHE_AND_TEXT_ENTRY", // = 117;
+ "CLICK", // = 118;
+ "119",
+ "DOC_HAS_IMAGES", // = 120;
+ "SET_SNAP_ANCHOR", // = 121;
+ "DELETE_SELECTION", // = 122;
+ "LISTBOX_CHOICES", // = 123;
+ "SINGLE_LISTBOX_CHOICE", // = 124;
+ "125",
+ "SET_BACKGROUND_COLOR", // = 126;
+ "UNBLOCK_FOCUS", // = 127;
+ "SAVE_DOCUMENT_STATE", // = 128;
+ "GET_SELECTION", // = 129;
+ "WEBKIT_DRAW", // = 130;
+ "SYNC_SCROLL", // = 131;
+ "REFRESH_PLUGINS", // = 132;
+ "SPLIT_PICTURE_SET", // = 133;
+ "CLEAR_CONTENT", // = 134;
+ "SET_FINAL_FOCUS", // = 135;
+ "SET_KIT_FOCUS", // = 136;
+ "REQUEST_FOCUS_HREF", // = 137;
+ "ADD_JS_INTERFACE", // = 138;
+ "LOAD_DATA", // = 139;
+ "TOUCH_UP", // = 140;
+ "TOUCH_EVENT", // = 141;
+ };
+
class EventHub {
// Message Ids
static final int LOAD_URL = 100;
@@ -595,6 +645,11 @@ final class WebViewCore {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, msg.what < LOAD_URL || msg.what
+ > TOUCH_EVENT ? Integer.toString(msg.what)
+ : HandlerDebugString[msg.what - LOAD_URL]);
+ }
switch (msg.what) {
case WEBKIT_DRAW:
webkitDraw();
@@ -675,7 +730,7 @@ final class WebViewCore {
case VIEW_SIZE_CHANGED:
viewSizeChanged(msg.arg1, msg.arg2,
- ((Float) msg.obj).floatValue());
+ ((Integer) msg.obj).intValue());
break;
case SET_SCROLL_OFFSET:
@@ -1131,12 +1186,22 @@ final class WebViewCore {
private int mCurrentViewWidth = 0;
private int mCurrentViewHeight = 0;
+ // Define a minimum screen width so that we won't wrap the paragraph to one
+ // word per line during zoom-in.
+ private static final int MIN_SCREEN_WIDTH = 160;
+
// notify webkit that our virtual view size changed size (after inv-zoom)
- private void viewSizeChanged(int w, int h, float scale) {
+ private void viewSizeChanged(int w, int h, int viewWidth) {
if (LOGV_ENABLED) Log.v(LOGTAG, "CORE onSizeChanged");
+ if (w == 0) {
+ Log.w(LOGTAG, "skip viewSizeChanged as w is 0");
+ return;
+ }
+ float scale = (float) viewWidth / w;
if (mSettings.getUseWideViewPort()
&& (w < mViewportWidth || mViewportWidth == -1)) {
int width = mViewportWidth;
+ int screenWidth = Math.max(w, MIN_SCREEN_WIDTH);
if (mViewportWidth == -1) {
if (mSettings.getLayoutAlgorithm() ==
WebSettings.LayoutAlgorithm.NORMAL) {
@@ -1154,12 +1219,21 @@ final class WebViewCore {
* In the worse case, the native width will be adjusted when
* next zoom or screen orientation change happens.
*/
- width = Math.max(w, nativeGetContentMinPrefWidth());
+ int minContentWidth = nativeGetContentMinPrefWidth();
+ if (minContentWidth > WebView.MAX_FLOAT_CONTENT_WIDTH) {
+ // keep the same width and screen width so that there is
+ // no reflow when zoom-out
+ width = minContentWidth;
+ screenWidth = Math.min(screenWidth, viewWidth);
+ } else {
+ width = Math.max(w, minContentWidth);
+ }
}
}
- nativeSetSize(width, Math.round((float) width * h / w), w, scale);
+ nativeSetSize(width, Math.round((float) width * h / w),
+ screenWidth, scale, w, h);
} else {
- nativeSetSize(w, h, w, scale);
+ nativeSetSize(w, h, w, scale, w, h);
}
// Remember the current width and height
boolean needInvalidate = (mCurrentViewWidth == 0);
@@ -1219,7 +1293,9 @@ final class WebViewCore {
draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
Message.obtain(mWebView.mPrivateHandler,
- WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget();
+ WebView.NEW_PICTURE_MSG_ID, nativeGetContentMinPrefWidth(),
+ 0, draw).sendToTarget();
+ nativeCheckNavCache();
if (mWebkitScrollX != 0 || mWebkitScrollY != 0) {
// as we have the new picture, try to sync the scroll position
Message.obtain(mWebView.mPrivateHandler,
@@ -1324,7 +1400,9 @@ final class WebViewCore {
for (int i = 0; i < size; i++) {
list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame);
}
+ mBrowserFrame.mLoadInitFromJava = true;
list.restoreIndex(mBrowserFrame.mNativeFrame, index);
+ mBrowserFrame.mLoadInitFromJava = false;
}
//-------------------------------------------------------------------------
@@ -1349,14 +1427,15 @@ final class WebViewCore {
}
// called by JNI
- private void contentScrollBy(int dx, int dy) {
+ private void contentScrollBy(int dx, int dy, boolean animate) {
if (!mBrowserFrame.firstLayoutDone()) {
// Will this happen? If yes, we need to do something here.
return;
}
if (mWebView != null) {
Message.obtain(mWebView.mPrivateHandler,
- WebView.SCROLL_BY_MSG_ID, dx, dy).sendToTarget();
+ WebView.SCROLL_BY_MSG_ID, dx, dy,
+ new Boolean(animate)).sendToTarget();
}
}
@@ -1461,7 +1540,7 @@ final class WebViewCore {
// current scale
mEventHub.sendMessage(Message.obtain(null,
EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent,
- mWebView.mLastHeightSent, -1.0f));
+ mWebView.mLastHeightSent, new Integer(-1)));
}
mBrowserFrame.didFirstLayout();
diff --git a/core/java/android/webkit/gears/HttpRequestAndroid.java b/core/java/android/webkit/gears/HttpRequestAndroid.java
deleted file mode 100644
index 30f855f..0000000
--- a/core/java/android/webkit/gears/HttpRequestAndroid.java
+++ /dev/null
@@ -1,745 +0,0 @@
-// Copyright 2008, The Android Open Source Project
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-// 3. Neither the name of Google Inc. nor the names of its contributors may be
-// used to endorse or promote products derived from this software without
-// specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package android.webkit.gears;
-
-import android.net.http.Headers;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.webkit.CacheManager;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.CookieManager;
-
-import org.apache.http.conn.ssl.StrictHostnameVerifier;
-import org.apache.http.impl.cookie.DateUtils;
-import org.apache.http.util.CharArrayBuffer;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import javax.net.ssl.*;
-
-/**
- * Performs the underlying HTTP/HTTPS GET and POST requests.
- * <p> These are performed synchronously (blocking). The caller should
- * ensure that it is in a background thread if asynchronous behavior
- * is required. All data is pushed, so there is no need for JNI native
- * callbacks.
- * <p> This uses the java.net.HttpURLConnection class to perform most
- * of the underlying network activity. The Android brower's cache,
- * android.webkit.CacheManager, is also used when caching is enabled,
- * and updated with new data. The android.webkit.CookieManager is also
- * queried and updated as necessary.
- * <p> The public interface is designed to be called by native code
- * through JNI, and to simplify coding none of the public methods will
- * surface a checked exception. Unchecked exceptions may still be
- * raised but only if the system is in an ill state, such as out of
- * memory.
- * <p> TODO: This isn't plumbed into LocalServer yet. Mutually
- * dependent on LocalServer - will attach the two together once both
- * are submitted.
- */
-public final class HttpRequestAndroid {
- /** Debug logging tag. */
- private static final String LOG_TAG = "Gears-J";
- /** HTTP response header line endings are CR-LF style. */
- private static final String HTTP_LINE_ENDING = "\r\n";
- /** Safe MIME type to use whenever it isn't specified. */
- private static final String DEFAULT_MIME_TYPE = "text/plain";
- /** Case-sensitive header keys */
- public static final String KEY_CONTENT_LENGTH = "Content-Length";
- public static final String KEY_EXPIRES = "Expires";
- public static final String KEY_LAST_MODIFIED = "Last-Modified";
- public static final String KEY_ETAG = "ETag";
- public static final String KEY_LOCATION = "Location";
- public static final String KEY_CONTENT_TYPE = "Content-Type";
- /** Number of bytes to send and receive on the HTTP connection in
- * one go. */
- private static final int BUFFER_SIZE = 4096;
- /** The first element of the String[] value in a headers map is the
- * unmodified (case-sensitive) key. */
- public static final int HEADERS_MAP_INDEX_KEY = 0;
- /** The second element of the String[] value in a headers map is the
- * associated value. */
- public static final int HEADERS_MAP_INDEX_VALUE = 1;
-
- /** Enable/disable all logging in this class. */
- private static boolean logEnabled = false;
- /** The underlying HTTP or HTTPS network connection. */
- private HttpURLConnection connection;
- /** HTTP body stream, setup after connection. */
- private InputStream inputStream;
- /** The complete response line e.g "HTTP/1.0 200 OK" */
- private String responseLine;
- /** Request headers, as a lowercase key -> [ unmodified key, value ] map. */
- private Map<String, String[]> requestHeaders =
- new HashMap<String, String[]>();
- /** Response headers, as a lowercase key -> [ unmodified key, value ] map. */
- private Map<String, String[]> responseHeaders;
- /** True if the child thread is in performing blocking IO. */
- private boolean inBlockingOperation = false;
- /** True when the thread acknowledges the abort. */
- private boolean abortReceived = false;
- /** The URL used for createCacheResult() */
- private String cacheResultUrl;
- /** CacheResult being saved into, if inserting a new cache entry. */
- private CacheResult cacheResult;
- /** Initialized by initChildThread(). Used to target abort(). */
- private Thread childThread;
-
- /**
- * Convenience debug function. Calls Android logging mechanism.
- * @param str String to log to the Android console.
- */
- private static void log(String str) {
- if (logEnabled) {
- Log.i(LOG_TAG, str);
- }
- }
-
- /**
- * Turn on/off logging in this class.
- * @param on Logging enable state.
- */
- public static void enableLogging(boolean on) {
- logEnabled = on;
- }
-
- /**
- * Initialize childThread using the TLS value of
- * Thread.currentThread(). Called on start up of the native child
- * thread.
- */
- public synchronized void initChildThread() {
- childThread = Thread.currentThread();
- }
-
- /**
- * Analagous to the native-side HttpRequest::open() function. This
- * initializes an underlying java.net.HttpURLConnection, but does
- * not go to the wire. On success, this enables a call to send() to
- * initiate the transaction.
- *
- * @param method The HTTP method, e.g GET or POST.
- * @param url The URL to open.
- * @return True on success with a complete HTTP response.
- * False on failure.
- */
- public synchronized boolean open(String method, String url) {
- if (logEnabled)
- log("open " + method + " " + url);
- // Reset the response between calls to open().
- inputStream = null;
- responseLine = null;
- responseHeaders = null;
- if (!method.equals("GET") && !method.equals("POST")) {
- log("Method " + method + " not supported");
- return false;
- }
- // Setup the connection. This doesn't go to the wire yet - it
- // doesn't block.
- try {
- URL url_object = new URL(url);
- // Check that the protocol is indeed HTTP(S).
- String protocol = url_object.getProtocol();
- if (protocol == null) {
- log("null protocol for URL " + url);
- return false;
- }
- protocol = protocol.toLowerCase();
- if (!"http".equals(protocol) && !"https".equals(protocol)) {
- log("Url has wrong protocol: " + url);
- return false;
- }
-
- connection = (HttpURLConnection) url_object.openConnection();
- connection.setRequestMethod(method);
- // Manually follow redirects.
- connection.setInstanceFollowRedirects(false);
- // Manually cache.
- connection.setUseCaches(false);
- // Enable data output in POST method requests.
- connection.setDoOutput(method.equals("POST"));
- // Enable data input in non-HEAD method requests.
- // TODO: HEAD requests not tested.
- connection.setDoInput(!method.equals("HEAD"));
- if (connection instanceof javax.net.ssl.HttpsURLConnection) {
- // Verify the certificate matches the origin.
- ((HttpsURLConnection) connection).setHostnameVerifier(
- new StrictHostnameVerifier());
- }
- return true;
- } catch (IOException e) {
- log("Got IOException in open: " + e.toString());
- return false;
- }
- }
-
- /**
- * Interrupt a blocking IO operation. This will cause the child
- * thread to expediently return from an operation if it was stuck at
- * the time. Note that this inherently races, and unfortunately
- * requires the caller to loop.
- */
- public synchronized void interrupt() {
- if (childThread == null) {
- log("interrupt() called but no child thread");
- return;
- }
- synchronized (this) {
- if (inBlockingOperation) {
- log("Interrupting blocking operation");
- childThread.interrupt();
- } else {
- log("Nothing to interrupt");
- }
- }
- }
-
- /**
- * Set a header to send with the HTTP request. Will not take effect
- * on a transaction already in progress. The key is associated
- * case-insensitive, but stored case-sensitive.
- * @param name The name of the header, e.g "Set-Cookie".
- * @param value The value for this header, e.g "text/html".
- */
- public synchronized void setRequestHeader(String name, String value) {
- String[] mapValue = { name, value };
- requestHeaders.put(name.toLowerCase(), mapValue);
- }
-
- /**
- * Returns the value associated with the given request header.
- * @param name The name of the request header, non-null, case-insensitive.
- * @return The value associated with the request header, or null if
- * not set, or error.
- */
- public synchronized String getRequestHeader(String name) {
- String[] value = requestHeaders.get(name.toLowerCase());
- if (value != null) {
- return value[HEADERS_MAP_INDEX_VALUE];
- } else {
- return null;
- }
- }
-
- /**
- * Returns the value associated with the given response header.
- * @param name The name of the response header, non-null, case-insensitive.
- * @return The value associated with the response header, or null if
- * not set or error.
- */
- public synchronized String getResponseHeader(String name) {
- if (responseHeaders != null) {
- String[] value = responseHeaders.get(name.toLowerCase());
- if (value != null) {
- return value[HEADERS_MAP_INDEX_VALUE];
- } else {
- return null;
- }
- } else {
- log("getResponseHeader() called but response not received");
- return null;
- }
- }
-
- /**
- * Set a response header and associated value. The key is associated
- * case-insensitively, but stored case-sensitively.
- * @param name Case sensitive request header key.
- * @param value The associated value.
- */
- private void setResponseHeader(String name, String value) {
- if (logEnabled)
- log("Set response header " + name + ": " + value);
- String mapValue[] = { name, value };
- responseHeaders.put(name.toLowerCase(), mapValue);
- }
-
- /**
- * Apply the contents of the Map requestHeaders to the connection
- * object. Calls to setRequestHeader() after this will not affect
- * the connection.
- */
- private synchronized void applyRequestHeadersToConnection() {
- Iterator<String[]> it = requestHeaders.values().iterator();
- while (it.hasNext()) {
- // Set the key case-sensitive.
- String[] entry = it.next();
- connection.setRequestProperty(
- entry[HEADERS_MAP_INDEX_KEY],
- entry[HEADERS_MAP_INDEX_VALUE]);
- }
- }
-
- /**
- * Return all response headers, separated by CR-LF line endings, and
- * ending with a trailing blank line. This mimics the format of the
- * raw response header up to but not including the body.
- * @return A string containing the entire response header.
- */
- public synchronized String getAllResponseHeaders() {
- if (responseHeaders == null) {
- log("getAllResponseHeaders() called but response not received");
- return null;
- }
- String result = new String();
- Iterator<String[]> it = responseHeaders.values().iterator();
- while (it.hasNext()) {
- String[] entry = it.next();
- // Output the "key: value" lines.
- result += entry[HEADERS_MAP_INDEX_KEY] + ": "
- + entry[HEADERS_MAP_INDEX_VALUE] + HTTP_LINE_ENDING;
- }
- result += HTTP_LINE_ENDING;
- return result;
- }
-
- /**
- * Get the complete response line of the HTTP request. Only valid on
- * completion of the transaction.
- * @return The complete HTTP response line, e.g "HTTP/1.0 200 OK".
- */
- public synchronized String getResponseLine() {
- return responseLine;
- }
-
- /**
- * Get the cookie for the given URL.
- * @param url The fully qualified URL.
- * @return A string containing the cookie for the URL if it exists,
- * or null if not.
- */
- public static String getCookieForUrl(String url) {
- // Get the cookie for this URL, set as a header
- return CookieManager.getInstance().getCookie(url);
- }
-
- /**
- * Set the cookie for the given URL.
- * @param url The fully qualified URL.
- * @param cookie The new cookie value.
- * @return A string containing the cookie for the URL if it exists,
- * or null if not.
- */
- public static void setCookieForUrl(String url, String cookie) {
- // Get the cookie for this URL, set as a header
- CookieManager.getInstance().setCookie(url, cookie);
- }
-
- /**
- * Perform a request using LocalServer if possible. Initializes
- * class members so that receive() will obtain data from the stream
- * provided by the response.
- * @param url The fully qualified URL to try in LocalServer.
- * @return True if the url was found and is now setup to receive.
- * False if not found, with no side-effect.
- */
- public synchronized boolean useLocalServerResult(String url) {
- UrlInterceptHandlerGears handler = UrlInterceptHandlerGears.getInstance();
- if (handler == null) {
- return false;
- }
- UrlInterceptHandlerGears.ServiceResponse serviceResponse =
- handler.getServiceResponse(url, requestHeaders);
- if (serviceResponse == null) {
- log("No response in LocalServer");
- return false;
- }
- // LocalServer will handle this URL. Initialize stream and
- // response.
- inputStream = serviceResponse.getInputStream();
- responseLine = serviceResponse.getStatusLine();
- responseHeaders = serviceResponse.getResponseHeaders();
- if (logEnabled)
- log("Got response from LocalServer: " + responseLine);
- return true;
- }
-
- /**
- * Perform a request using the cache result if present. Initializes
- * class members so that receive() will obtain data from the cache.
- * @param url The fully qualified URL to try in the cache.
- * @return True is the url was found and is now setup to receive
- * from cache. False if not found, with no side-effect.
- */
- public synchronized boolean useCacheResult(String url) {
- // Try the browser's cache. CacheManager wants a Map<String, String>.
- Map<String, String> cacheRequestHeaders = new HashMap<String, String>();
- Iterator<Map.Entry<String, String[]>> it =
- requestHeaders.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<String, String[]> entry = it.next();
- cacheRequestHeaders.put(
- entry.getKey(),
- entry.getValue()[HEADERS_MAP_INDEX_VALUE]);
- }
- CacheResult cacheResult =
- CacheManager.getCacheFile(url, cacheRequestHeaders);
- if (cacheResult == null) {
- if (logEnabled)
- log("No CacheResult for " + url);
- return false;
- }
- if (logEnabled)
- log("Got CacheResult from browser cache");
- // Check for expiry. -1 is "never", otherwise milliseconds since 1970.
- // Can be compared to System.currentTimeMillis().
- long expires = cacheResult.getExpires();
- if (expires >= 0 && System.currentTimeMillis() >= expires) {
- log("CacheResult expired "
- + (System.currentTimeMillis() - expires)
- + " milliseconds ago");
- // Cache hit has expired. Do not return it.
- return false;
- }
- // Setup the inputStream to come from the cache.
- inputStream = cacheResult.getInputStream();
- if (inputStream == null) {
- // Cache result may have gone away.
- log("No inputStream for CacheResult " + url);
- return false;
- }
- // Cache hit. Parse headers.
- synthesizeHeadersFromCacheResult(cacheResult);
- return true;
- }
-
- /**
- * Take the limited set of headers in a CacheResult and synthesize
- * response headers.
- * @param cacheResult A CacheResult to populate responseHeaders with.
- */
- private void synthesizeHeadersFromCacheResult(CacheResult cacheResult) {
- int statusCode = cacheResult.getHttpStatusCode();
- // The status message is informal, so we can greatly simplify it.
- String statusMessage;
- if (statusCode >= 200 && statusCode < 300) {
- statusMessage = "OK";
- } else if (statusCode >= 300 && statusCode < 400) {
- statusMessage = "MOVED";
- } else {
- statusMessage = "UNAVAILABLE";
- }
- // Synthesize the response line.
- responseLine = "HTTP/1.1 " + statusCode + " " + statusMessage;
- if (logEnabled)
- log("Synthesized " + responseLine);
- // Synthesize the returned headers from cache.
- responseHeaders = new HashMap<String, String[]>();
- String contentLength = Long.toString(cacheResult.getContentLength());
- setResponseHeader(KEY_CONTENT_LENGTH, contentLength);
- long expires = cacheResult.getExpires();
- if (expires >= 0) {
- // "Expires" header is valid and finite. Milliseconds since 1970
- // epoch, formatted as RFC-1123.
- String expiresString = DateUtils.formatDate(new Date(expires));
- setResponseHeader(KEY_EXPIRES, expiresString);
- }
- String lastModified = cacheResult.getLastModified();
- if (lastModified != null) {
- // Last modification time of the page. Passed end-to-end, but
- // not used by us.
- setResponseHeader(KEY_LAST_MODIFIED, lastModified);
- }
- String eTag = cacheResult.getETag();
- if (eTag != null) {
- // Entity tag. A kind of GUID to identify identical resources.
- setResponseHeader(KEY_ETAG, eTag);
- }
- String location = cacheResult.getLocation();
- if (location != null) {
- // If valid, refers to the location of a redirect.
- setResponseHeader(KEY_LOCATION, location);
- }
- String mimeType = cacheResult.getMimeType();
- if (mimeType == null) {
- // Use a safe default MIME type when none is
- // specified. "text/plain" is safe to render in the browser
- // window (even if large) and won't be intepreted as anything
- // that would cause execution.
- mimeType = DEFAULT_MIME_TYPE;
- }
- String encoding = cacheResult.getEncoding();
- // Encoding may not be specified. No default.
- String contentType = mimeType;
- if (encoding != null && encoding.length() > 0) {
- contentType += "; charset=" + encoding;
- }
- setResponseHeader(KEY_CONTENT_TYPE, contentType);
- }
-
- /**
- * Create a CacheResult for this URL. This enables the repsonse body
- * to be sent in calls to appendCacheResult().
- * @param url The fully qualified URL to add to the cache.
- * @param responseCode The response code returned for the request, e.g 200.
- * @param mimeType The MIME type of the body, e.g "text/plain".
- * @param encoding The encoding, e.g "utf-8". Use "" for unknown.
- */
- public synchronized boolean createCacheResult(
- String url, int responseCode, String mimeType, String encoding) {
- if (logEnabled)
- log("Making cache entry for " + url);
- // Take the headers and parse them into a format needed by
- // CacheManager.
- Headers cacheHeaders = new Headers();
- Iterator<Map.Entry<String, String[]>> it =
- responseHeaders.entrySet().iterator();
- while (it.hasNext()) {
- Map.Entry<String, String[]> entry = it.next();
- // Headers.parseHeader() expects lowercase keys.
- String keyValue = entry.getKey() + ": "
- + entry.getValue()[HEADERS_MAP_INDEX_VALUE];
- CharArrayBuffer buffer = new CharArrayBuffer(keyValue.length());
- buffer.append(keyValue);
- // Parse it into the header container.
- cacheHeaders.parseHeader(buffer);
- }
- cacheResult = CacheManager.createCacheFile(
- url, responseCode, cacheHeaders, mimeType, true);
- if (cacheResult != null) {
- if (logEnabled)
- log("Saving into cache");
- cacheResult.setEncoding(encoding);
- cacheResultUrl = url;
- return true;
- } else {
- log("Couldn't create cacheResult");
- return false;
- }
- }
-
- /**
- * Add data from the response body to the CacheResult created with
- * createCacheResult().
- * @param data A byte array of the next sequential bytes in the
- * response body.
- * @param bytes The number of bytes to write from the start of
- * the array.
- * @return True if all bytes successfully written, false on failure.
- */
- public synchronized boolean appendCacheResult(byte[] data, int bytes) {
- if (cacheResult == null) {
- log("appendCacheResult() called without a CacheResult initialized");
- return false;
- }
- try {
- cacheResult.getOutputStream().write(data, 0, bytes);
- } catch (IOException ex) {
- log("Got IOException writing cache data: " + ex);
- return false;
- }
- return true;
- }
-
- /**
- * Save the completed CacheResult into the CacheManager. This must
- * have been created first with createCacheResult().
- * @return Returns true if the entry has been successfully saved.
- */
- public synchronized boolean saveCacheResult() {
- if (cacheResult == null || cacheResultUrl == null) {
- log("Tried to save cache result but createCacheResult not called");
- return false;
- }
- if (logEnabled)
- log("Saving cache result");
- CacheManager.saveCacheFile(cacheResultUrl, cacheResult);
- cacheResult = null;
- cacheResultUrl = null;
- return true;
- }
-
- /**
- * Perform an HTTP request on the network. The underlying
- * HttpURLConnection is connected to the remote server and the
- * response headers are received.
- * @return True if the connection succeeded and headers have been
- * received. False on connection failure.
- */
- public boolean connectToRemote() {
- synchronized (this) {
- // Transfer a snapshot of our internally maintained map of request
- // headers to the connection object.
- applyRequestHeadersToConnection();
- // Note blocking I/O so abort() can interrupt us.
- inBlockingOperation = true;
- }
- boolean success;
- try {
- if (logEnabled)
- log("Connecting to remote");
- connection.connect();
- if (logEnabled)
- log("Connected");
- success = true;
- } catch (IOException e) {
- log("Got IOException in connect(): " + e.toString());
- success = false;
- } finally {
- synchronized (this) {
- // No longer blocking.
- inBlockingOperation = false;
- }
- }
- return success;
- }
-
- /**
- * Receive all headers from the server and populate
- * responseHeaders. This converts from the slightly odd format
- * returned by java.net.HttpURLConnection to a simpler
- * java.util.Map.
- * @return True if headers are successfully received, False on
- * connection error.
- */
- public synchronized boolean parseHeaders() {
- responseHeaders = new HashMap<String, String[]>();
- /* HttpURLConnection contains a null terminated list of
- * key->value response pairs. If the key is null, then the value
- * contains the complete status line. If both key and value are
- * null for an index, we've reached the end.
- */
- for (int i = 0; ; ++i) {
- String key = connection.getHeaderFieldKey(i);
- String value = connection.getHeaderField(i);
- if (logEnabled)
- log("header " + key + " -> " + value);
- if (key == null && value == null) {
- // End of list.
- break;
- } else if (key == null) {
- // The pair with null key has the complete status line in
- // the value, e.g "HTTP/1.0 200 OK".
- responseLine = value;
- } else if (value != null) {
- // If key and value are non-null, this is a response pair, e.g
- // "Content-Length" -> "5". Use setResponseHeader() to
- // correctly deal with case-insensitivity of the key.
- setResponseHeader(key, value);
- } else {
- // The key is non-null but value is null. Unexpected
- // condition.
- return false;
- }
- }
- return true;
- }
-
- /**
- * Receive the next sequential bytes of the response body after
- * successful connection. This will receive up to the size of the
- * provided byte array. If there is no body, this will return 0
- * bytes on the first call after connection.
- * @param buf A pre-allocated byte array to receive data into.
- * @return The number of bytes from the start of the array which
- * have been filled, 0 on EOF, or negative on error.
- */
- public int receive(byte[] buf) {
- if (inputStream == null) {
- // If this is the first call, setup the InputStream. This may
- // fail if there were headers, but no body returned by the
- // server.
- try {
- inputStream = connection.getInputStream();
- } catch (IOException inputException) {
- log("Failed to connect InputStream: " + inputException);
- // Not unexpected. For example, 404 response return headers,
- // and sometimes a body with a detailed error. Try the error
- // stream.
- inputStream = connection.getErrorStream();
- if (inputStream == null) {
- // No error stream either. Treat as a 0 byte response.
- log("No InputStream");
- return 0; // EOF.
- }
- }
- }
- synchronized (this) {
- // Note blocking I/O so abort() can interrupt us.
- inBlockingOperation = true;
- }
- int ret;
- try {
- int got = inputStream.read(buf);
- if (got > 0) {
- // Got some bytes, not EOF.
- ret = got;
- } else {
- // EOF.
- inputStream.close();
- ret = 0;
- }
- } catch (IOException e) {
- // An abort() interrupts us by calling close() on our stream.
- log("Got IOException in inputStream.read(): " + e.toString());
- ret = -1;
- } finally {
- synchronized (this) {
- // No longer blocking.
- inBlockingOperation = false;
- }
- }
- return ret;
- }
-
- /**
- * For POST method requests, send a stream of data provided by the
- * native side in repeated callbacks.
- * @param data A byte array containing the data to sent, or null
- * if indicating EOF.
- * @param bytes The number of bytes from the start of the array to
- * send, or 0 if indicating EOF.
- * @return True if all bytes were successfully sent, false on error.
- */
- public boolean sendPostData(byte[] data, int bytes) {
- synchronized (this) {
- // Note blocking I/O so abort() can interrupt us.
- inBlockingOperation = true;
- }
- boolean success;
- try {
- OutputStream outputStream = connection.getOutputStream();
- if (data == null && bytes == 0) {
- outputStream.close();
- } else {
- outputStream.write(data, 0, bytes);
- }
- success = true;
- } catch (IOException e) {
- log("Got IOException in post: " + e.toString());
- success = false;
- } finally {
- synchronized (this) {
- // No longer blocking.
- inBlockingOperation = false;
- }
- }
- return success;
- }
-}
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
index 288240e..2a5cbe9 100644
--- a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -64,11 +64,11 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
/** The unmodified (case-sensitive) key in the headers map is the
* same index as used by HttpRequestAndroid. */
public static final int HEADERS_MAP_INDEX_KEY =
- HttpRequestAndroid.HEADERS_MAP_INDEX_KEY;
+ ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_KEY;
/** The associated value in the headers map is the same index as
* used by HttpRequestAndroid. */
public static final int HEADERS_MAP_INDEX_VALUE =
- HttpRequestAndroid.HEADERS_MAP_INDEX_VALUE;
+ ApacheHttpRequestAndroid.HEADERS_MAP_INDEX_VALUE;
/**
* Object passed to the native side, containing information about
@@ -382,7 +382,7 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
// browser's cache for too long.
long now_ms = System.currentTimeMillis();
String expires = DateUtils.formatDate(new Date(now_ms + CACHE_EXPIRY_MS));
- response.setResponseHeader(HttpRequestAndroid.KEY_EXPIRES, expires);
+ response.setResponseHeader(ApacheHttpRequestAndroid.KEY_EXPIRES, expires);
// The browser is only interested in a small subset of headers,
// contained in a Headers object. Iterate the map of all headers
// and add them to Headers.
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c012e25..f362e22 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -41,7 +41,7 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import android.view.WindowManagerImpl;
+import android.view.inputmethod.InputMethodManager;
import android.view.ContextMenu.ContextMenuInfo;
import com.android.internal.R;
@@ -425,6 +425,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private int mTouchSlop;
+ private float mDensityScale;
+
/**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
@@ -567,7 +569,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
@Override
protected boolean isVerticalScrollBarHidden() {
- return mFastScroller != null ? mFastScroller.isVisible() : false;
+ return mFastScroller != null && mFastScroller.isVisible();
}
/**
@@ -709,6 +711,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
setScrollingCacheEnabled(true);
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ mDensityScale = getContext().getResources().getDisplayMetrics().density;
}
private void useDefaultSelector() {
@@ -891,14 +894,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
// Don't restore the type filter window when there is no keyboard
- int keyboardHidden = getContext().getResources().getConfiguration().keyboardHidden;
- if (keyboardHidden != Configuration.KEYBOARDHIDDEN_YES) {
+ if (acceptFilter()) {
String filterText = ss.filter;
setFilterText(filterText);
}
+
requestLayout();
}
+ private boolean acceptFilter() {
+ final Context context = mContext;
+ final Configuration configuration = context.getResources().getConfiguration();
+ final boolean keyboardShowing = configuration.keyboardHidden !=
+ Configuration.KEYBOARDHIDDEN_YES;
+ final boolean hasKeyboard = configuration.keyboard != Configuration.KEYBOARD_NOKEYS;
+ final InputMethodManager inputManager = (InputMethodManager)
+ context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ return (hasKeyboard && keyboardShowing) ||
+ (!hasKeyboard && !inputManager.isFullscreenMode());
+ }
+
/**
* Sets the initial value for the text filter.
* @param filterText The text to use for the filter.
@@ -906,6 +921,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setTextFilterEnabled
*/
public void setFilterText(String filterText) {
+ // TODO: Should we check for acceptFilter()?
if (mTextFilterEnabled && filterText != null && filterText.length() > 0) {
createTextFilter(false);
// This is going to call our listener onTextChanged, but we might not
@@ -1076,6 +1092,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mInLayout = false;
}
+ /**
+ * @hide
+ */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ final boolean changed = super.setFrame(left, top, right, bottom);
+
+ // Reposition the popup when the frame has changed. This includes
+ // translating the widget, not just changing its dimension. The
+ // filter popup needs to follow the widget.
+ if (mFiltered && changed && getWindowVisibility() == View.VISIBLE && mPopup != null &&
+ mPopup.isShowing()) {
+ positionPopup(true);
+ }
+
+ return changed;
+ }
+
protected void layoutChildren() {
}
@@ -2587,10 +2621,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
clearScrollingCache();
mSpecificTop = selectedTop;
selectedPos = lookForSelectablePosition(selectedPos, down);
- if (selectedPos >= 0) {
+ if (selectedPos >= firstPosition && selectedPos <= getLastVisiblePosition()) {
mLayoutMode = LAYOUT_SPECIFIC;
setSelectionInt(selectedPos);
invokeOnItemScrollListener();
+ } else {
+ selectedPos = INVALID_POSITION;
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
@@ -2727,19 +2763,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private void showPopup() {
// Make sure we have a window before showing the popup
if (getWindowVisibility() == View.VISIBLE) {
- int screenHeight = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight();
- final int[] xy = new int[2];
- getLocationOnScreen(xy);
- // TODO: The 20 below should come from the theme and be expressed in dip
- final float scale = getContext().getResources().getDisplayMetrics().density;
- int bottomGap = screenHeight - xy[1] - getHeight() + (int) (scale * 20);
- mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
- xy[0], bottomGap);
+ positionPopup(false);
// Make sure we get focus if we are showing the popup
checkFocus();
}
}
+ private void positionPopup(boolean update) {
+ int screenHeight = getResources().getDisplayMetrics().heightPixels;
+ final int[] xy = new int[2];
+ getLocationOnScreen(xy);
+ // TODO: The 20 below should come from the theme and be expressed in dip
+ // TODO: And the gravity should be defined in the theme as well
+ final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20);
+ if (!update) {
+ mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ xy[0], bottomGap);
+ } else {
+ mPopup.update(xy[0], bottomGap, -1, -1);
+ }
+ }
+
/**
* What is the distance between the source and destination rectangles given the direction of
* focus navigation between them? The direction basically helps figure out more quickly what is
@@ -2831,7 +2875,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
break;
}
- if (okToSend) {
+ if (okToSend && acceptFilter()) {
createTextFilter(true);
KeyEvent forwardEvent = event;
@@ -2873,6 +2917,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTextFilter.addTextChangedListener(this);
p.setFocusable(false);
p.setTouchable(false);
+ p.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
p.setContentView(mTextFilter);
p.setWidth(LayoutParams.WRAP_CONTENT);
p.setHeight(LayoutParams.WRAP_CONTENT);
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 7086ae2..369221e 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -46,6 +46,18 @@ import java.util.Locale;
public class Chronometer extends TextView {
private static final String TAG = "Chronometer";
+ /**
+ * A callback that notifies when the chronometer has incremented on its own.
+ */
+ public interface OnChronometerTickListener {
+
+ /**
+ * Notification that the chronometer has changed.
+ */
+ void onChronometerTick(Chronometer chronometer);
+
+ }
+
private long mBase;
private boolean mVisible;
private boolean mStarted;
@@ -56,6 +68,7 @@ public class Chronometer extends TextView {
private Locale mFormatterLocale;
private Object[] mFormatterArgs = new Object[1];
private StringBuilder mFormatBuilder;
+ private OnChronometerTickListener mOnChronometerTickListener;
/**
* Initialize this Chronometer object.
@@ -99,6 +112,7 @@ public class Chronometer extends TextView {
*
* @param base Use the {@link SystemClock#elapsedRealtime} time base.
*/
+ @android.view.RemotableViewMethod
public void setBase(long base) {
mBase = base;
updateText(SystemClock.elapsedRealtime());
@@ -122,6 +136,7 @@ public class Chronometer extends TextView {
*
* @param format the format string.
*/
+ @android.view.RemotableViewMethod
public void setFormat(String format) {
mFormat = format;
if (format != null && mFormatBuilder == null) {
@@ -137,6 +152,23 @@ public class Chronometer extends TextView {
}
/**
+ * Sets the listener to be called when the chronometer changes.
+ *
+ * @param listener The listener.
+ */
+ public void setOnChronometerTickListener(OnChronometerTickListener listener) {
+ mOnChronometerTickListener = listener;
+ }
+
+ /**
+ * @return The listener (may be null) that is listening for chronometer change
+ * events.
+ */
+ public OnChronometerTickListener getOnChronometerTickListener() {
+ return mOnChronometerTickListener;
+ }
+
+ /**
* Start counting up. This does not affect the base as set from {@link #setBase}, just
* the view display.
*
@@ -161,6 +193,15 @@ public class Chronometer extends TextView {
updateRunning();
}
+ /**
+ * The same as calling {@link #start} or {@link #stop}.
+ */
+ @android.view.RemotableViewMethod
+ public void setStarted(boolean started) {
+ mStarted = started;
+ updateRunning();
+ }
+
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
@@ -216,8 +257,15 @@ public class Chronometer extends TextView {
public void handleMessage(Message m) {
if (mStarted) {
updateText(SystemClock.elapsedRealtime());
+ dispatchChronometerTick();
sendMessageDelayed(Message.obtain(), 1000);
}
}
};
+
+ void dispatchChronometerTick() {
+ if (mOnChronometerTickListener != null) {
+ mOnChronometerTickListener.onChronometerTick(this);
+ }
+ }
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index b4ed3ba..8aafee2 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -93,6 +93,7 @@ public class FrameLayout extends ViewGroup {
*
* @attr ref android.R.styleable#FrameLayout_foregroundGravity
*/
+ @android.view.RemotableViewMethod
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
@@ -348,6 +349,7 @@ public class FrameLayout extends ViewGroup {
*
* @attr ref android.R.styleable#FrameLayout_measureAllChildren
*/
+ @android.view.RemotableViewMethod
public void setMeasureAllChildren(boolean measureAll) {
mMeasureAllChildren = measureAll;
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 4ae322e..94d1bd1 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -193,6 +193,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
*/
+ @android.view.RemotableViewMethod
public void setAdjustViewBounds(boolean adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds;
if (adjustViewBounds) {
@@ -217,6 +218,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_maxWidth
*/
+ @android.view.RemotableViewMethod
public void setMaxWidth(int maxWidth) {
mMaxWidth = maxWidth;
}
@@ -238,6 +240,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_maxHeight
*/
+ @android.view.RemotableViewMethod
public void setMaxHeight(int maxHeight) {
mMaxHeight = maxHeight;
}
@@ -256,6 +259,7 @@ public class ImageView extends View {
*
* @attr ref android.R.styleable#ImageView_src
*/
+ @android.view.RemotableViewMethod
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
updateDrawable(null);
@@ -272,6 +276,7 @@ public class ImageView extends View {
*
* @param uri The Uri of an image
*/
+ @android.view.RemotableViewMethod
public void setImageURI(Uri uri) {
if (mResource != 0 ||
(mUri != uri &&
@@ -306,6 +311,7 @@ public class ImageView extends View {
*
* @param bm The bitmap to set
*/
+ @android.view.RemotableViewMethod
public void setImageBitmap(Bitmap bm) {
// if this is used frequently, may handle bitmaps explicitly
// to reduce the intermediate drawable object
@@ -327,6 +333,7 @@ public class ImageView extends View {
resizeFromDrawable();
}
+ @android.view.RemotableViewMethod
public void setImageLevel(int level) {
mLevel = level;
if (mDrawable != null) {
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 85a7339..a9822f8 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -136,6 +136,7 @@ public class LinearLayout extends ViewGroup {
*
* @attr ref android.R.styleable#LinearLayout_baselineAligned
*/
+ @android.view.RemotableViewMethod
public void setBaselineAligned(boolean baselineAligned) {
mBaselineAligned = baselineAligned;
}
@@ -208,6 +209,7 @@ public class LinearLayout extends ViewGroup {
*
* @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
*/
+ @android.view.RemotableViewMethod
public void setBaselineAlignedChildIndex(int i) {
if ((i < 0) || (i >= getChildCount())) {
throw new IllegalArgumentException("base aligned child index out "
@@ -265,6 +267,7 @@ public class LinearLayout extends ViewGroup {
* to 0.0f if the weight sum should be computed from the children's
* layout_weight
*/
+ @android.view.RemotableViewMethod
public void setWeightSum(float weightSum) {
mWeightSum = Math.max(0.0f, weightSum);
}
@@ -1149,6 +1152,7 @@ public class LinearLayout extends ViewGroup {
*
* @attr ref android.R.styleable#LinearLayout_gravity
*/
+ @android.view.RemotableViewMethod
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
@@ -1164,6 +1168,7 @@ public class LinearLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setHorizontalGravity(int horizontalGravity) {
final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
@@ -1172,6 +1177,7 @@ public class LinearLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setVerticalGravity(int verticalGravity) {
final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 9c7f600..4e5989c 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -2179,6 +2179,10 @@ public class ListView extends AbsListView {
&& !isViewAncestorOf(selectedView, this)) {
selectedView = null;
hideSelector();
+
+ // but we don't want to set the ressurect position (that would make subsequent
+ // unhandled key events bring back the item we just scrolled off!)
+ mResurrectToPosition = INVALID_POSITION;
}
if (needToRedraw) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index dada105..4a5cea1 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -24,6 +24,8 @@ import android.view.View;
import android.view.WindowManager;
import android.view.Gravity;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.View.OnTouchListener;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -33,6 +35,8 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import java.lang.ref.WeakReference;
+
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
* windows is a floating container that appears on top of the current
@@ -109,7 +113,23 @@ public class PopupWindow {
private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
com.android.internal.R.attr.state_above_anchor
};
-
+
+ private WeakReference<View> mAnchor;
+ private OnScrollChangedListener mOnScrollChangedListener =
+ new OnScrollChangedListener() {
+ public void onScrollChanged() {
+ View anchor = mAnchor.get();
+ if (anchor != null && mPopupView != null) {
+ WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+ mPopupView.getLayoutParams();
+
+ mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff);
+ update(p.x, p.y, -1, -1, true);
+ }
+ }
+ };
+ private int mAnchorXoff, mAnchorYoff;
+
/**
* <p>Create a new empty, non focusable popup window of dimension (0,0).</p>
*
@@ -579,6 +599,8 @@ public class PopupWindow {
return;
}
+ unregisterForScrollChanged();
+
mIsShowing = true;
mIsDropdown = false;
@@ -617,6 +639,8 @@ public class PopupWindow {
* the popup in its entirety, this method tries to find a parent scroll
* view to scroll. If no parent scroll view can be scrolled, the bottom-left
* corner of the popup is pinned at the top left corner of the anchor view.</p>
+ * <p>If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.</p>
*
* @param anchor the view on which to pin the popup window
*
@@ -627,6 +651,8 @@ public class PopupWindow {
return;
}
+ registerForScrollChanged(anchor, xoff, yoff);
+
mIsShowing = true;
mIsDropdown = true;
@@ -894,6 +920,8 @@ public class PopupWindow {
*/
public void dismiss() {
if (isShowing() && mPopupView != null) {
+ unregisterForScrollChanged();
+
mWindowManager.removeView(mPopupView);
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
((ViewGroup) mPopupView).removeView(mContentView);
@@ -962,6 +990,25 @@ public class PopupWindow {
* @param height the new height, can be -1 to ignore
*/
public void update(int x, int y, int width, int height) {
+ update(x, y, width, height, false);
+ }
+
+ /**
+ * <p>Updates the position and the dimension of the popup window. Width and
+ * height can be set to -1 to update location only. Calling this function
+ * also updates the window with the current popup state as
+ * described for {@link #update()}.</p>
+ *
+ * @param x the new x location
+ * @param y the new y location
+ * @param width the new width, can be -1 to ignore
+ * @param height the new height, can be -1 to ignore
+ * @param force reposition the window even if the specified position
+ * already seems to correspond to the LayoutParams
+ *
+ * @hide pending API council approval
+ */
+ public void update(int x, int y, int width, int height, boolean force) {
if (width != -1) {
mLastWidth = width;
setWidth(width);
@@ -979,7 +1026,7 @@ public class PopupWindow {
WindowManager.LayoutParams p = (WindowManager.LayoutParams)
mPopupView.getLayoutParams();
- boolean update = false;
+ boolean update = force;
final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth;
if (width != -1 && p.width != finalWidth) {
@@ -1039,6 +1086,8 @@ public class PopupWindow {
* height can be set to -1 to update location only. Calling this function
* also updates the window with the current popup state as
* described for {@link #update()}.</p>
+ * <p>If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.</p>
*
* @param anchor the popup's anchor view
* @param xoff x offset from the view's left edge
@@ -1051,6 +1100,12 @@ public class PopupWindow {
return;
}
+ WeakReference<View> oldAnchor = mAnchor;
+ if (oldAnchor == null || oldAnchor.get() != anchor ||
+ mAnchorXoff != xoff || mAnchorYoff != yoff) {
+ registerForScrollChanged(anchor, xoff, yoff);
+ }
+
WindowManager.LayoutParams p = (WindowManager.LayoutParams)
mPopupView.getLayoutParams();
@@ -1065,10 +1120,10 @@ public class PopupWindow {
mPopupHeight = height;
}
- findDropDownPosition(anchor, p, xoff, yoff);
+ mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
update(p.x, p.y, width, height);
}
-
+
/**
* Listener that is called when this popup window is dismissed.
*/
@@ -1078,7 +1133,33 @@ public class PopupWindow {
*/
public void onDismiss();
}
-
+
+ private void unregisterForScrollChanged() {
+ WeakReference<View> anchorRef = mAnchor;
+ View anchor = null;
+ if (anchorRef != null) {
+ anchor = anchorRef.get();
+ }
+ if (anchor != null) {
+ ViewTreeObserver vto = anchor.getViewTreeObserver();
+ vto.removeOnScrollChangedListener(mOnScrollChangedListener);
+ }
+ mAnchor = null;
+ }
+
+ private void registerForScrollChanged(View anchor, int xoff, int yoff) {
+ unregisterForScrollChanged();
+
+ mAnchor = new WeakReference<View>(anchor);
+ ViewTreeObserver vto = anchor.getViewTreeObserver();
+ if (vto != null) {
+ vto.addOnScrollChangedListener(mOnScrollChangedListener);
+ }
+
+ mAnchorXoff = xoff;
+ mAnchorYoff = yoff;
+ }
+
private class PopupViewContainer extends FrameLayout {
public PopupViewContainer(Context context) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 434e9f3..dd2570a 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -344,6 +344,7 @@ public class ProgressBar extends View {
*
* @param indeterminate true to enable the indeterminate mode
*/
+ @android.view.RemotableViewMethod
public synchronized void setIndeterminate(boolean indeterminate) {
if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
mIndeterminate = indeterminate;
@@ -529,6 +530,7 @@ public class ProgressBar extends View {
setProgress(progress, false);
}
+ @android.view.RemotableViewMethod
synchronized void setProgress(int progress, boolean fromUser) {
if (mIndeterminate) {
return;
@@ -560,6 +562,7 @@ public class ProgressBar extends View {
* @see #getSecondaryProgress()
* @see #incrementSecondaryProgressBy(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setSecondaryProgress(int secondaryProgress) {
if (mIndeterminate) {
return;
@@ -633,6 +636,7 @@ public class ProgressBar extends View {
* @see #setProgress(int)
* @see #setSecondaryProgress(int)
*/
+ @android.view.RemotableViewMethod
public synchronized void setMax(int max) {
if (max < 0) {
max = 0;
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 9ded52b..ba63ec3 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -168,6 +168,7 @@ public class RelativeLayout extends ViewGroup {
*
* @attr ref android.R.styleable#RelativeLayout_ignoreGravity
*/
+ @android.view.RemotableViewMethod
public void setIgnoreGravity(int viewId) {
mIgnoreGravity = viewId;
}
@@ -183,6 +184,7 @@ public class RelativeLayout extends ViewGroup {
*
* @attr ref android.R.styleable#RelativeLayout_gravity
*/
+ @android.view.RemotableViewMethod
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
@@ -198,6 +200,7 @@ public class RelativeLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setHorizontalGravity(int horizontalGravity) {
final int gravity = horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != gravity) {
@@ -206,6 +209,7 @@ public class RelativeLayout extends ViewGroup {
}
}
+ @android.view.RemotableViewMethod
public void setVerticalGravity(int verticalGravity) {
final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 25afee8..e000d2e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -31,6 +31,7 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater.Filter;
@@ -38,10 +39,13 @@ import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import java.lang.Class;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -80,19 +84,22 @@ public class RemoteViews implements Parcelable, Filter {
/**
- * This annotation indicates that a subclass of View is alllowed to be used with the
- * {@link android.widget.RemoteViews} mechanism.
+ * This annotation indicates that a subclass of View is alllowed to be used
+ * with the {@link android.widget.RemoteViews} mechanism.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoteView {
}
-
+
/**
* Exception to send when something goes wrong executing an action
*
*/
public static class ActionException extends RuntimeException {
+ public ActionException(Exception ex) {
+ super(ex);
+ }
public ActionException(String message) {
super(message);
}
@@ -110,274 +117,7 @@ public class RemoteViews implements Parcelable, Filter {
return 0;
}
};
-
- /**
- * Equivalent to calling View.setVisibility
- */
- private class SetViewVisibility extends Action {
- public SetViewVisibility(int id, int vis) {
- viewId = id;
- visibility = vis;
- }
-
- public SetViewVisibility(Parcel parcel) {
- viewId = parcel.readInt();
- visibility = parcel.readInt();
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(visibility);
- }
-
- @Override
- public void apply(View root) {
- View target = root.findViewById(viewId);
- if (target != null) {
- target.setVisibility(visibility);
- }
- }
-
- private int viewId;
- private int visibility;
- public final static int TAG = 0;
- }
-
- /**
- * Equivalent to calling TextView.setText
- */
- private class SetTextViewText extends Action {
- public SetTextViewText(int id, CharSequence t) {
- viewId = id;
- text = t;
- }
-
- public SetTextViewText(Parcel parcel) {
- viewId = parcel.readInt();
- text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- TextUtils.writeToParcel(text, dest, flags);
- }
-
- @Override
- public void apply(View root) {
- TextView target = (TextView) root.findViewById(viewId);
- if (target != null) {
- target.setText(text);
- }
- }
-
- int viewId;
- CharSequence text;
- public final static int TAG = 1;
- }
-
- /**
- * Equivalent to calling ImageView.setResource
- */
- private class SetImageViewResource extends Action {
- public SetImageViewResource(int id, int src) {
- viewId = id;
- srcId = src;
- }
-
- public SetImageViewResource(Parcel parcel) {
- viewId = parcel.readInt();
- srcId = parcel.readInt();
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(srcId);
- }
-
- @Override
- public void apply(View root) {
- ImageView target = (ImageView) root.findViewById(viewId);
- Drawable d = mContext.getResources().getDrawable(srcId);
- if (target != null) {
- target.setImageDrawable(d);
- }
- }
-
- int viewId;
- int srcId;
- public final static int TAG = 2;
- }
-
- /**
- * Equivalent to calling ImageView.setImageURI
- */
- private class SetImageViewUri extends Action {
- public SetImageViewUri(int id, Uri u) {
- viewId = id;
- uri = u;
- }
-
- public SetImageViewUri(Parcel parcel) {
- viewId = parcel.readInt();
- uri = Uri.CREATOR.createFromParcel(parcel);
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- Uri.writeToParcel(dest, uri);
- }
-
- @Override
- public void apply(View root) {
- ImageView target = (ImageView) root.findViewById(viewId);
- if (target != null) {
- target.setImageURI(uri);
- }
- }
-
- int viewId;
- Uri uri;
- public final static int TAG = 3;
- }
-
- /**
- * Equivalent to calling ImageView.setImageBitmap
- */
- private class SetImageViewBitmap extends Action {
- public SetImageViewBitmap(int id, Bitmap src) {
- viewId = id;
- bitmap = src;
- }
-
- public SetImageViewBitmap(Parcel parcel) {
- viewId = parcel.readInt();
- bitmap = Bitmap.CREATOR.createFromParcel(parcel);
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- if (bitmap != null) {
- bitmap.writeToParcel(dest, flags);
- }
- }
-
- @Override
- public void apply(View root) {
- if (bitmap != null) {
- ImageView target = (ImageView) root.findViewById(viewId);
- Drawable d = new BitmapDrawable(bitmap);
- if (target != null) {
- target.setImageDrawable(d);
- }
- }
- }
- int viewId;
- Bitmap bitmap;
- public final static int TAG = 4;
- }
-
- /**
- * Equivalent to calling Chronometer.setBase, Chronometer.setFormat,
- * and Chronometer.start/stop.
- */
- private class SetChronometer extends Action {
- public SetChronometer(int id, long base, String format, boolean running) {
- this.viewId = id;
- this.base = base;
- this.format = format;
- this.running = running;
- }
-
- public SetChronometer(Parcel parcel) {
- viewId = parcel.readInt();
- base = parcel.readLong();
- format = parcel.readString();
- running = parcel.readInt() != 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeLong(base);
- dest.writeString(format);
- dest.writeInt(running ? 1 : 0);
- }
-
- @Override
- public void apply(View root) {
- Chronometer target = (Chronometer) root.findViewById(viewId);
- if (target != null) {
- target.setBase(base);
- target.setFormat(format);
- if (running) {
- target.start();
- } else {
- target.stop();
- }
- }
- }
-
- int viewId;
- boolean running;
- long base;
- String format;
-
- public final static int TAG = 5;
- }
-
- /**
- * Equivalent to calling ProgressBar.setMax, ProgressBar.setProgress and
- * ProgressBar.setIndeterminate
- */
- private class SetProgressBar extends Action {
- public SetProgressBar(int id, int max, int progress, boolean indeterminate) {
- this.viewId = id;
- this.progress = progress;
- this.max = max;
- this.indeterminate = indeterminate;
- }
-
- public SetProgressBar(Parcel parcel) {
- viewId = parcel.readInt();
- progress = parcel.readInt();
- max = parcel.readInt();
- indeterminate = parcel.readInt() != 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(progress);
- dest.writeInt(max);
- dest.writeInt(indeterminate ? 1 : 0);
- }
-
- @Override
- public void apply(View root) {
- ProgressBar target = (ProgressBar) root.findViewById(viewId);
- if (target != null) {
- target.setIndeterminate(indeterminate);
- if (!indeterminate) {
- target.setMax(max);
- target.setProgress(progress);
- }
- }
- }
-
- int viewId;
- boolean indeterminate;
- int progress;
- int max;
-
- public final static int TAG = 6;
- }
-
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
@@ -421,7 +161,7 @@ public class RemoteViews implements Parcelable, Filter {
int viewId;
PendingIntent pendingIntent;
- public final static int TAG = 7;
+ public final static int TAG = 1;
}
/**
@@ -511,92 +251,215 @@ public class RemoteViews implements Parcelable, Filter {
PorterDuff.Mode filterMode;
int level;
- public final static int TAG = 8;
+ public final static int TAG = 3;
}
/**
- * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
+ * Base class for the reflection actions.
*/
- private class SetTextColor extends Action {
- public SetTextColor(int id, int color) {
- this.viewId = id;
- this.color = color;
- }
-
- public SetTextColor(Parcel parcel) {
- viewId = parcel.readInt();
- color = parcel.readInt();
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(color);
- }
-
- @Override
- public void apply(View root) {
- final View target = root.findViewById(viewId);
- if (target instanceof TextView) {
- final TextView textView = (TextView) target;
- textView.setTextColor(color);
+ private class ReflectionAction extends Action {
+ static final int TAG = 2;
+
+ static final int BOOLEAN = 1;
+ static final int BYTE = 2;
+ static final int SHORT = 3;
+ static final int INT = 4;
+ static final int LONG = 5;
+ static final int FLOAT = 6;
+ static final int DOUBLE = 7;
+ static final int CHAR = 8;
+ static final int STRING = 9;
+ static final int CHAR_SEQUENCE = 10;
+ static final int URI = 11;
+ static final int BITMAP = 12;
+
+ int viewId;
+ String methodName;
+ int type;
+ Object value;
+
+ ReflectionAction(int viewId, String methodName, int type, Object value) {
+ this.viewId = viewId;
+ this.methodName = methodName;
+ this.type = type;
+ this.value = value;
+ }
+
+ ReflectionAction(Parcel in) {
+ this.viewId = in.readInt();
+ this.methodName = in.readString();
+ this.type = in.readInt();
+ if (false) {
+ Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
+ + " methodName=" + this.methodName + " type=" + this.type);
+ }
+ switch (this.type) {
+ case BOOLEAN:
+ this.value = in.readInt() != 0;
+ break;
+ case BYTE:
+ this.value = in.readByte();
+ break;
+ case SHORT:
+ this.value = (short)in.readInt();
+ break;
+ case INT:
+ this.value = in.readInt();
+ break;
+ case LONG:
+ this.value = in.readLong();
+ break;
+ case FLOAT:
+ this.value = in.readFloat();
+ break;
+ case DOUBLE:
+ this.value = in.readDouble();
+ break;
+ case CHAR:
+ this.value = (char)in.readInt();
+ break;
+ case STRING:
+ this.value = in.readString();
+ break;
+ case CHAR_SEQUENCE:
+ this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ break;
+ case URI:
+ this.value = Uri.CREATOR.createFromParcel(in);
+ break;
+ case BITMAP:
+ this.value = Bitmap.CREATOR.createFromParcel(in);
+ break;
+ default:
+ break;
}
}
-
- int viewId;
- int color;
- public final static int TAG = 9;
- }
-
- /**
- * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()}
- * or {@link android.widget.ViewFlipper#stopFlipping()} along with
- * {@link android.widget.ViewFlipper#setFlipInterval(int)}.
- */
- private class SetFlipping extends Action {
- public SetFlipping(int id, boolean flipping, int milliseconds) {
- this.viewId = id;
- this.flipping = flipping;
- this.milliseconds = milliseconds;
- }
-
- public SetFlipping(Parcel parcel) {
- viewId = parcel.readInt();
- flipping = parcel.readInt() != 0;
- milliseconds = parcel.readInt();
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(TAG);
+ out.writeInt(this.viewId);
+ out.writeString(this.methodName);
+ out.writeInt(this.type);
+ if (false) {
+ Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
+ + " methodName=" + this.methodName + " type=" + this.type);
+ }
+ switch (this.type) {
+ case BOOLEAN:
+ out.writeInt(((Boolean)this.value).booleanValue() ? 1 : 0);
+ break;
+ case BYTE:
+ out.writeByte(((Byte)this.value).byteValue());
+ break;
+ case SHORT:
+ out.writeInt(((Short)this.value).shortValue());
+ break;
+ case INT:
+ out.writeInt(((Integer)this.value).intValue());
+ break;
+ case LONG:
+ out.writeLong(((Long)this.value).longValue());
+ break;
+ case FLOAT:
+ out.writeFloat(((Float)this.value).floatValue());
+ break;
+ case DOUBLE:
+ out.writeDouble(((Double)this.value).doubleValue());
+ break;
+ case CHAR:
+ out.writeInt((int)((Character)this.value).charValue());
+ break;
+ case STRING:
+ out.writeString((String)this.value);
+ break;
+ case CHAR_SEQUENCE:
+ TextUtils.writeToParcel((CharSequence)this.value, out, flags);
+ break;
+ case URI:
+ ((Uri)this.value).writeToParcel(out, flags);
+ break;
+ case BITMAP:
+ ((Bitmap)this.value).writeToParcel(out, flags);
+ break;
+ default:
+ break;
+ }
}
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TAG);
- dest.writeInt(viewId);
- dest.writeInt(flipping ? 1 : 0);
- dest.writeInt(milliseconds);
+
+ private Class getParameterType() {
+ switch (this.type) {
+ case BOOLEAN:
+ return boolean.class;
+ case BYTE:
+ return byte.class;
+ case SHORT:
+ return short.class;
+ case INT:
+ return int.class;
+ case LONG:
+ return long.class;
+ case FLOAT:
+ return float.class;
+ case DOUBLE:
+ return double.class;
+ case CHAR:
+ return char.class;
+ case STRING:
+ return String.class;
+ case CHAR_SEQUENCE:
+ return CharSequence.class;
+ case URI:
+ return Uri.class;
+ case BITMAP:
+ return Bitmap.class;
+ default:
+ return null;
+ }
}
-
+
@Override
public void apply(View root) {
- final View target = root.findViewById(viewId);
- if (target instanceof ViewFlipper) {
- final ViewFlipper flipper = (ViewFlipper) target;
- if (milliseconds != -1) {
- flipper.setFlipInterval(milliseconds);
- }
- if (flipping) {
- flipper.startFlipping();
- } else {
- flipper.stopFlipping();
+ final View view = root.findViewById(viewId);
+ if (view == null) {
+ throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
+ }
+
+ Class param = getParameterType();
+ if (param == null) {
+ throw new ActionException("bad type: " + this.type);
+ }
+
+ Class klass = view.getClass();
+ Method method = null;
+ try {
+ method = klass.getMethod(this.methodName, getParameterType());
+ }
+ catch (NoSuchMethodException ex) {
+ throw new ActionException("view: " + klass.getName() + " doesn't have method: "
+ + this.methodName + "(" + param.getName() + ")");
+ }
+
+ if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
+ throw new ActionException("view: " + klass.getName()
+ + " can't use method with RemoteViews: "
+ + this.methodName + "(" + param.getName() + ")");
+ }
+
+ try {
+ if (false) {
+ Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ + this.methodName + "(" + param.getName() + ") with "
+ + (this.value == null ? "null" : this.value.getClass().getName()));
}
+ method.invoke(view, this.value);
+ }
+ catch (Exception ex) {
+ throw new ActionException(ex);
}
}
-
- int viewId;
- boolean flipping;
- int milliseconds;
-
- public final static int TAG = 10;
}
+
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
@@ -623,41 +486,17 @@ public class RemoteViews implements Parcelable, Filter {
for (int i=0; i<count; i++) {
int tag = parcel.readInt();
switch (tag) {
- case SetViewVisibility.TAG:
- mActions.add(new SetViewVisibility(parcel));
- break;
- case SetTextViewText.TAG:
- mActions.add(new SetTextViewText(parcel));
- break;
- case SetImageViewResource.TAG:
- mActions.add(new SetImageViewResource(parcel));
- break;
- case SetImageViewUri.TAG:
- mActions.add(new SetImageViewUri(parcel));
- break;
- case SetImageViewBitmap.TAG:
- mActions.add(new SetImageViewBitmap(parcel));
- break;
- case SetChronometer.TAG:
- mActions.add(new SetChronometer(parcel));
- break;
- case SetProgressBar.TAG:
- mActions.add(new SetProgressBar(parcel));
- break;
case SetOnClickPendingIntent.TAG:
mActions.add(new SetOnClickPendingIntent(parcel));
break;
case SetDrawableParameters.TAG:
mActions.add(new SetDrawableParameters(parcel));
break;
- case SetTextColor.TAG:
- mActions.add(new SetTextColor(parcel));
- break;
- case SetFlipping.TAG:
- mActions.add(new SetFlipping(parcel));
+ case ReflectionAction.TAG:
+ mActions.add(new ReflectionAction(parcel));
break;
default:
- throw new ActionException("Tag " + tag + "not found");
+ throw new ActionException("Tag " + tag + " not found");
}
}
}
@@ -690,7 +529,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param visibility The new visibility for the view
*/
public void setViewVisibility(int viewId, int visibility) {
- addAction(new SetViewVisibility(viewId, visibility));
+ setInt(viewId, "setVisibility", visibility);
}
/**
@@ -700,7 +539,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param text The new text for the view
*/
public void setTextViewText(int viewId, CharSequence text) {
- addAction(new SetTextViewText(viewId, text));
+ setCharSequence(viewId, "setText", text);
}
/**
@@ -710,7 +549,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param srcId The new resource id for the drawable
*/
public void setImageViewResource(int viewId, int srcId) {
- addAction(new SetImageViewResource(viewId, srcId));
+ setInt(viewId, "setImageResource", srcId);
}
/**
@@ -720,7 +559,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param uri The Uri for the image
*/
public void setImageViewUri(int viewId, Uri uri) {
- addAction(new SetImageViewUri(viewId, uri));
+ setUri(viewId, "setImageURI", uri);
}
/**
@@ -730,7 +569,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param bitmap The new Bitmap for the drawable
*/
public void setImageViewBitmap(int viewId, Bitmap bitmap) {
- addAction(new SetImageViewBitmap(viewId, bitmap));
+ setBitmap(viewId, "setImageBitmap", bitmap);
}
/**
@@ -745,16 +584,20 @@ public class RemoteViews implements Parcelable, Filter {
* {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
* @param format The Chronometer format string, or null to
* simply display the timer value.
- * @param running True if you want the clock to be running, false if not.
+ * @param started True if you want the clock to be started, false if not.
*/
- public void setChronometer(int viewId, long base, String format, boolean running) {
- addAction(new SetChronometer(viewId, base, format, running));
+ public void setChronometer(int viewId, long base, String format, boolean started) {
+ setLong(viewId, "setBase", base);
+ setString(viewId, "setFormat", format);
+ setBoolean(viewId, "setStarted", started);
}
/**
* Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
* {@link ProgressBar#setProgress ProgressBar.setProgress}, and
* {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
+ *
+ * If indeterminate is true, then the values for max and progress are ignored.
*
* @param viewId The id of the view whose text should change
* @param max The 100% value for the progress bar
@@ -764,7 +607,11 @@ public class RemoteViews implements Parcelable, Filter {
*/
public void setProgressBar(int viewId, int max, int progress,
boolean indeterminate) {
- addAction(new SetProgressBar(viewId, max, progress, indeterminate));
+ setBoolean(viewId, "setIndeterminate", indeterminate);
+ if (!indeterminate) {
+ setInt(viewId, "setMax", max);
+ setInt(viewId, "setProgress", progress);
+ }
}
/**
@@ -780,6 +627,7 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * @hide
* Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
* {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
* and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
@@ -818,23 +666,55 @@ public class RemoteViews implements Parcelable, Filter {
* focused) to be this color.
*/
public void setTextColor(int viewId, int color) {
- addAction(new SetTextColor(viewId, color));
+ setInt(viewId, "setTextColor", color);
}
- /**
- * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()}
- * or {@link android.widget.ViewFlipper#stopFlipping()} along with
- * {@link android.widget.ViewFlipper#setFlipInterval(int)}.
- *
- * @param viewId The id of the view to apply changes to
- * @param flipping True means we should
- * {@link android.widget.ViewFlipper#startFlipping()}, otherwise
- * {@link android.widget.ViewFlipper#stopFlipping()}.
- * @param milliseconds How long to wait before flipping to the next view, or
- * -1 to leave unchanged.
- */
- public void setFlipping(int viewId, boolean flipping, int milliseconds) {
- addAction(new SetFlipping(viewId, flipping, milliseconds));
+ public void setBoolean(int viewId, String methodName, boolean value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
+ }
+
+ public void setByte(int viewId, String methodName, byte value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
+ }
+
+ public void setShort(int viewId, String methodName, short value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
+ }
+
+ public void setInt(int viewId, String methodName, int value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
+ }
+
+ public void setLong(int viewId, String methodName, long value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
+ }
+
+ public void setFloat(int viewId, String methodName, float value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
+ }
+
+ public void setDouble(int viewId, String methodName, double value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
+ }
+
+ public void setChar(int viewId, String methodName, char value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
+ }
+
+ public void setString(int viewId, String methodName, String value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
+ }
+
+ public void setCharSequence(int viewId, String methodName, CharSequence value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
+ }
+
+ public void setUri(int viewId, String methodName, Uri value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
+ }
+
+ public void setBitmap(int viewId, String methodName, Bitmap value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
}
/**
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index da4a077..dc2c70d 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -405,7 +405,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
* Specify a label and icon as the tab indicator.
*/
public TabSpec setIndicator(CharSequence label, Drawable icon) {
- mIndicatorStrategy = new LabelAndIconIndicatorStategy(label, icon);
+ mIndicatorStrategy = new LabelAndIconIndicatorStrategy(label, icon);
return this;
}
@@ -497,12 +497,12 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
/**
* How we create a tab indicator that has a label and an icon
*/
- private class LabelAndIconIndicatorStategy implements IndicatorStrategy {
+ private class LabelAndIconIndicatorStrategy implements IndicatorStrategy {
private final CharSequence mLabel;
private final Drawable mIcon;
- private LabelAndIconIndicatorStategy(CharSequence label, Drawable icon) {
+ private LabelAndIconIndicatorStrategy(CharSequence label, Drawable icon) {
mLabel = label;
mIcon = icon;
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 2ae5d4e..bdc54ff 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -87,6 +87,7 @@ import android.view.ViewDebug;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
+import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -1521,6 +1522,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textSize
*/
+ @android.view.RemotableViewMethod
public void setTextSize(float size) {
setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
}
@@ -1572,6 +1574,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textScaleX
*/
+ @android.view.RemotableViewMethod
public void setTextScaleX(float size) {
if (size != mTextPaint.getTextScaleX()) {
mTextPaint.setTextScaleX(size);
@@ -1620,6 +1623,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColor
*/
+ @android.view.RemotableViewMethod
public void setTextColor(int color) {
mTextColor = ColorStateList.valueOf(color);
updateTextColors();
@@ -1662,6 +1666,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorHighlight
*/
+ @android.view.RemotableViewMethod
public void setHighlightColor(int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
@@ -1703,6 +1708,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_autoLink
*/
+ @android.view.RemotableViewMethod
public final void setAutoLinkMask(int mask) {
mAutoLinkMask = mask;
}
@@ -1715,6 +1721,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_linksClickable
*/
+ @android.view.RemotableViewMethod
public final void setLinksClickable(boolean whether) {
mLinksClickable = whether;
}
@@ -1751,6 +1758,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorHint
*/
+ @android.view.RemotableViewMethod
public final void setHintTextColor(int color) {
mHintTextColor = ColorStateList.valueOf(color);
updateTextColors();
@@ -1789,6 +1797,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorLink
*/
+ @android.view.RemotableViewMethod
public final void setLinkTextColor(int color) {
mLinkTextColor = ColorStateList.valueOf(color);
updateTextColors();
@@ -1876,6 +1885,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* reflows the text if they are different from the old flags.
* @see Paint#setFlags
*/
+ @android.view.RemotableViewMethod
public void setPaintFlags(int flags) {
if (mTextPaint.getFlags() != flags) {
mTextPaint.setFlags(flags);
@@ -1909,6 +1919,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minLines
*/
+ @android.view.RemotableViewMethod
public void setMinLines(int minlines) {
mMinimum = minlines;
mMinMode = LINES;
@@ -1922,6 +1933,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minHeight
*/
+ @android.view.RemotableViewMethod
public void setMinHeight(int minHeight) {
mMinimum = minHeight;
mMinMode = PIXELS;
@@ -1935,6 +1947,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxLines
*/
+ @android.view.RemotableViewMethod
public void setMaxLines(int maxlines) {
mMaximum = maxlines;
mMaxMode = LINES;
@@ -1948,6 +1961,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxHeight
*/
+ @android.view.RemotableViewMethod
public void setMaxHeight(int maxHeight) {
mMaximum = maxHeight;
mMaxMode = PIXELS;
@@ -1961,6 +1975,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_lines
*/
+ @android.view.RemotableViewMethod
public void setLines(int lines) {
mMaximum = mMinimum = lines;
mMaxMode = mMinMode = LINES;
@@ -1976,6 +1991,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_height
*/
+ @android.view.RemotableViewMethod
public void setHeight(int pixels) {
mMaximum = mMinimum = pixels;
mMaxMode = mMinMode = PIXELS;
@@ -1989,6 +2005,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minEms
*/
+ @android.view.RemotableViewMethod
public void setMinEms(int minems) {
mMinWidth = minems;
mMinWidthMode = EMS;
@@ -2002,6 +2019,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_minWidth
*/
+ @android.view.RemotableViewMethod
public void setMinWidth(int minpixels) {
mMinWidth = minpixels;
mMinWidthMode = PIXELS;
@@ -2015,6 +2033,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxEms
*/
+ @android.view.RemotableViewMethod
public void setMaxEms(int maxems) {
mMaxWidth = maxems;
mMaxWidthMode = EMS;
@@ -2028,6 +2047,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_maxWidth
*/
+ @android.view.RemotableViewMethod
public void setMaxWidth(int maxpixels) {
mMaxWidth = maxpixels;
mMaxWidthMode = PIXELS;
@@ -2041,6 +2061,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_ems
*/
+ @android.view.RemotableViewMethod
public void setEms(int ems) {
mMaxWidth = mMinWidth = ems;
mMaxWidthMode = mMinWidthMode = EMS;
@@ -2056,6 +2077,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_width
*/
+ @android.view.RemotableViewMethod
public void setWidth(int pixels) {
mMaxWidth = mMinWidth = pixels;
mMaxWidthMode = mMinWidthMode = PIXELS;
@@ -2321,6 +2343,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_freezesText
*/
+ @android.view.RemotableViewMethod
public void setFreezesText(boolean freezesText) {
mFreezesText = freezesText;
}
@@ -2366,6 +2389,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_text
*/
+ @android.view.RemotableViewMethod
public final void setText(CharSequence text) {
setText(text, mBufferType);
}
@@ -2378,6 +2402,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @see #setText(CharSequence)
*/
+ @android.view.RemotableViewMethod
public final void setTextKeepState(CharSequence text) {
setTextKeepState(text, mBufferType);
}
@@ -2648,6 +2673,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ @android.view.RemotableViewMethod
public final void setText(int resid) {
setText(getContext().getResources().getText(resid));
}
@@ -2666,6 +2692,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_hint
*/
+ @android.view.RemotableViewMethod
public final void setHint(CharSequence hint) {
mHint = TextUtils.stringOrSpannedString(hint);
@@ -2686,6 +2713,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_hint
*/
+ @android.view.RemotableViewMethod
public final void setHint(int resid) {
setHint(getContext().getResources().getText(resid));
}
@@ -2896,6 +2924,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* <code>error</code> is <code>null</code>, the error message and icon
* will be cleared.
*/
+ @android.view.RemotableViewMethod
public void setError(CharSequence error) {
if (error == null) {
setError(null, null);
@@ -2954,10 +2983,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mPopup == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
- TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
+ final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint,
null);
- mPopup = new PopupWindow(err, 200, 50);
+ mPopup = new PopupWindow(err, 200, 50) {
+ private boolean mAbove = false;
+
+ @Override
+ public void update(int x, int y, int w, int h, boolean force) {
+ super.update(x, y, w, h, force);
+
+ boolean above = isAboveAnchor();
+ if (above != mAbove) {
+ mAbove = above;
+
+ if (above) {
+ err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above);
+ } else {
+ err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error);
+ }
+ }
+ }
+ };
mPopup.setFocusable(false);
}
@@ -5094,6 +5141,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_singleLine
*/
+ @android.view.RemotableViewMethod
public void setSingleLine(boolean singleLine) {
if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
== EditorInfo.TYPE_CLASS_TEXT) {
@@ -5168,6 +5216,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_selectAllOnFocus
*/
+ @android.view.RemotableViewMethod
public void setSelectAllOnFocus(boolean selectAllOnFocus) {
mSelectAllOnFocus = selectAllOnFocus;
@@ -5181,6 +5230,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_cursorVisible
*/
+ @android.view.RemotableViewMethod
public void setCursorVisible(boolean visible) {
mCursorVisible = visible;
invalidate();
@@ -5730,6 +5780,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
startStopMarquee(hasWindowFocus);
}
+ /**
+ * Use {@link BaseInputConnection#removeComposingSpans
+ * BaseInputConnection.removeComposingSpans()} to remove any IME composing
+ * state from this text view.
+ */
+ public void clearComposingText() {
+ if (mText instanceof Spannable) {
+ BaseInputConnection.removeComposingSpans((Spannable)mText);
+ }
+ }
+
@Override
public void setSelected(boolean selected) {
boolean wasSelected = isSelected();
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index e20bfdf..8a7946b 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -31,7 +31,6 @@ import android.widget.RemoteViews.RemoteView;
*
* @attr ref android.R.styleable#ViewFlipper_flipInterval
*/
-@RemoteView
public class ViewFlipper extends ViewAnimator {
private int mFlipInterval = 3000;
private boolean mKeepFlipping = false;
@@ -56,6 +55,7 @@ public class ViewFlipper extends ViewAnimator {
* @param milliseconds
* time in milliseconds
*/
+ @android.view.RemotableViewMethod
public void setFlipInterval(int milliseconds) {
mFlipInterval = milliseconds;
}
diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java
index be3b1fb..22881b3 100644
--- a/core/java/android/widget/ZoomRing.java
+++ b/core/java/android/widget/ZoomRing.java
@@ -7,7 +7,11 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RotateDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
@@ -19,10 +23,8 @@ import android.view.ViewConfiguration;
public class ZoomRing extends View {
// TODO: move to ViewConfiguration?
- static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getJumpTapTimeout();
+ static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
// TODO: get from theme
- private static final int DISABLED_ALPHA = 160;
-
private static final String TAG = "ZoomRing";
// TODO: Temporary until the trail is done
@@ -32,15 +34,26 @@ public class ZoomRing extends View {
private static final int THUMB_DISTANCE = 63;
/** To avoid floating point calculations, we multiply radians by this value. */
- public static final int RADIAN_INT_MULTIPLIER = 100000000;
+ public static final int RADIAN_INT_MULTIPLIER = 10000;
+ public static final int RADIAN_INT_ERROR = 100;
/** PI using our multiplier. */
public static final int PI_INT_MULTIPLIED = (int) (Math.PI * RADIAN_INT_MULTIPLIER);
+ public static final int TWO_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED * 2;
/** PI/2 using our multiplier. */
private static final int HALF_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED / 2;
private int mZeroAngle = HALF_PI_INT_MULTIPLIED * 3;
+
+ private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 8;
+ private static final int THUMB_DRAG_SLOP = PI_INT_MULTIPLIED / 12;
- private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 4;
+ /**
+ * Includes error because we compare this to the result of
+ * getDelta(getClosestTickeAngle(..), oldAngle) which ends up having some
+ * rounding error.
+ */
+ private static final int MAX_ABS_JUMP_DELTA_ANGLE = (2 * PI_INT_MULTIPLIED / 3) +
+ RADIAN_INT_ERROR;
/** The cached X of our center. */
private int mCenterX;
@@ -49,10 +62,13 @@ public class ZoomRing extends View {
/** The angle of the thumb (in int radians) */
private int mThumbAngle;
- private boolean mIsThumbAngleValid;
private int mThumbHalfWidth;
private int mThumbHalfHeight;
-
+
+ private int mThumbCwBound = Integer.MIN_VALUE;
+ private int mThumbCcwBound = Integer.MIN_VALUE;
+ private boolean mEnforceMaxAbsJump = true;
+
/** The inner radius of the track. */
private int mBoundInnerRadiusSquared = 0;
/** The outer radius of the track. */
@@ -63,8 +79,23 @@ public class ZoomRing extends View {
private boolean mDrawThumb = true;
private Drawable mThumbDrawable;
-
+
+ /** Shown beneath the thumb if we can still zoom in. */
+ private Drawable mThumbPlusArrowDrawable;
+ /** Shown beneath the thumb if we can still zoom out. */
+ private Drawable mThumbMinusArrowDrawable;
+ private static final int THUMB_ARROWS_FADE_DURATION = 300;
+ private long mThumbArrowsFadeStartTime;
+ private int mThumbArrowsAlpha = 255;
+
private static final int MODE_IDLE = 0;
+
+ /**
+ * User has his finger down somewhere on the ring (besides the thumb) and we
+ * are waiting for him to move the slop amount before considering him in the
+ * drag thumb state.
+ */
+ private static final int MODE_WAITING_FOR_DRAG_THUMB = 5;
private static final int MODE_DRAG_THUMB = 1;
/**
* User has his finger down, but we are waiting for him to pass the touch
@@ -74,24 +105,47 @@ public class ZoomRing extends View {
private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4;
private static final int MODE_MOVE_ZOOM_RING = 2;
private static final int MODE_TAP_DRAG = 3;
+ /** Ignore the touch interaction. Reset to MODE_IDLE after up/cancel. */
+ private static final int MODE_IGNORE_UNTIL_UP = 6;
private int mMode;
- private long mPreviousDownTime;
+ private long mPreviousUpTime;
private int mPreviousDownX;
private int mPreviousDownY;
- private Disabler mDisabler = new Disabler();
-
+ private int mWaitingForDragThumbDownAngle;
+
private OnZoomRingCallback mCallback;
private int mPreviousCallbackAngle;
private int mCallbackThreshold = Integer.MAX_VALUE;
private boolean mResetThumbAutomatically = true;
private int mThumbDragStartAngle;
+
private final int mTouchSlop;
+
private Drawable mTrail;
private double mAcculumalatedTrailAngle;
-
+
+ private Scroller mThumbScroller;
+
+ private static final int MSG_THUMB_SCROLLER_TICK = 1;
+ private static final int MSG_THUMB_ARROWS_FADE_TICK = 2;
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_THUMB_SCROLLER_TICK:
+ onThumbScrollerTick();
+ break;
+
+ case MSG_THUMB_ARROWS_FADE_TICK:
+ onThumbArrowsFadeTick();
+ break;
+ }
+ }
+ };
+
public ZoomRing(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
@@ -101,6 +155,10 @@ public class ZoomRing extends View {
// TODO get drawables from style instead
Resources res = context.getResources();
mThumbDrawable = res.getDrawable(R.drawable.zoom_ring_thumb);
+ mThumbPlusArrowDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_plus_arrow_rotatable).
+ mutate();
+ mThumbMinusArrowDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus_arrow_rotatable).
+ mutate();
if (DRAW_TRAIL) {
mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate();
}
@@ -108,7 +166,7 @@ public class ZoomRing extends View {
// TODO: add padding to drawable
setBackgroundResource(R.drawable.zoom_ring_track);
// TODO get from style
- setBounds(30, Integer.MAX_VALUE);
+ setRingBounds(30, Integer.MAX_VALUE);
mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2;
mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2;
@@ -134,7 +192,7 @@ public class ZoomRing extends View {
}
// TODO: from XML too
- public void setBounds(int innerRadius, int outerRadius) {
+ public void setRingBounds(int innerRadius, int outerRadius) {
mBoundInnerRadiusSquared = innerRadius * innerRadius;
if (mBoundInnerRadiusSquared < innerRadius) {
// Prevent overflow
@@ -148,7 +206,64 @@ public class ZoomRing extends View {
}
}
+ public void setThumbClockwiseBound(int angle) {
+ if (angle < 0) {
+ mThumbCwBound = Integer.MIN_VALUE;
+ } else {
+ mThumbCwBound = getClosestTickAngle(angle);
+ }
+ setEnforceMaxAbsJump();
+ }
+
+ public void setThumbCounterclockwiseBound(int angle) {
+ if (angle < 0) {
+ mThumbCcwBound = Integer.MIN_VALUE;
+ } else {
+ mThumbCcwBound = getClosestTickAngle(angle);
+ }
+ setEnforceMaxAbsJump();
+ }
+
+ private void setEnforceMaxAbsJump() {
+ // If there are bounds in both direction, there is no reason to restrict
+ // the amount that a user can absolute jump to
+ mEnforceMaxAbsJump =
+ mThumbCcwBound == Integer.MIN_VALUE || mThumbCwBound == Integer.MIN_VALUE;
+ }
+
+ public int getThumbAngle() {
+ return mThumbAngle;
+ }
+
public void setThumbAngle(int angle) {
+ angle = getValidAngle(angle);
+ mPreviousCallbackAngle = getClosestTickAngle(angle);
+ setThumbAngleAuto(angle, false, false);
+ }
+
+ /**
+ * Sets the thumb angle. If already animating, will continue the animation,
+ * otherwise it will do a direct jump.
+ *
+ * @param angle
+ * @param useDirection Whether to use the ccw parameter
+ * @param ccw Whether going counterclockwise (only used if useDirection is true)
+ */
+ private void setThumbAngleAuto(int angle, boolean useDirection, boolean ccw) {
+ if (mThumbScroller == null
+ || mThumbScroller.isFinished()
+ || Math.abs(getDelta(angle, getThumbScrollerAngle())) < THUMB_GRAB_SLOP) {
+ setThumbAngleInt(angle);
+ } else {
+ if (useDirection) {
+ setThumbAngleAnimated(angle, 0, ccw);
+ } else {
+ setThumbAngleAnimated(angle, 0);
+ }
+ }
+ }
+
+ private void setThumbAngleInt(int angle) {
mThumbAngle = angle;
int unoffsetAngle = angle + mZeroAngle;
int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) *
@@ -161,6 +276,10 @@ public class ZoomRing extends View {
thumbCenterX + mThumbHalfWidth,
thumbCenterY + mThumbHalfHeight);
+ if (mThumbArrowsAlpha > 0) {
+ setThumbArrowsAngle(angle);
+ }
+
if (DRAW_TRAIL) {
double degrees;
degrees = Math.min(359.0, Math.abs(mAcculumalatedTrailAngle));
@@ -174,10 +293,66 @@ public class ZoomRing extends View {
invalidate();
}
+
+ /**
+ *
+ * @param angle
+ * @param duration The animation duration, or 0 for the default duration.
+ */
+ public void setThumbAngleAnimated(int angle, int duration) {
+ // The angle when going from the current angle to the new angle
+ int deltaAngle = getDelta(mThumbAngle, angle);
+ // Counter clockwise if the new angle is more the current angle
+ boolean counterClockwise = deltaAngle > 0;
+
+ if (deltaAngle > PI_INT_MULTIPLIED || deltaAngle < -PI_INT_MULTIPLIED) {
+ // It's quicker to go the other direction
+ counterClockwise = !counterClockwise;
+ }
+
+ setThumbAngleAnimated(angle, duration, counterClockwise);
+ }
+
+ public void setThumbAngleAnimated(int angle, int duration, boolean counterClockwise) {
+ if (mThumbScroller == null) {
+ mThumbScroller = new Scroller(mContext);
+ }
+
+ int startAngle = mThumbAngle;
+ int endAngle = getValidAngle(angle);
+ int deltaAngle = getDelta(startAngle, endAngle, counterClockwise);
+ if (startAngle + deltaAngle < 0) {
+ // Keep our angles positive
+ startAngle += TWO_PI_INT_MULTIPLIED;
+ }
+
+ if (!mThumbScroller.isFinished()) {
+ duration = mThumbScroller.getDuration() - mThumbScroller.timePassed();
+ } else if (duration == 0) {
+ duration = getAnimationDuration(deltaAngle);
+ }
+ mThumbScroller.startScroll(startAngle, 0, deltaAngle, 0, duration);
+ onThumbScrollerTick();
+ }
+
+ private int getAnimationDuration(int deltaAngle) {
+ if (deltaAngle < 0) deltaAngle *= -1;
+ return 300 + deltaAngle * 300 / RADIAN_INT_MULTIPLIER;
+ }
+
+ private void onThumbScrollerTick() {
+ if (!mThumbScroller.computeScrollOffset()) return;
+ setThumbAngleInt(getThumbScrollerAngle());
+ mHandler.sendEmptyMessage(MSG_THUMB_SCROLLER_TICK);
+ }
+ private int getThumbScrollerAngle() {
+ return mThumbScroller.getCurrX() % TWO_PI_INT_MULTIPLIED;
+ }
+
public void resetThumbAngle(int angle) {
mPreviousCallbackAngle = angle;
- setThumbAngle(angle);
+ setThumbAngleInt(angle);
}
public void resetThumbAngle() {
@@ -185,7 +360,7 @@ public class ZoomRing extends View {
resetThumbAngle(0);
}
}
-
+
public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
mResetThumbAutomatically = resetThumbAutomatically;
}
@@ -214,6 +389,9 @@ public class ZoomRing extends View {
if (DRAW_TRAIL) {
mTrail.setBounds(0, 0, right - left, bottom - top);
}
+
+ mThumbPlusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
+ mThumbMinusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
}
@Override
@@ -227,15 +405,13 @@ public class ZoomRing extends View {
mMode = MODE_IDLE;
mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE;
mAcculumalatedTrailAngle = 0.0;
- mIsThumbAngleValid = false;
}
public void setTapDragMode(boolean tapDragMode, int x, int y) {
resetState();
mMode = tapDragMode ? MODE_TAP_DRAG : MODE_IDLE;
- mIsThumbAngleValid = false;
- if (tapDragMode && mCallback != null) {
+ if (tapDragMode) {
onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY));
}
}
@@ -244,35 +420,44 @@ public class ZoomRing extends View {
switch (action) {
case MotionEvent.ACTION_DOWN:
- if (mPreviousDownTime + DOUBLE_TAP_DISMISS_TIMEOUT >= time) {
- if (mCallback != null) {
- mCallback.onZoomRingDismissed();
- }
- } else {
- mPreviousDownTime = time;
- mPreviousDownX = x;
- mPreviousDownY = y;
+ mCallback.onUserInteractionStarted();
+
+ if (time - mPreviousUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT) {
+ mCallback.onZoomRingDismissed(true);
}
+
+ mPreviousDownX = x;
+ mPreviousDownY = y;
resetState();
- return true;
+ // Fall through to code below switch (since the down is used for
+ // jumping to the touched tick)
+ break;
case MotionEvent.ACTION_MOVE:
+ if (mMode == MODE_IGNORE_UNTIL_UP) return true;
+
// Fall through to code below switch
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
- if (mCallback != null) {
- if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
- mCallback.onZoomRingSetMovableHintVisible(false);
- if (mMode == MODE_MOVE_ZOOM_RING) {
- mCallback.onZoomRingMovingStopped();
- }
- } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) {
- onThumbDragStopped(getAngle(x - mCenterX, y - mCenterY));
+ if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
+ mCallback.onZoomRingSetMovableHintVisible(false);
+ if (mMode == MODE_MOVE_ZOOM_RING) {
+ mCallback.onZoomRingMovingStopped();
+ }
+ } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG ||
+ mMode == MODE_WAITING_FOR_DRAG_THUMB) {
+ onThumbDragStopped();
+
+ if (mMode == MODE_DRAG_THUMB) {
+ // Animate back to a tick
+ setThumbAngleAnimated(mPreviousCallbackAngle, 0);
}
}
- mDisabler.setEnabling(true);
+
+ mPreviousUpTime = time;
+ mCallback.onUserInteractionStopped();
return true;
default:
@@ -283,18 +468,18 @@ public class ZoomRing extends View {
int localX = x - mCenterX;
int localY = y - mCenterY;
boolean isTouchingThumb = true;
- boolean isInBounds = true;
+ boolean isInRingBounds = true;
+
int touchAngle = getAngle(localX, localY);
-
int radiusSquared = localX * localX + localY * localY;
if (radiusSquared < mBoundInnerRadiusSquared ||
radiusSquared > mBoundOuterRadiusSquared) {
// Out-of-bounds
isTouchingThumb = false;
- isInBounds = false;
+ isInRingBounds = false;
}
- int deltaThumbAndTouch = getDelta(touchAngle, mThumbAngle);
+ int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle);
int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ?
deltaThumbAndTouch : -deltaThumbAndTouch;
if (isTouchingThumb &&
@@ -305,17 +490,68 @@ public class ZoomRing extends View {
if (mMode == MODE_IDLE) {
if (isTouchingThumb) {
+ // They grabbed the thumb
mMode = MODE_DRAG_THUMB;
+ onThumbDragStarted(touchAngle);
+
+ } else if (isInRingBounds) {
+ // They tapped somewhere else on the ring
+ int tickAngle = getClosestTickAngle(touchAngle);
+
+ int deltaThumbAndTick = getDelta(mThumbAngle, tickAngle);
+ int boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
+
+ if (mEnforceMaxAbsJump) {
+ // Enforcing the max jump
+ if (deltaThumbAndTick > MAX_ABS_JUMP_DELTA_ANGLE ||
+ deltaThumbAndTick < -MAX_ABS_JUMP_DELTA_ANGLE) {
+ // Trying to jump too far, ignore this touch interaction
+ mMode = MODE_IGNORE_UNTIL_UP;
+ return true;
+ }
+
+ // Make sure we only let them jump within bounds
+ if (boundAngle != Integer.MIN_VALUE) {
+ tickAngle = boundAngle;
+ }
+ } else {
+ // Not enforcing the max jump, but we have to make sure
+ // we're getting to the tapped angle by going through the
+ // in-bounds region
+ if (boundAngle != Integer.MIN_VALUE) {
+ // Going this direction hits a bound, let's go the opposite direction
+ boolean oldDirectionIsCcw = deltaThumbAndTick > 0;
+ deltaThumbAndTick = getDelta(mThumbAngle, tickAngle, !oldDirectionIsCcw);
+ boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick);
+ if (boundAngle != Integer.MIN_VALUE) {
+ Log
+ .d(
+ TAG,
+ "Tapped somewhere where the shortest distance goes through a bound, but then the opposite direction also went through a bound!");
+ }
+ }
+ }
+
+ mMode = MODE_WAITING_FOR_DRAG_THUMB;
+ mWaitingForDragThumbDownAngle = touchAngle;
+ boolean ccw = deltaThumbAndTick > 0;
+ setThumbAngleAnimated(tickAngle, 0, ccw);
+
+ // Our thumb scrolling animation takes us from mThumbAngle to tickAngle
+ onThumbDragStarted(mThumbAngle);
+ onThumbDragged(tickAngle, true, ccw);
+
} else {
+ // They tapped somewhere else
mMode = MODE_WAITING_FOR_MOVE_ZOOM_RING;
+ mCallback.onZoomRingSetMovableHintVisible(true);
}
- if (mCallback != null) {
- if (mMode == MODE_DRAG_THUMB) {
- onThumbDragStarted(touchAngle);
- } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
- mCallback.onZoomRingSetMovableHintVisible(true);
- }
+ } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB) {
+ int deltaDownAngle = getDelta(mWaitingForDragThumbDownAngle, touchAngle);
+ if ((deltaDownAngle < -THUMB_DRAG_SLOP || deltaDownAngle > THUMB_DRAG_SLOP) &&
+ isDeltaInBounds(mWaitingForDragThumbDownAngle, deltaDownAngle)) {
+ mMode = MODE_DRAG_THUMB;
}
} else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) {
@@ -323,19 +559,14 @@ public class ZoomRing extends View {
Math.abs(y - mPreviousDownY) > mTouchSlop) {
/* Make sure the user has moved the slop amount before going into that mode. */
mMode = MODE_MOVE_ZOOM_RING;
-
- if (mCallback != null) {
- mCallback.onZoomRingMovingStarted();
- }
+ mCallback.onZoomRingMovingStarted();
}
}
// Purposefully not an "else if"
if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) {
- if (isInBounds) {
- onThumbDragged(touchAngle, mIsThumbAngleValid ? deltaThumbAndTouch : 0);
- } else {
- mIsThumbAngleValid = false;
+ if (isInRingBounds) {
+ onThumbDragged(touchAngle, false, false);
}
} else if (mMode == MODE_MOVE_ZOOM_RING) {
onZoomRingMoved(rawX, rawY);
@@ -344,55 +575,219 @@ public class ZoomRing extends View {
return true;
}
- private int getDelta(int angle1, int angle2) {
- int delta = angle1 - angle2;
-
- // Assume this is a result of crossing over the discontinuous 0 -> 2pi
- if (delta > PI_INT_MULTIPLIED || delta < -PI_INT_MULTIPLIED) {
- // Bring both the radians and previous angle onto a continuous range
- if (angle1 < HALF_PI_INT_MULTIPLIED) {
- // Same as deltaRadians = (radians + 2PI) - previousAngle
- delta += PI_INT_MULTIPLIED * 2;
- } else if (angle2 < HALF_PI_INT_MULTIPLIED) {
- // Same as deltaRadians = radians - (previousAngle + 2PI)
- delta -= PI_INT_MULTIPLIED * 2;
+ private boolean isDeltaInBounds(int startAngle, int deltaAngle) {
+ return getBoundIfExceeds(startAngle, deltaAngle) == Integer.MIN_VALUE;
+ }
+
+ private int getBoundIfExceeds(int startAngle, int deltaAngle) {
+ if (deltaAngle > 0) {
+ // Counterclockwise movement
+ if (mThumbCcwBound != Integer.MIN_VALUE &&
+ getDelta(startAngle, mThumbCcwBound, true) < deltaAngle) {
+ return mThumbCcwBound;
+ }
+ } else if (deltaAngle < 0) {
+ // Clockwise movement, both of these will be negative
+ int deltaThumbAndBound = getDelta(startAngle, mThumbCwBound, false);
+ if (mThumbCwBound != Integer.MIN_VALUE &&
+ deltaThumbAndBound > deltaAngle) {
+ // Tapped outside of the bound in that direction
+ return mThumbCwBound;
}
}
+
+ return Integer.MIN_VALUE;
+ }
+
+ private int getDelta(int startAngle, int endAngle, boolean useDirection, boolean ccw) {
+ return useDirection ? getDelta(startAngle, endAngle, ccw) : getDelta(startAngle, endAngle);
+ }
+
+ /**
+ * Gets the smallest delta between two angles, and infers the direction
+ * based on the shortest path between the two angles. If going from
+ * startAngle to endAngle is counterclockwise, the result will be positive.
+ * If it is clockwise, the result will be negative.
+ *
+ * @param startAngle The start angle.
+ * @param endAngle The end angle.
+ * @return The difference in angles.
+ */
+ private int getDelta(int startAngle, int endAngle) {
+ int largerAngle, smallerAngle;
+ if (endAngle > startAngle) {
+ largerAngle = endAngle;
+ smallerAngle = startAngle;
+ } else {
+ largerAngle = startAngle;
+ smallerAngle = endAngle;
+ }
- return delta;
+ int delta = largerAngle - smallerAngle;
+ if (delta <= PI_INT_MULTIPLIED) {
+ // If going clockwise, negate the delta
+ return startAngle == largerAngle ? -delta : delta;
+ } else {
+ // The other direction is the delta we want (it includes the
+ // discontinuous 0-2PI angle)
+ delta = TWO_PI_INT_MULTIPLIED - delta;
+ // If going clockwise, negate the delta
+ return startAngle == smallerAngle ? -delta : delta;
+ }
}
+ /**
+ * Gets the delta between two angles in the direction specified.
+ *
+ * @param startAngle The start angle.
+ * @param endAngle The end angle.
+ * @param counterClockwise The direction to take when computing the delta.
+ * @return The difference in angles in the given direction.
+ */
+ private int getDelta(int startAngle, int endAngle, boolean counterClockwise) {
+ int delta = endAngle - startAngle;
+
+ if (!counterClockwise && delta > 0) {
+ // Crossed the discontinuous 0/2PI angle, take the leftover slice of
+ // the pie and negate it
+ return -TWO_PI_INT_MULTIPLIED + delta;
+ } else if (counterClockwise && delta < 0) {
+ // Crossed the discontinuous 0/2PI angle, take the leftover slice of
+ // the pie (and ensure it is positive)
+ return TWO_PI_INT_MULTIPLIED + delta;
+ } else {
+ return delta;
+ }
+ }
+
private void onThumbDragStarted(int startAngle) {
+ setThumbArrowsVisible(false);
mThumbDragStartAngle = startAngle;
- mCallback.onZoomRingThumbDraggingStarted(startAngle);
+ mCallback.onZoomRingThumbDraggingStarted();
}
-
- private void onThumbDragged(int touchAngle, int deltaAngle) {
- mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER);
- int totalDeltaAngle = getDelta(touchAngle, mPreviousCallbackAngle);
- if (totalDeltaAngle > mCallbackThreshold
- || totalDeltaAngle < -mCallbackThreshold) {
- if (mCallback != null) {
- boolean canStillZoom = mCallback.onZoomRingThumbDragged(
- totalDeltaAngle / mCallbackThreshold,
- mThumbDragStartAngle, touchAngle);
- mDisabler.setEnabling(canStillZoom);
-
- if (canStillZoom) {
- // TODO: we're trying the haptics to see how it goes with
- // users, so we're ignoring the settings (for now)
- performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
- HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+
+ private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) {
+ boolean animateThumbToNewAngle = false;
+
+ int totalDeltaAngle;
+ totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+ int fuzzyCallbackThreshold = (int) (mCallbackThreshold * 0.65f);
+ if (totalDeltaAngle >= fuzzyCallbackThreshold
+ || totalDeltaAngle <= -fuzzyCallbackThreshold) {
+
+ if (!useDirection) {
+ // Set ccw to match the direction found by getDelta
+ ccw = totalDeltaAngle > 0;
+ }
+
+ /*
+ * When the user slides the thumb through the tick that corresponds
+ * to a zoom bound, we don't want to abruptly stop there. Instead,
+ * let the user slide it to the next tick, and then animate it back
+ * to the original zoom bound tick. Because of this, we make sure
+ * the delta from the bound is more than halfway to the next tick.
+ * We make sure the bound is between the touch and the previous
+ * callback to ensure we just passed the bound.
+ */
+ int oldTouchAngle = touchAngle;
+ if (ccw && mThumbCcwBound != Integer.MIN_VALUE) {
+ int deltaCcwBoundAndTouch =
+ getDelta(mThumbCcwBound, touchAngle, useDirection, true);
+ if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) {
+ // The touch has past a bound
+ int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+ touchAngle, useDirection, true);
+ if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) {
+ // The bound is between the previous callback angle and the touch
+ touchAngle = mThumbCcwBound;
+ // We're moving the touch BACK to the bound, so opposite direction
+ ccw = false;
+ }
+ }
+ } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) {
+ // See block above for general comments
+ int deltaCwBoundAndTouch =
+ getDelta(mThumbCwBound, touchAngle, useDirection, false);
+ if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) {
+ int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+ touchAngle, useDirection, false);
+ /*
+ * Both of these will be negative since we got delta in
+ * clockwise direction, and we want the magnitude of
+ * deltaPreviousCbAndTouch to be greater than the magnitude
+ * of deltaCwBoundAndTouch
+ */
+ if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) {
+ touchAngle = mThumbCwBound;
+ ccw = true;
+ }
+ }
+ }
+ if (touchAngle != oldTouchAngle) {
+ // We bounded the touch angle
+ totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+ animateThumbToNewAngle = true;
+ mMode = MODE_IGNORE_UNTIL_UP;
+ }
+
+
+ // Prevent it from jumping too far
+ if (mEnforceMaxAbsJump) {
+ if (totalDeltaAngle <= -MAX_ABS_JUMP_DELTA_ANGLE) {
+ totalDeltaAngle = -MAX_ABS_JUMP_DELTA_ANGLE;
+ animateThumbToNewAngle = true;
+ } else if (totalDeltaAngle >= MAX_ABS_JUMP_DELTA_ANGLE) {
+ totalDeltaAngle = MAX_ABS_JUMP_DELTA_ANGLE;
+ animateThumbToNewAngle = true;
}
}
- // Get the closest tick and lock on there
- mPreviousCallbackAngle = getClosestTickAngle(touchAngle);
+ /*
+ * We need to cover the edge case of a user grabbing the thumb,
+ * going into the center of the widget, and then coming out from the
+ * center to an angle that's slightly below the angle he's trying to
+ * hit. If we do int division, we'll end up with one level lower
+ * than the one he was going for.
+ */
+ int deltaLevels = Math.round((float) totalDeltaAngle / mCallbackThreshold);
+ if (deltaLevels != 0) {
+ boolean canStillZoom = mCallback.onZoomRingThumbDragged(
+ deltaLevels, mThumbDragStartAngle, touchAngle);
+
+ // TODO: we're trying the haptics to see how it goes with
+ // users, so we're ignoring the settings (for now)
+ performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING |
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+
+ // Set the callback angle to the actual angle based on how many delta levels we gave
+ mPreviousCallbackAngle = getValidAngle(
+ mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold));
+ }
}
- setThumbAngle(touchAngle);
- mIsThumbAngleValid = true;
+ int deltaAngle = getDelta(mThumbAngle, touchAngle, useDirection, ccw);
+ mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER);
+
+ if (animateThumbToNewAngle) {
+ if (useDirection) {
+ setThumbAngleAnimated(touchAngle, 0, ccw);
+ } else {
+ setThumbAngleAnimated(touchAngle, 0);
+ }
+ } else {
+ setThumbAngleAuto(touchAngle, useDirection, ccw);
+ }
+ }
+
+ private int getValidAngle(int invalidAngle) {
+ if (invalidAngle < 0) {
+ return (invalidAngle % TWO_PI_INT_MULTIPLIED) + TWO_PI_INT_MULTIPLIED;
+ } else if (invalidAngle >= TWO_PI_INT_MULTIPLIED) {
+ return invalidAngle % TWO_PI_INT_MULTIPLIED;
+ } else {
+ return invalidAngle;
+ }
}
private int getClosestTickAngle(int angle) {
@@ -403,12 +798,12 @@ public class ZoomRing extends View {
return smallerAngle;
} else {
// Closer to the bigger angle (premodding)
- return (smallerAngle + mCallbackThreshold) % (PI_INT_MULTIPLIED * 2);
+ return (smallerAngle + mCallbackThreshold) % TWO_PI_INT_MULTIPLIED;
}
}
- private void onThumbDragStopped(int stopAngle) {
- mCallback.onZoomRingThumbDraggingStopped(stopAngle);
+ private void onThumbDragStopped() {
+ mCallback.onZoomRingThumbDraggingStopped();
}
private void onZoomRingMoved(int x, int y) {
@@ -416,9 +811,7 @@ public class ZoomRing extends View {
int deltaX = x - mPreviousWidgetDragX;
int deltaY = y - mPreviousWidgetDragY;
- if (mCallback != null) {
- mCallback.onZoomRingMoved(deltaX, deltaY);
- }
+ mCallback.onZoomRingMoved(deltaX, deltaY);
}
mPreviousWidgetDragX = x;
@@ -429,11 +822,11 @@ public class ZoomRing extends View {
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus && mCallback != null) {
- mCallback.onZoomRingDismissed();
+ if (!hasWindowFocus) {
+ mCallback.onZoomRingDismissed(true);
}
}
-
+
private int getAngle(int localX, int localY) {
int radians = (int) (Math.atan2(localY, localX) * RADIAN_INT_MULTIPLIER);
@@ -458,45 +851,65 @@ public class ZoomRing extends View {
if (DRAW_TRAIL) {
mTrail.draw(canvas);
}
+
+ // If we aren't near the bounds, draw the corresponding arrows
+ int callbackAngle = mPreviousCallbackAngle;
+ if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR ||
+ callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) {
+ mThumbPlusArrowDrawable.draw(canvas);
+ }
+ if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR ||
+ callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) {
+ mThumbMinusArrowDrawable.draw(canvas);
+ }
mThumbDrawable.draw(canvas);
}
}
-
- private class Disabler implements Runnable {
- private static final int DELAY = 15;
- private static final float ENABLE_RATE = 1.05f;
- private static final float DISABLE_RATE = 0.95f;
-
- private int mAlpha = 255;
- private boolean mEnabling;
-
- public int getAlpha() {
- return mAlpha;
- }
-
- public void setEnabling(boolean enabling) {
- if ((enabling && mAlpha != 255) || (!enabling && mAlpha != DISABLED_ALPHA)) {
- mEnabling = enabling;
- post(this);
- }
+
+ private void setThumbArrowsAngle(int angle) {
+ int level = -angle * 10000 / ZoomRing.TWO_PI_INT_MULTIPLIED;
+ mThumbPlusArrowDrawable.setLevel(level);
+ mThumbMinusArrowDrawable.setLevel(level);
+ }
+
+ public void setThumbArrowsVisible(boolean visible) {
+ if (visible) {
+ mThumbArrowsAlpha = 255;
+ mThumbPlusArrowDrawable.setAlpha(255);
+ mThumbMinusArrowDrawable.setAlpha(255);
+ invalidate();
+ } else if (mThumbArrowsAlpha == 255) {
+ // Only start fade if we're fully visible (otherwise another fade is happening already)
+ mThumbArrowsFadeStartTime = SystemClock.elapsedRealtime();
+ onThumbArrowsFadeTick();
}
+ }
+
+ private void onThumbArrowsFadeTick() {
+ if (mThumbArrowsAlpha <= 0) return;
- public void run() {
- mAlpha *= mEnabling ? ENABLE_RATE : DISABLE_RATE;
- if (mAlpha < DISABLED_ALPHA) {
- mAlpha = DISABLED_ALPHA;
- } else if (mAlpha > 255) {
- mAlpha = 255;
- } else {
- // Still more to go
- postDelayed(this, DELAY);
- }
-
- getBackground().setAlpha(mAlpha);
- invalidate();
+ mThumbArrowsAlpha = (int)
+ (255 - (255 * (SystemClock.elapsedRealtime() - mThumbArrowsFadeStartTime)
+ / THUMB_ARROWS_FADE_DURATION));
+ if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0;
+ mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mThumbPlusArrowDrawable);
+ invalidateDrawable(mThumbMinusArrowDrawable);
+
+ if (!mHandler.hasMessages(MSG_THUMB_ARROWS_FADE_TICK)) {
+ mHandler.sendEmptyMessage(MSG_THUMB_ARROWS_FADE_TICK);
}
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ setThumbArrowsAngle(mThumbAngle);
+ setThumbArrowsVisible(true);
+ }
+
public interface OnZoomRingCallback {
void onZoomRingSetMovableHintVisible(boolean visible);
@@ -504,11 +917,17 @@ public class ZoomRing extends View {
boolean onZoomRingMoved(int deltaX, int deltaY);
void onZoomRingMovingStopped();
- void onZoomRingThumbDraggingStarted(int startAngle);
+ void onZoomRingThumbDraggingStarted();
boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle);
- void onZoomRingThumbDraggingStopped(int endAngle);
+ void onZoomRingThumbDraggingStopped();
- void onZoomRingDismissed();
+ void onZoomRingDismissed(boolean dismissImmediately);
+
+ void onUserInteractionStarted();
+ void onUserInteractionStopped();
}
+ private static void printAngle(String angleName, int angle) {
+ Log.d(TAG, angleName + ": " + (long) angle * 180 / PI_INT_MULTIPLIED);
+ }
}
diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java
index eb28767..31074b6 100644
--- a/core/java/android/widget/ZoomRingController.java
+++ b/core/java/android/widget/ZoomRingController.java
@@ -16,6 +16,8 @@
package android.widget;
+import android.app.AlertDialog;
+import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -27,7 +29,6 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
@@ -35,6 +36,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
@@ -46,12 +48,16 @@ import android.view.animation.DecelerateInterpolator;
/**
* TODO: Docs
*
+ * If you are using this with a custom View, please call
+ * {@link #setVisible(boolean) setVisible(false)} from the
+ * {@link View#onDetachedFromWindow}.
+ *
* @hide
*/
public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
View.OnTouchListener, View.OnKeyListener {
- private static final int SHOW_TUTORIAL_TOAST_DELAY = 1000;
+ private static final int ZOOM_RING_RADIUS_INSET = 10;
private static final int ZOOM_RING_RECENTERING_DURATION = 500;
@@ -69,9 +75,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// TODO: scale px values based on latest from ViewConfiguration
private static final int SECOND_TAP_TIMEOUT = 500;
private static final int ZOOM_RING_DISMISS_DELAY = SECOND_TAP_TIMEOUT / 2;
- private static final int SECOND_TAP_SLOP = 70;
- private static final int SECOND_TAP_MOVE_SLOP = 15;
- private static final int MAX_PAN_GAP = 30;
+ // TODO: view config? at least scaled
+ private static final int MAX_PAN_GAP = 20;
+ private static final int MAX_INITIATE_PAN_GAP = 10;
+ // TODO view config
+ private static final int INITIATE_PAN_DELAY = 400;
private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast";
@@ -95,6 +103,23 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private FrameLayout mContainer;
private LayoutParams mContainerLayoutParams;
+ /**
+ * The view (or null) that should receive touch events. This will get set if
+ * the touch down hits the container. It will be reset on the touch up.
+ */
+ private View mTouchTargetView;
+ /**
+ * The {@link #mTouchTargetView}'s location in window, set on touch down.
+ */
+ private int[] mTouchTargetLocationInWindow = new int[2];
+ /**
+ * If the zoom ring is dismissed but the user is still in a touch
+ * interaction, we set this to true. This will ignore all touch events until
+ * up/cancel, and then set the owner's touch listener to null.
+ */
+ private boolean mReleaseTouchListenerOnUp;
+
+
/*
* Tap-drag is an interaction where the user first taps and then (quickly)
* does the clockwise or counter-clockwise drag. In reality, this is: (down,
@@ -122,6 +147,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/** Invokes panning of owner view if the zoom ring is touching an edge. */
private Panner mPanner = new Panner();
+ private long mTouchingEdgeStartTime;
+ private boolean mPanningEnabledForThisInteraction;
private ImageView mPanningArrows;
private Animation mPanningArrowsEnterAnimation;
@@ -162,26 +189,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* the UI thread so it will be exceuted AFTER the layout. This is the logic.
*/
private Runnable mPostedVisibleInitializer;
-
- // TODO: need a better way to persist this value, becuase right now this
- // requires the WRITE_SETTINGS perimssion which the app may not have
-// private Runnable mShowTutorialToast = new Runnable() {
-// public void run() {
-// if (Settings.System.getInt(mContext.getContentResolver(),
-// SETTING_NAME_SHOWN_TOAST, 0) == 1) {
-// return;
-// }
-// try {
-// Settings.System.putInt(mContext.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
-// } catch (SecurityException e) {
-// // The app does not have permission to clear this flag, oh well!
-// }
-//
-// Toast.makeText(mContext,
-// com.android.internal.R.string.tutorial_double_tap_to_zoom_message,
-// Toast.LENGTH_LONG).show();
-// }
-// };
+
+ /**
+ * Only touch from the main thread.
+ */
+ private static Dialog sTutorialDialog;
+ private static long sTutorialShowTime;
+ private static final int TUTORIAL_MIN_DISPLAY_TIME = 2000;
private IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -230,38 +244,37 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mOwnerView = ownerView;
mZoomRing = new ZoomRing(context);
+ mZoomRing.setId(com.android.internal.R.id.zoomControls);
mZoomRing.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
- FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER));
mZoomRing.setCallback(this);
createPanningArrows();
- mContainer = new FrameLayout(context);
- mContainer.setMeasureAllChildren(true);
- mContainer.setOnTouchListener(this);
-
- mContainer.addView(mZoomRing);
- mContainer.addView(mPanningArrows);
- mContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-
mContainerLayoutParams = new LayoutParams();
mContainerLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
- mContainerLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL |
+ mContainerLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
LayoutParams.FLAG_NOT_FOCUSABLE |
- LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ LayoutParams.FLAG_LAYOUT_NO_LIMITS;
mContainerLayoutParams.height = LayoutParams.WRAP_CONTENT;
mContainerLayoutParams.width = LayoutParams.WRAP_CONTENT;
mContainerLayoutParams.type = LayoutParams.TYPE_APPLICATION_PANEL;
- mContainerLayoutParams.format = PixelFormat.TRANSLUCENT;
+ mContainerLayoutParams.format = PixelFormat.TRANSPARENT;
// TODO: make a new animation for this
mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Dialog;
+
+ mContainer = new FrameLayout(context);
+ mContainer.setLayoutParams(mContainerLayoutParams);
+ mContainer.setMeasureAllChildren(true);
+
+ mContainer.addView(mZoomRing);
+ mContainer.addView(mPanningArrows);
mScroller = new Scroller(context, new DecelerateInterpolator());
mViewConfig = ViewConfiguration.get(context);
-
-// mHandler.postDelayed(mShowTutorialToast, SHOW_TUTORIAL_TOAST_DELAY);
}
private void createPanningArrows() {
@@ -272,7 +285,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
- mPanningArrows.setVisibility(View.GONE);
+ mPanningArrows.setVisibility(View.INVISIBLE);
mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.fade_in);
@@ -291,6 +304,17 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void setZoomCallbackThreshold(float callbackThreshold) {
mZoomRing.setCallbackThreshold((int) (callbackThreshold * ZoomRing.RADIAN_INT_MULTIPLIER));
}
+
+ /**
+ * Sets a drawable for the zoom ring track.
+ *
+ * @param drawable The drawable to use for the track.
+ * @hide Need a better way of doing this, but this one-off for browser so it
+ * can have its final look for the usability study
+ */
+ public void setZoomRingTrack(int drawable) {
+ mZoomRing.setBackgroundResource(drawable);
+ }
public void setCallback(OnZoomListener callback) {
mCallback = callback;
@@ -300,10 +324,26 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
}
+ public void setThumbAngleAnimated(float angle) {
+ mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0);
+ }
+
public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
mZoomRing.setResetThumbAutomatically(resetThumbAutomatically);
}
+ public void setThumbClockwiseBound(float angle) {
+ mZoomRing.setThumbClockwiseBound(angle >= 0 ?
+ (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
+ Integer.MIN_VALUE);
+ }
+
+ public void setThumbCounterclockwiseBound(float angle) {
+ mZoomRing.setThumbCounterclockwiseBound(angle >= 0 ?
+ (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) :
+ Integer.MIN_VALUE);
+ }
+
public boolean isVisible() {
return mIsZoomRingVisible;
}
@@ -321,12 +361,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
if (mIsZoomRingVisible == visible) {
return;
}
+ mIsZoomRingVisible = visible;
if (visible) {
if (mContainerLayoutParams.token == null) {
mContainerLayoutParams.token = mOwnerView.getWindowToken();
}
-
+
mWindowManager.addView(mContainer, mContainerLayoutParams);
if (mPostedVisibleInitializer == null) {
@@ -340,6 +381,10 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// probably can only be retrieved after it's measured, which happens
// after it's added).
mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(true);
+ }
}
};
}
@@ -349,24 +394,49 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
// Handle configuration changes when visible
mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter);
- // Steal key events from the owner
+ // Steal key/touches events from the owner
mOwnerView.setOnKeyListener(this);
+ mOwnerView.setOnTouchListener(this);
+ mReleaseTouchListenerOnUp = false;
} else {
- // Don't want to steal any more keys
+ // Don't want to steal any more keys/touches
mOwnerView.setOnKeyListener(null);
+ if (mTouchTargetView != null) {
+ // We are still stealing the touch events for this touch
+ // sequence, so release the touch listener later
+ mReleaseTouchListenerOnUp = true;
+ } else {
+ mOwnerView.setOnTouchListener(null);
+ }
// No longer care about configuration changes
mContext.unregisterReceiver(mConfigurationChangedReceiver);
mWindowManager.removeView(mContainer);
- }
-
- mIsZoomRingVisible = visible;
- if (mCallback != null) {
- mCallback.onVisibilityChanged(visible);
+ if (mCallback != null) {
+ mCallback.onVisibilityChanged(false);
+ }
}
+
+ }
+
+ /**
+ * TODO: docs
+ *
+ * Notes:
+ * - Touch dispatching is different. Only direct children who are clickable are eligble for touch events.
+ * - Please ensure you set your View to INVISIBLE not GONE when hiding it.
+ *
+ * @return
+ */
+ public FrameLayout getContainer() {
+ return mContainer;
+ }
+
+ public int getZoomRingId() {
+ return mZoomRing.getId();
}
private void dismissZoomRingDelayed(int delay) {
@@ -484,77 +554,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mOwnerViewBounds, mTempRect);
mCenteredContainerX = mTempRect.left;
mCenteredContainerY = mTempRect.top;
-
}
- // MOVE ALL THIS TO GESTURE DETECTOR
-// public boolean onTouch(View v, MotionEvent event) {
-// int action = event.getAction();
-//
-// if (mListenForInvocation) {
-// switch (mTouchMode) {
-// case TOUCH_MODE_IDLE: {
-// if (action == MotionEvent.ACTION_DOWN) {
-// setFirstTap(event);
-// }
-// break;
-// }
-//
-// case TOUCH_MODE_WAITING_FOR_SECOND_TAP: {
-// switch (action) {
-// case MotionEvent.ACTION_DOWN:
-// if (isSecondTapWithinSlop(event)) {
-// handleDoubleTapEvent(event);
-// } else {
-// setFirstTap(event);
-// }
-// break;
-//
-// case MotionEvent.ACTION_MOVE:
-// int deltaX = (int) event.getX() - mFirstTapX;
-// if (deltaX < -SECOND_TAP_MOVE_SLOP ||
-// deltaX > SECOND_TAP_MOVE_SLOP) {
-// mTouchMode = TOUCH_MODE_IDLE;
-// } else {
-// int deltaY = (int) event.getY() - mFirstTapY;
-// if (deltaY < -SECOND_TAP_MOVE_SLOP ||
-// deltaY > SECOND_TAP_MOVE_SLOP) {
-// mTouchMode = TOUCH_MODE_IDLE;
-// }
-// }
-// break;
-// }
-// break;
-// }
-//
-// case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT:
-// case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: {
-// handleDoubleTapEvent(event);
-// break;
-// }
-// }
-//
-// if (action == MotionEvent.ACTION_CANCEL) {
-// mTouchMode = TOUCH_MODE_IDLE;
-// }
-// }
-//
-// return false;
-// }
-//
-// private void setFirstTap(MotionEvent event) {
-// mFirstTapTime = event.getEventTime();
-// mFirstTapX = (int) event.getX();
-// mFirstTapY = (int) event.getY();
-// mTouchMode = TOUCH_MODE_WAITING_FOR_SECOND_TAP;
-// }
-//
-// private boolean isSecondTapWithinSlop(MotionEvent event) {
-// return mFirstTapTime + SECOND_TAP_TIMEOUT > event.getEventTime() &&
-// Math.abs((int) event.getX() - mFirstTapX) < SECOND_TAP_SLOP &&
-// Math.abs((int) event.getY() - mFirstTapY) < SECOND_TAP_SLOP;
-// }
-
/**
* Centers the point (in owner view's coordinates).
*/
@@ -575,16 +576,28 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void onZoomRingSetMovableHintVisible(boolean visible) {
setPanningArrowsVisible(visible);
}
+
+ public void onUserInteractionStarted() {
+ mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+ }
+
+ public void onUserInteractionStopped() {
+ dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ }
public void onZoomRingMovingStarted() {
- mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
mScroller.abortAnimation();
+ mPanningEnabledForThisInteraction = false;
+ mTouchingEdgeStartTime = 0;
+ if (mCallback != null) {
+ mCallback.onBeginPan();
+ }
}
private void setPanningArrowsVisible(boolean visible) {
mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation
: mPanningArrowsExitAnimation);
- mPanningArrows.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
public boolean onZoomRingMoved(int deltaX, int deltaY) {
@@ -611,37 +624,73 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mWindowManager.updateViewLayout(mContainer, lp);
// Check for pan
+ boolean horizontalPanning = true;
int leftGap = newZoomRingX - ownerBounds.left;
if (leftGap < MAX_PAN_GAP) {
- mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap));
+ if (shouldPan(leftGap)) {
+ mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap));
+ }
} else {
int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft);
if (rightGap < MAX_PAN_GAP) {
- mPanner.setHorizontalStrength(getStrengthFromGap(rightGap));
+ if (shouldPan(rightGap)) {
+ mPanner.setHorizontalStrength(getStrengthFromGap(rightGap));
+ }
} else {
mPanner.setHorizontalStrength(0);
+ horizontalPanning = false;
}
}
int topGap = newZoomRingY - ownerBounds.top;
if (topGap < MAX_PAN_GAP) {
- mPanner.setVerticalStrength(-getStrengthFromGap(topGap));
+ if (shouldPan(topGap)) {
+ mPanner.setVerticalStrength(-getStrengthFromGap(topGap));
+ }
} else {
int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop);
if (bottomGap < MAX_PAN_GAP) {
- mPanner.setVerticalStrength(getStrengthFromGap(bottomGap));
+ if (shouldPan(bottomGap)) {
+ mPanner.setVerticalStrength(getStrengthFromGap(bottomGap));
+ }
} else {
mPanner.setVerticalStrength(0);
+ if (!horizontalPanning) {
+ // Neither are panning, reset any timer to start pan mode
+ mTouchingEdgeStartTime = 0;
+ }
}
}
return true;
}
+ private boolean shouldPan(int gap) {
+ if (mPanningEnabledForThisInteraction) return true;
+
+ if (gap < MAX_INITIATE_PAN_GAP) {
+ long time = SystemClock.elapsedRealtime();
+ if (mTouchingEdgeStartTime != 0 &&
+ mTouchingEdgeStartTime + INITIATE_PAN_DELAY < time) {
+ mPanningEnabledForThisInteraction = true;
+ return true;
+ } else if (mTouchingEdgeStartTime == 0) {
+ mTouchingEdgeStartTime = time;
+ } else {
+ }
+ } else {
+ // Moved away from the initiate pan gap, so reset the timer
+ mTouchingEdgeStartTime = 0;
+ }
+ return false;
+ }
+
public void onZoomRingMovingStopped() {
- dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
mPanner.stop();
- setPanningArrowsVisible(false);
+ setPanningArrowsVisible(false);
+ if (mCallback != null) {
+ mCallback.onEndPan();
+ }
}
private int getStrengthFromGap(int gap) {
@@ -649,10 +698,9 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
(MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP;
}
- public void onZoomRingThumbDraggingStarted(int startAngle) {
- mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+ public void onZoomRingThumbDraggingStarted() {
if (mCallback != null) {
- mCallback.onBeginDrag((float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
+ mCallback.onBeginDrag();
}
}
@@ -674,25 +722,122 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return false;
}
- public void onZoomRingThumbDraggingStopped(int endAngle) {
- dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ public void onZoomRingThumbDraggingStopped() {
if (mCallback != null) {
- mCallback.onEndDrag((float) endAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
+ mCallback.onEndDrag();
}
}
- public void onZoomRingDismissed() {
- dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+ public void onZoomRingDismissed(boolean dismissImmediately) {
+ if (dismissImmediately) {
+ mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+ setVisible(false);
+ } else {
+ dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+ }
}
-
+
+ public void onRingDown(int tickAngle, int touchAngle) {
+ }
+
public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- // If the user touches outside of the zoom ring, dismiss the zoom ring
- dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+ if (sTutorialDialog != null && sTutorialDialog.isShowing() &&
+ SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) {
+ finishZoomTutorial();
+ }
+
+ int action = event.getAction();
+
+ if (mReleaseTouchListenerOnUp) {
+ // The ring was dismissed but we need to throw away all events until the up
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mOwnerView.setOnTouchListener(null);
+ mReleaseTouchListenerOnUp = false;
+ }
+
+ // Eat this event
return true;
}
- return false;
+ View targetView = mTouchTargetView;
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ targetView = mTouchTargetView =
+ getViewForTouch((int) event.getRawX(), (int) event.getRawY());
+ if (targetView != null) {
+ targetView.getLocationInWindow(mTouchTargetLocationInWindow);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mTouchTargetView = null;
+ break;
+ }
+
+ if (targetView != null) {
+ // The upperleft corner of the target view in raw coordinates
+ int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0];
+ int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1];
+
+ MotionEvent containerEvent = MotionEvent.obtain(event);
+ // Convert the motion event into the target view's coordinates (from
+ // owner view's coordinates)
+ containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX,
+ mOwnerViewBounds.top - targetViewRawY);
+ boolean retValue = targetView.dispatchTouchEvent(containerEvent);
+ containerEvent.recycle();
+ return retValue;
+
+ } else {
+ if (action == MotionEvent.ACTION_DOWN) {
+ dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Returns the View that should receive a touch at the given coordinates.
+ *
+ * @param rawX The raw X.
+ * @param rawY The raw Y.
+ * @return The view that should receive the touches, or null if there is not one.
+ */
+ private View getViewForTouch(int rawX, int rawY) {
+ // Check to see if it is touching the ring
+ int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2;
+ int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2;
+ int distanceFromCenterX = rawX - containerCenterX;
+ int distanceFromCenterY = rawY - containerCenterY;
+ int zoomRingRadius = mZoomRingWidth / 2 - ZOOM_RING_RADIUS_INSET;
+ if (distanceFromCenterX * distanceFromCenterX +
+ distanceFromCenterY * distanceFromCenterY <=
+ zoomRingRadius * zoomRingRadius) {
+ return mZoomRing;
+ }
+
+ // Check to see if it is touching any other clickable View.
+ // Reverse order so the child drawn on top gets first dibs.
+ int containerCoordsX = rawX - mContainerLayoutParams.x;
+ int containerCoordsY = rawY - mContainerLayoutParams.y;
+ Rect frame = mTempRect;
+ for (int i = mContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mContainer.getChildAt(i);
+ if (child == mZoomRing || child.getVisibility() != View.VISIBLE ||
+ !child.isClickable()) {
+ continue;
+ }
+
+ child.getHitRect(frame);
+ if (frame.contains(containerCoordsX, containerCoordsY)) {
+ return child;
+ }
+ }
+
+ return null;
}
/** Steals key events from the owner view. */
@@ -707,6 +852,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case KeyEvent.KEYCODE_DPAD_DOWN:
// Keep the zoom alive a little longer
dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ // They started zooming, hide the thumb arrows
+ mZoomRing.setThumbArrowsVisible(false);
if (mCallback != null && event.getAction() == KeyEvent.ACTION_DOWN) {
mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP);
@@ -734,9 +881,14 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
ensureZoomRingIsCentered();
}
+ /*
+ * This is static so Activities can call this instead of the Views
+ * (Activities usually do not have a reference to the ZoomRingController
+ * instance.)
+ */
/**
* Shows a "tutorial" (some text) to the user teaching her the new zoom
- * invocation method.
+ * invocation method. Must call from the main thread.
* <p>
* It checks the global system setting to ensure this has not been seen
* before. Furthermore, if the application does not have privilege to write
@@ -757,20 +909,45 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return;
}
+ if (sTutorialDialog != null && sTutorialDialog.isShowing()) {
+ sTutorialDialog.dismiss();
+ }
+
+ sTutorialDialog = new AlertDialog.Builder(context)
+ .setMessage(
+ com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short)
+ .setIcon(0)
+ .create();
+
+ Window window = sTutorialDialog.getWindow();
+ window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND |
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+ window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+
+ sTutorialDialog.show();
+ sTutorialShowTime = SystemClock.elapsedRealtime();
+ }
+
+ public void finishZoomTutorial() {
+ if (sTutorialDialog == null) return;
+
+ sTutorialDialog.dismiss();
+ sTutorialDialog = null;
+
+ // Record that they have seen the tutorial
try {
- Settings.System.putInt(cr, SETTING_NAME_SHOWN_TOAST, 1);
+ Settings.System.putInt(mContext.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1);
} catch (SecurityException e) {
/*
* The app does not have permission to clear this global flag, make
* sure the user does not see the message when he comes back to this
* same app at least.
*/
+ SharedPreferences sp = mContext.getSharedPreferences("_zoom", Context.MODE_PRIVATE);
sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit();
}
-
- Toast.makeText(context,
- com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short,
- Toast.LENGTH_LONG).show();
}
private class Panner implements Runnable {
@@ -861,12 +1038,14 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
public interface OnZoomListener {
- void onBeginDrag(float startAngle);
+ void onBeginDrag();
boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle,
float curAngle);
- void onEndDrag(float endAngle);
+ void onEndDrag();
void onSimpleZoom(boolean deltaZoomLevel);
+ void onBeginPan();
boolean onPan(int deltaX, int deltaY);
+ void onEndPan();
void onCenter(int x, int y);
void onVisibilityChanged(boolean visible);
}