summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-05 14:34:35 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-05 14:34:35 -0800
commit4df2423a947bcd3f024cc3d3a1a315a8dc428598 (patch)
treee7dac2c5a367b169e7f05a36058cf470e93f003b /core
parentc474dec3ffa1c0fe37edb3e701684188f7e8e7bc (diff)
downloadframeworks_base-4df2423a947bcd3f024cc3d3a1a315a8dc428598.zip
frameworks_base-4df2423a947bcd3f024cc3d3a1a315a8dc428598.tar.gz
frameworks_base-4df2423a947bcd3f024cc3d3a1a315a8dc428598.tar.bz2
auto import from //depot/cupcake/@136594
Diffstat (limited to 'core')
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java8
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java9
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java16
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java72
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java2
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java7
-rw-r--r--core/java/android/os/ResultReceiver.aidl20
-rw-r--r--core/java/android/os/ResultReceiver.java130
-rw-r--r--core/java/android/provider/Settings.java5
-rw-r--r--core/java/android/provider/Telephony.java5
-rw-r--r--core/java/android/text/InputType.java20
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java2
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java27
-rw-r--r--core/java/android/view/ViewGroup.java12
-rw-r--r--core/java/android/view/animation/Animation.java26
-rw-r--r--core/java/android/view/animation/AnimationSet.java33
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java6
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java27
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java287
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java12
-rw-r--r--core/java/android/webkit/BrowserFrame.java23
-rw-r--r--core/java/android/webkit/FrameLoader.java13
-rw-r--r--core/java/android/webkit/LoadListener.java14
-rw-r--r--core/java/android/webkit/PluginContentLoader.java96
-rw-r--r--core/java/android/webkit/PluginData.java116
-rw-r--r--core/java/android/webkit/TextDialog.java8
-rw-r--r--core/java/android/webkit/UrlInterceptHandler.java15
-rw-r--r--core/java/android/webkit/UrlInterceptRegistry.java34
-rw-r--r--core/java/android/webkit/WebView.java29
-rw-r--r--core/java/android/webkit/gears/UrlInterceptHandlerGears.java160
-rw-r--r--core/java/android/widget/AbsListView.java15
-rw-r--r--core/java/android/widget/FastScroller.java10
-rw-r--r--core/java/android/widget/GridView.java4
-rw-r--r--core/java/android/widget/ImageView.java6
-rw-r--r--core/java/android/widget/ListView.java26
-rw-r--r--core/java/android/widget/TextView.java158
-rw-r--r--core/java/android/widget/ZoomRing.java398
-rw-r--r--core/java/android/widget/ZoomRingController.java381
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java4
-rw-r--r--core/java/com/android/internal/os/IResultReceiver.aidl25
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl5
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl8
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl2
-rw-r--r--core/java/com/android/internal/widget/EditStyledText.java653
-rw-r--r--core/jni/android/graphics/Typeface.cpp6
-rw-r--r--core/jni/android_text_format_Time.cpp146
-rw-r--r--core/res/assets/webkit/nullPlugin.png (renamed from core/res/assets/webkit/nullplugin.png)bin1552 -> 1552 bytes
-rw-r--r--core/res/res/anim/zoom_ring_enter.xml29
-rw-r--r--core/res/res/anim/zoom_ring_exit.xml28
-rw-r--r--core/res/res/values/attrs.xml47
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/values/styles.xml20
-rw-r--r--core/res/res/values/themes.xml1
54 files changed, 2543 insertions, 670 deletions
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index f89c87d..9e85452 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -22,7 +22,7 @@ import android.util.Log;
* A base class for compiled SQLite programs.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
- static final String TAG = "SQLiteProgram";
+ private static final String TAG = "SQLiteProgram";
/** The database this program is compiled against. */
protected SQLiteDatabase mDatabase;
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 5bfa0e8..1386a0d 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -18,13 +18,14 @@ package android.database.sqlite;
import android.database.CursorWindow;
import android.os.SystemClock;
+import android.util.Log;
/**
* A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
* This class is used by SQLiteCursor and isn't useful itself.
*/
public class SQLiteQuery extends SQLiteProgram {
- //private static final String TAG = "Cursor";
+ private static final String TAG = "Cursor";
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
@@ -73,6 +74,11 @@ public class SQLiteQuery extends SQLiteProgram {
// is not safe in this situation. the native code will ignore maxRead
int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
maxRead, lastPos);
+
+ // Logging
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ Log.d(TAG, "fillWindow(): " + mQuery);
+ }
if (logStats) {
mDatabase.logTimeStat(true /* read */, startTime,
SystemClock.elapsedRealtime());
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 5a85c66..6cf90d6 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -30,6 +30,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
private static final int DO_UPDATE_SELECTION = 90;
private static final int DO_UPDATE_CURSOR = 95;
private static final int DO_APP_PRIVATE_COMMAND = 100;
+ private static final int DO_TOGGLE_SOFT_INPUT = 105;
final HandlerCaller mCaller;
final InputMethodSession mInputMethodSession;
@@ -106,6 +107,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mCaller.recycleArgs(args);
return;
}
+ case DO_TOGGLE_SOFT_INPUT: {
+ mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2);
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -149,4 +154,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
public void appPrivateCommand(String action, Bundle data) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
}
+
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index a2c75b5..20a05a5 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -13,6 +13,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
@@ -144,10 +145,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
mInputMethod.revokeSession((InputMethodSession)msg.obj);
return;
case DO_SHOW_SOFT_INPUT:
- mInputMethod.showSoftInput(msg.arg1);
+ mInputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
return;
case DO_HIDE_SOFT_INPUT:
- mInputMethod.hideSoftInput();
+ mInputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
return;
}
Log.w(TAG, "Unhandled message code: " + msg.what);
@@ -225,12 +226,13 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
- public void showSoftInput(int flags) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_SHOW_SOFT_INPUT,
- flags));
+ public void showSoftInput(int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
+ flags, resultReceiver));
}
- public void hideSoftInput() {
- mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_HIDE_SOFT_INPUT));
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
+ flags, resultReceiver));
}
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 1e2e2f3..f1e613e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.InputType;
@@ -53,7 +54,6 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.FrameLayout;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -348,23 +348,37 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* Handle a request by the system to hide the soft input area.
*/
- public void hideSoftInput() {
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
+ boolean wasVis = isInputViewShown();
mShowInputFlags = 0;
mShowInputRequested = false;
mShowInputForced = false;
hideWindow();
+ if (resultReceiver != null) {
+ resultReceiver.send(wasVis != isInputViewShown()
+ ? InputMethodManager.RESULT_HIDDEN
+ : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
+ }
}
/**
* Handle a request by the system to show the soft input area.
*/
- public void showSoftInput(int flags) {
+ public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
+ boolean wasVis = isInputViewShown();
mShowInputFlags = 0;
if (onShowInputRequested(flags, false)) {
showWindow(true);
}
+ if (resultReceiver != null) {
+ resultReceiver.send(wasVis != isInputViewShown()
+ ? InputMethodManager.RESULT_SHOWN
+ : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
+ }
}
}
@@ -440,6 +454,13 @@ public class InputMethodService extends AbstractInputMethodService {
}
InputMethodService.this.onAppPrivateCommand(action, data);
}
+
+ /**
+ *
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ InputMethodService.this.onToggleSoftInput(showFlags, hideFlags);
+ }
}
/**
@@ -1048,7 +1069,7 @@ public class InputMethodService extends AbstractInputMethodService {
* text; you can override this (not calling the base class implementation)
* to perform whatever behavior you would like.
*
- * @boolean finishingInput If true, {@link #onFinishInput} will be
+ * @param finishingInput If true, {@link #onFinishInput} will be
* called immediately after.
*/
public void onFinishInputView(boolean finishingInput) {
@@ -1092,7 +1113,7 @@ public class InputMethodService extends AbstractInputMethodService {
* text; you can override this (not calling the base class implementation)
* to perform whatever behavior you would like.
*
- * @boolean finishingInput If true, {@link #onFinishInput} will be
+ * @param finishingInput If true, {@link #onFinishInput} will be
* called immediately after.
*/
public void onFinishCandidatesView(boolean finishingInput) {
@@ -1107,14 +1128,14 @@ 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)}
+ * {@link InputMethod#showSoftInput InputMethod.showSoftInput()}
* 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)}.
+ * as per {@link InputMethod#showSoftInput InputMethod.showSoftInput()}.
* @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.
@@ -1290,7 +1311,7 @@ public class InputMethodService extends AbstractInputMethodService {
mStartedInputConnection = null;
mCurCompletions = null;
}
-
+
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
if (!restarting) {
doFinishInput();
@@ -1399,11 +1420,25 @@ public class InputMethodService extends AbstractInputMethodService {
* 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
* InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
*/
- public void dismissSoftInput(int flags) {
+ public void requestHideSelf(int flags) {
mImm.hideSoftInputFromInputMethod(mToken, flags);
}
/**
+ * Show the input method. This is a call back to the
+ * IMF to handle showing the input method.
+ * Close this input method's soft input area, removing it from the display.
+ * The input method will continue running, but the user can no longer use
+ * it to generate input by touching the screen.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link InputMethodManager#SHOW_FORCED
+ * InputMethodManager.} bit set.
+ */
+ private void requestShowSelf(int flags) {
+ mImm.showSoftInputFromInputMethod(mToken, flags);
+ }
+
+ /**
* Override this to intercept key down events before they are processed by the
* application. If you return true, the application will not itself
* process the event. If you return true, the normal application processing
@@ -1421,7 +1456,7 @@ public class InputMethodService extends AbstractInputMethodService {
if (mShowInputRequested) {
// If the soft input area is shown, back closes it and we
// consume the back key.
- dismissSoftInput(0);
+ requestHideSelf(0);
return true;
} else if (mWindowVisible) {
if (mCandidatesVisibility == View.VISIBLE) {
@@ -1438,7 +1473,6 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
}
-
return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
@@ -1480,6 +1514,18 @@ public class InputMethodService extends AbstractInputMethodService {
public void onAppPrivateCommand(String action, Bundle data) {
}
+ /**
+ * Handle a request by the system to toggle the soft input area.
+ */
+ private void onToggleSoftInput(int showFlags, int hideFlags) {
+ if (DEBUG) Log.v(TAG, "toggleSoftInput()");
+ if (isInputViewShown()) {
+ requestHideSelf(hideFlags);
+ } else {
+ requestShowSelf(showFlags);
+ }
+ }
+
static final int MOVEMENT_DOWN = -1;
static final int MOVEMENT_UP = -2;
@@ -1737,6 +1783,8 @@ public class InputMethodService extends AbstractInputMethodService {
return getText(com.android.internal.R.string.ime_action_send);
case EditorInfo.IME_ACTION_NEXT:
return getText(com.android.internal.R.string.ime_action_next);
+ case EditorInfo.IME_ACTION_DONE:
+ return getText(com.android.internal.R.string.ime_action_done);
default:
return getText(com.android.internal.R.string.ime_action_default);
}
@@ -1777,7 +1825,7 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public void onExtractingInputChanged(EditorInfo ei) {
if (ei.inputType == InputType.TYPE_NULL) {
- dismissSoftInput(InputMethodManager.HIDE_NOT_ALWAYS);
+ requestHideSelf(InputMethodManager.HIDE_NOT_ALWAYS);
}
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index c838779..f58b7ef 100755
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -502,7 +502,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
private CharSequence adjustCase(CharSequence label) {
- if (mKeyboard.isShifted() && label != null && label.length() == 1
+ if (mKeyboard.isShifted() && label != null && label.length() < 3
&& Character.isLowerCase(label.charAt(0))) {
label = label.toString().toUpperCase();
}
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index 0c4fcda..c2013d5 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -63,6 +63,7 @@ import android.util.Log;
import android.content.ContentResolver;
import android.provider.Settings;
import android.text.TextUtils;
+import android.os.SystemProperties;
/**
* Subclass of the Apache {@link DefaultHttpClient} that is configured with
@@ -387,10 +388,12 @@ public final class AndroidHttpClient implements HttpClient {
}
/**
- * Returns true if auth logging is turned on for this configuration.
+ * Returns true if auth logging is turned on for this configuration. Can only be set on
+ * insecure devices.
*/
private boolean isAuthLoggable() {
- return Log.isLoggable(tag + "-auth", level);
+ String secure = SystemProperties.get("ro.secure");
+ return "0".equals(secure) && Log.isLoggable(tag + "-auth", level);
}
/**
diff --git a/core/java/android/os/ResultReceiver.aidl b/core/java/android/os/ResultReceiver.aidl
new file mode 100644
index 0000000..28ce6d5
--- /dev/null
+++ b/core/java/android/os/ResultReceiver.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/os/ParcelFileDescriptor.aidl
+**
+** Copyright 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.
+** 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.os;
+
+parcelable ResultReceiver;
diff --git a/core/java/android/os/ResultReceiver.java b/core/java/android/os/ResultReceiver.java
new file mode 100644
index 0000000..711d4d9
--- /dev/null
+++ b/core/java/android/os/ResultReceiver.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2009 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.os;
+
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * Generic interface for receiving a callback result from someone. Use this
+ * by creating a subclass and implement {@link #onReceiveResult}, which you can
+ * then pass to others and send through IPC, and receive results they
+ * supply with {@link #send}.
+ */
+public class ResultReceiver implements Parcelable {
+ final boolean mLocal;
+ final Handler mHandler;
+
+ IResultReceiver mReceiver;
+
+ class MyRunnable implements Runnable {
+ final int mResultCode;
+ final Bundle mResultData;
+
+ MyRunnable(int resultCode, Bundle resultData) {
+ mResultCode = resultCode;
+ mResultData = resultData;
+ }
+
+ public void run() {
+ onReceiveResult(mResultCode, mResultData);
+ }
+ }
+
+ class MyResultReceiver extends IResultReceiver.Stub {
+ public void send(int resultCode, Bundle resultData) {
+ if (mHandler != null) {
+ mHandler.post(new MyRunnable(resultCode, resultData));
+ } else {
+ onReceiveResult(resultCode, resultData);
+ }
+ }
+ }
+
+ /**
+ * Create a new ResultReceive to receive results. Your
+ * {@link #onReceiveResult} method will be called from the thread running
+ * <var>handler</var> if given, or from an arbitrary thread if null.
+ */
+ public ResultReceiver(Handler handler) {
+ mLocal = true;
+ mHandler = handler;
+ }
+
+ /**
+ * Deliver a result to this receiver. Will call {@link #onReceiveResult},
+ * always asynchronously if the receiver has supplied a Handler in which
+ * to dispatch the result.
+ * @param resultCode Arbitrary result code to deliver, as defined by you.
+ * @param resultData Any additional data provided by you.
+ */
+ public void send(int resultCode, Bundle resultData) {
+ if (mLocal) {
+ if (mHandler != null) {
+ mHandler.post(new MyRunnable(resultCode, resultData));
+ } else {
+ onReceiveResult(resultCode, resultData);
+ }
+ return;
+ }
+
+ if (mReceiver != null) {
+ try {
+ mReceiver.send(resultCode, resultData);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Override to receive results delivered to this object.
+ *
+ * @param resultCode Arbitrary result code delivered by the sender, as
+ * defined by the sender.
+ * @param resultData Any additional data provided by the sender.
+ */
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ synchronized (this) {
+ if (mReceiver == null) {
+ mReceiver = new MyResultReceiver();
+ }
+ out.writeStrongBinder(mReceiver.asBinder());
+ }
+ }
+
+ ResultReceiver(Parcel in) {
+ mLocal = false;
+ mHandler = null;
+ mReceiver = IResultReceiver.Stub.asInterface(in.readStrongBinder());
+ }
+
+ public static final Parcelable.Creator<ResultReceiver> CREATOR
+ = new Parcelable.Creator<ResultReceiver>() {
+ public ResultReceiver createFromParcel(Parcel in) {
+ return new ResultReceiver(in);
+ }
+ public ResultReceiver[] newArray(int size) {
+ return new ResultReceiver[size];
+ }
+ };
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b536b0d..b0ee479 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2825,6 +2825,11 @@ public final class Settings {
public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
"battery_discharge_duration_threshold";
public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+
+ /**
+ * An email address that anr bugreports should be sent to.
+ */
+ public static final String ANR_BUGREPORT_RECIPIENT = "anr_bugreport_recipient";
/**
* @deprecated
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 18c64ed..d802c14 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -1023,6 +1023,11 @@ public final class Telephony {
* <P>Type: INTEGER</P>
*/
public static final String ERROR = "error";
+ /**
+ * Indicates whether this thread contains any attachments.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String HAS_ATTACHMENT = "has_attachment";
}
/**
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index f643f92..d50684a 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -179,9 +179,27 @@ public interface InputType {
public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000080;
/**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a password, which should
+ * be visible to the user.
+ */
+ public static final int TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 0x00000090;
+
+ /**
* Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form.
*/
- public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090;
+ public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x000000a0;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering text to filter contents
+ * of a list etc.
+ */
+ public static final int TYPE_TEXT_VARIATION_FILTER = 0x000000b0;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering text for phonetic
+ * pronunciation, such as a phonetic name field in contacts.
+ */
+ public static final int TYPE_TEXT_VARIATION_PHONETIC = 0x000000c0;
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 6df0b35..8aa49af 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -204,7 +204,7 @@ implements MovementMethod
MotionEvent event) {
boolean handled = Touch.onTouchEvent(widget, buffer, event);
- if (widget.isFocused()) {
+ if (widget.isFocused() && !widget.didTouchFocusSelectAll()) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index 0b39517..3f8288c 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -296,21 +296,28 @@ public class QwertyKeyListener extends BaseKeyListener {
String old = new String(repl[0].mText);
content.removeSpan(repl[0]);
- content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
- en, en, Spannable.SPAN_POINT_POINT);
- content.replace(st, en, old);
- en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
- if (en - 1 >= 0) {
+ // only cancel the autocomplete if the cursor is at the end of
+ // the replaced span
+ if (selStart == en) {
content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
- en - 1, en,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ en, en, Spannable.SPAN_POINT_POINT);
+ content.replace(st, en, old);
+
+ en = content.getSpanStart(TextKeyListener.INHIBIT_REPLACEMENT);
+ if (en - 1 >= 0) {
+ content.setSpan(TextKeyListener.INHIBIT_REPLACEMENT,
+ en - 1, en,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
+ }
+ adjustMetaAfterKeypress(content);
} else {
- content.removeSpan(TextKeyListener.INHIBIT_REPLACEMENT);
+ adjustMetaAfterKeypress(content);
+ return super.onKeyDown(view, content, keyCode, event);
}
- adjustMetaAfterKeypress(content);
-
return true;
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 70cc2a9..dc7b299 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1387,14 +1387,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
mPrivateFlags |= DRAW_ANIMATION;
- // 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.)
- 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));
+
+ final int left = cl + (int) region.left;
+ final int top = ct + (int) region.top;
+ invalidate(left, top, left + (int) region.width(), top + (int) region.height());
}
}
} else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index b9c8ec3..a662760 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -340,6 +340,7 @@ public abstract class Animation implements Cloneable {
* to run.
*/
public void restrictDuration(long durationMillis) {
+ // If we start after the duration, then we just won't run.
if (mStartOffset > durationMillis) {
mStartOffset = durationMillis;
mDuration = 0;
@@ -349,11 +350,26 @@ public abstract class Animation implements Cloneable {
long dur = mDuration + mStartOffset;
if (dur > durationMillis) {
- mDuration = dur = durationMillis-mStartOffset;
+ mDuration = durationMillis-mStartOffset;
+ dur = durationMillis;
}
+ // If the duration is 0 or less, then we won't run.
+ if (mDuration <= 0) {
+ mDuration = 0;
+ mRepeatCount = 0;
+ return;
+ }
+ // Reduce the number of repeats to keep below the maximum duration.
+ // The comparison between mRepeatCount and duration is to catch
+ // overflows after multiplying them.
if (mRepeatCount < 0 || mRepeatCount > durationMillis
|| (dur*mRepeatCount) > durationMillis) {
- mRepeatCount = (int)(durationMillis/dur);
+ // Figure out how many times to do the animation. Subtract 1 since
+ // repeat count is the number of times to repeat so 0 runs once.
+ mRepeatCount = (int)(durationMillis/dur) - 1;
+ if (mRepeatCount < 0) {
+ mRepeatCount = 0;
+ }
}
}
@@ -416,7 +432,7 @@ public abstract class Animation implements Cloneable {
* Sets how many times the animation should be repeated. If the repeat
* count is 0, the animation is never repeated. If the repeat count is
* greater than 0 or {@link #INFINITE}, the repeat mode will be taken
- * into account. The repeat count if 0 by default.
+ * into account. The repeat count is 0 by default.
*
* @param repeatCount the number of times the animation should be repeated
* @attr ref android.R.styleable#Animation_repeatCount
@@ -806,6 +822,8 @@ public abstract class Animation implements Cloneable {
invalidate.set(left, top, right, bottom);
transformation.getMatrix().mapRect(invalidate);
+ // Enlarge the invalidate region to account for rounding errors
+ invalidate.inset(-1.0f, -1.0f);
tempRegion.set(invalidate);
invalidate.union(previousRegion);
@@ -830,6 +848,8 @@ public abstract class Animation implements Cloneable {
public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
final RectF region = mPreviousRegion;
region.set(left, top, right, bottom);
+ // Enlarge the invalidate region to account for rounding errors
+ region.inset(-1.0f, -1.0f);
if (mFillBefore) {
final Transformation previousTransformation = mPreviousTransformation;
applyTransformation(0.0f, previousTransformation);
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 590ce06..98b2594 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -266,32 +266,10 @@ public class AnimationSet extends Animation {
/**
* @hide
*/
- public void getInvalidateRegion(int left, int top, int right, int bottom,
- RectF invalidate, Transformation transformation) {
-
- final RectF previousRegion = mPreviousRegion;
-
- invalidate.set(left, top, right, bottom);
- transformation.getMatrix().mapRect(invalidate);
- invalidate.union(previousRegion);
-
- previousRegion.set(left, top, right, bottom);
- transformation.getMatrix().mapRect(previousRegion);
-
- final Transformation tempTransformation = mTransformation;
- final Transformation previousTransformation = mPreviousTransformation;
-
- tempTransformation.set(transformation);
- transformation.set(previousTransformation);
- previousTransformation.set(tempTransformation);
- }
-
- /**
- * @hide
- */
public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
final RectF region = mPreviousRegion;
region.set(left, top, right, bottom);
+ region.inset(-1.0f, -1.0f);
if (mFillBefore) {
final int count = mAnimations.size();
@@ -400,8 +378,12 @@ public class AnimationSet extends Animation {
long[] storedOffsets = mStoredOffsets;
- if (storedOffsets == null || storedOffsets.length != count) {
- storedOffsets = mStoredOffsets = new long[count];
+ if (startOffsetSet) {
+ if (storedOffsets == null || storedOffsets.length != count) {
+ storedOffsets = mStoredOffsets = new long[count];
+ }
+ } else if (storedOffsets != null) {
+ storedOffsets = mStoredOffsets = null;
}
for (int i = 0; i < count; i++) {
@@ -446,7 +428,6 @@ public class AnimationSet extends Animation {
final ArrayList<Animation> children = mAnimations;
final int count = children.size();
-
for (int i = 0; i < count; i++) {
children.get(i).setStartOffset(offsets[i]);
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 0405371..1c0d42a 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -66,6 +66,12 @@ public class EditorInfo implements InputType, Parcelable {
public static final int IME_ACTION_NEXT = 0x00000004;
/**
+ * Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
+ * operation, typically meaning the IME will be closed.
+ */
+ public static final int IME_ACTION_DONE = 0x00000005;
+
+ /**
* Flag of {@link #imeOptions}: used in conjunction with
* {@link #IME_MASK_ACTION}, this indicates that the action should not
* be available in-line as the same as a "enter" key. Typically this is
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 740dca8..a5e0e94 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -18,6 +18,7 @@ package android.view.inputmethod;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.os.ResultReceiver;
/**
* The InputMethod interface represents an input method which can generate key
@@ -171,7 +172,7 @@ public interface InputMethod {
public void revokeSession(InputMethodSession session);
/**
- * Flag for {@link #showSoftInput(int)}: this show has been explicitly
+ * Flag for {@link #showSoftInput}: this show has been explicitly
* requested by the user. If not set, the system has decided it may be
* a good idea to show the input method based on a navigation operation
* in the UI.
@@ -179,7 +180,7 @@ public interface InputMethod {
public static final int SHOW_EXPLICIT = 0x00001;
/**
- * Flag for {@link #showSoftInput(int)}: this show has been forced to
+ * Flag for {@link #showSoftInput}: this show has been forced to
* happen by the user. If set, the input method should remain visible
* until deliberated dismissed by the user in its UI.
*/
@@ -188,13 +189,29 @@ public interface InputMethod {
/**
* Request that any soft input part of the input method be shown to the user.
*
- * @param flags Provide additional information about the show request.
+ * @param flags Provides additional information about the show request.
* Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
- public void showSoftInput(int flags);
+ public void showSoftInput(int flags, ResultReceiver resultReceiver);
/**
* Request that any soft input part of the input method be hidden from the user.
+ * @param flags Provides additional information about the show request.
+ * Currently always 0.
+ * @param resultReceiver The client requesting the show may wish to
+ * be told the impact of their request, which should be supplied here.
+ * The result code should be
+ * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN},
+ * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN},
+ * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
+ * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
*/
- public void hideSoftInput();
+ public void hideSoftInput(int flags, ResultReceiver resultReceiver);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0aa1d6c..916ffea 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -24,6 +24,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -287,6 +288,9 @@ public final class InputMethodManager {
// -----------------------------------------------------------
static final int MSG_DUMP = 1;
+ static final int MSG_BIND = 2;
+ static final int MSG_UNBIND = 3;
+ static final int MSG_SET_ACTIVE = 4;
class H extends Handler {
H(Looper looper) {
@@ -309,6 +313,68 @@ public final class InputMethodManager {
}
return;
}
+ case MSG_BIND: {
+ final InputBindResult res = (InputBindResult)msg.obj;
+ synchronized (mH) {
+ if (mBindSequence < 0 || mBindSequence != res.sequence) {
+ Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+ + ", given seq=" + res.sequence);
+ return;
+ }
+
+ mCurMethod = res.method;
+ mCurId = res.id;
+ mBindSequence = res.sequence;
+ }
+ startInputInner();
+ return;
+ }
+ case MSG_UNBIND: {
+ final int sequence = msg.arg1;
+ synchronized (mH) {
+ if (mBindSequence == sequence) {
+ if (false) {
+ // XXX the server has already unbound!
+ if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
+ try {
+ mCurMethod.finishInput();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+ clearBindingLocked();
+
+ // If we were actively using the last input method, then
+ // we would like to re-connect to the next input method.
+ if (mServedView != null && mServedView.isFocused()) {
+ mServedConnecting = true;
+ }
+ }
+ startInputInner();
+ }
+ return;
+ }
+ case MSG_SET_ACTIVE: {
+ final boolean active = msg.arg1 != 0;
+ synchronized (mH) {
+ mActive = active;
+ mFullscreenMode = false;
+ if (!active) {
+ // Some other client has starting using the IME, so note
+ // that this happened and make sure our own editor's
+ // state is reset.
+ mHasBeenInactive = true;
+ try {
+ // Note that finishComposingText() is allowed to run
+ // even when we are not active.
+ mIInputContext.finishComposingText();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ return;
+ }
}
}
}
@@ -348,62 +414,15 @@ public final class InputMethodManager {
}
public void onBindMethod(InputBindResult res) {
- synchronized (mH) {
- if (mBindSequence < 0 || mBindSequence != res.sequence) {
- Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
- + ", given seq=" + res.sequence);
- return;
- }
-
- mCurMethod = res.method;
- mCurId = res.id;
- mBindSequence = res.sequence;
- }
- startInputInner();
+ mH.sendMessage(mH.obtainMessage(MSG_BIND, res));
}
public void onUnbindMethod(int sequence) {
- synchronized (mH) {
- if (mBindSequence == sequence) {
- if (false) {
- // XXX the server has already unbound!
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- mCurMethod.finishInput();
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
- }
- }
- }
- clearBindingLocked();
-
- // If we were actively using the last input method, then
- // we would like to re-connect to the next input method.
- if (mServedView != null && mServedView.isFocused()) {
- mServedConnecting = true;
- }
- }
- startInputInner();
- }
+ mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, 0));
}
public void setActive(boolean active) {
- synchronized (mH) {
- mActive = active;
- mFullscreenMode = false;
- if (!active) {
- // Some other client has starting using the IME, so note
- // that this happened and make sure our own editor's
- // state is reset.
- mHasBeenInactive = true;
- try {
- // Note that finishComposingText() is allowed to run
- // even when we are not active.
- mIInputContext.finishComposingText();
- } catch (RemoteException e) {
- }
- }
- }
+ mH.sendMessage(mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, 0));
}
};
@@ -646,6 +665,52 @@ public final class InputMethodManager {
public static final int SHOW_FORCED = 0x0002;
/**
+ * Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
+ * a result receiver: explicitly request that the current input method's
+ * soft input area be shown to the user, if needed.
+ *
+ * @param view The currently focused view, which would like to receive
+ * soft keyboard input.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} bit set.
+ */
+ public boolean showSoftInput(View view, int flags) {
+ return showSoftInput(view, flags, null);
+ }
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window was unchanged and remains shown.
+ */
+ public static final int RESULT_UNCHANGED_SHOWN = 0;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window was unchanged and remains hidden.
+ */
+ public static final int RESULT_UNCHANGED_HIDDEN = 1;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window changed from hidden to shown.
+ */
+ public static final int RESULT_SHOWN = 2;
+
+ /**
+ * Flag for the {@link ResultReceiver} result code from
+ * {@link #showSoftInput(View, int, ResultReceiver)} and
+ * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
+ * state of the soft input window changed from shown to hidden.
+ */
+ public static final int RESULT_HIDDEN = 3;
+
+ /**
* Explicitly request that the current input method's soft input area be
* shown to the user, if needed. Call this if the user interacts with
* your view in such a way that they have expressed they would like to
@@ -655,25 +720,33 @@ public final class InputMethodManager {
* soft keyboard input.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #SHOW_IMPLICIT} bit set.
+ * @param resultReceiver If non-null, this will be called by the IME when
+ * it has processed your request to tell you what it has done. The result
+ * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+ * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+ * {@link #RESULT_HIDDEN}.
*/
- public void showSoftInput(View view, int flags) {
+ public boolean showSoftInput(View view, int flags,
+ ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
if (mServedView != view) {
- return;
+ return false;
}
try {
- mService.showSoftInput(mClient, flags);
+ return mService.showSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
}
+
+ return false;
}
}
/** @hide */
- public void showSoftInputUnchecked(int flags) {
+ public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
try {
- mService.showSoftInput(mClient, flags);
+ mService.showSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
}
}
@@ -693,6 +766,20 @@ public final class InputMethodManager {
public static final int HIDE_NOT_ALWAYS = 0x0002;
/**
+ * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)
+ * without a result: request to hide the soft input window from the
+ * context of the window that is currently accepting input.
+ *
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ */
+ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
+ return hideSoftInputFromWindow(windowToken, flags, null);
+ }
+
+ /**
* Request to hide the soft input window from the context of the window
* that is currently accepting input. This should be called as a result
* of the user doing some actually than fairly explicitly requests to
@@ -702,21 +789,77 @@ public final class InputMethodManager {
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ * @param resultReceiver If non-null, this will be called by the IME when
+ * it has processed your request to tell you what it has done. The result
+ * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
+ * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
+ * {@link #RESULT_HIDDEN}.
*/
- public void hideSoftInputFromWindow(IBinder windowToken, int flags) {
+ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
+ ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
if (mServedView == null || mServedView.getWindowToken() != windowToken) {
- return;
+ return false;
}
try {
- mService.hideSoftInput(mClient, flags);
+ return mService.hideSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
}
+ return false;
}
}
+
+ /**
+ * This method toggles the input method window display.
+ * If the input window is already displayed, it gets hidden.
+ * If not the input window will be displayed.
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link #SHOW_IMPLICIT},
+ * {@link #SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ **/
+ public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
+ synchronized (mH) {
+ if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ return;
+ }
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ /*
+ * This method toggles the input method window display.
+ * If the input window is already displayed, it gets hidden.
+ * If not the input window will be displayed.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link #SHOW_IMPLICIT},
+ * {@link #SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
+ * @hide
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags) {
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.toggleSoftInput(showFlags, hideFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
/**
* If the input method is currently connected to the given view,
* restart it with its new contents. You should call this when the text
@@ -956,7 +1099,7 @@ public final class InputMethodManager {
void closeCurrentInput() {
try {
- mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS);
+ mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS, null);
} catch (RemoteException e) {
}
}
@@ -1118,7 +1261,8 @@ public final class InputMethodManager {
* when it was started, which allows it to perform this operation on
* itself.
* @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
+ * 0 or have the {@link #HIDE_IMPLICIT_ONLY},
+ * {@link #HIDE_NOT_ALWAYS} bit set.
*/
public void hideSoftInputFromInputMethod(IBinder token, int flags) {
try {
@@ -1129,6 +1273,27 @@ public final class InputMethodManager {
}
/**
+ * Show the input method's soft input area, so the user
+ * sees the input method window and can interact with it.
+ * This can only be called from the currently active input method,
+ * as validated by the given token.
+ *
+ * @param token Supplies the identifying token given to an input method
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param flags Provides additional operating flags. Currently may be
+ * 0 or have the {@link #SHOW_IMPLICIT} or
+ * {@link #SHOW_FORCED} bit set.
+ */
+ public void showSoftInputFromInputMethod(IBinder token, int flags) {
+ try {
+ mService.showMySoftInput(token, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* @hide
*/
public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
@@ -1204,7 +1369,7 @@ public final class InputMethodManager {
}
}
}
-
+
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index b5bbaff..bb03afa 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -139,4 +139,16 @@ public interface InputMethodSession {
* @param data Any data to include with the command.
*/
public void appPrivateCommand(String action, Bundle data);
+
+ /**
+ * Toggle the soft input window.
+ * Applications can toggle the state of the soft input window.
+ * @param showFlags Provides additional operating flags. May be
+ * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT},
+ * {@link InputMethodManager#SHOW_FORCED} bit set.
+ * @param hideFlags Provides additional operating flags. May be
+ * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
+ * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
+ */
+ public void toggleSoftInput(int showFlags, int hideFlags);
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 451af6d..5401a6e 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -201,10 +201,14 @@ class BrowserFrame extends Handler {
final String failingUrl) {
// As this is called for the main resource and loading will be stopped
// after, reset the state variables.
+ resetLoadingStates();
+ mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
+ }
+
+ private void resetLoadingStates() {
mCommitted = true;
mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false;
mFirstLayoutDone = true;
- mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
}
/* package */boolean committed() {
@@ -290,6 +294,7 @@ class BrowserFrame extends Handler {
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
if (isMainFrame) {
+ resetLoadingStates();
mCallbackProxy.switchOutDrawHistory();
mCallbackProxy.onPageFinished(url);
}
@@ -555,8 +560,11 @@ class BrowserFrame extends Handler {
method, isHighPriority);
loader.setHeaders(headers);
loader.setPostData(postData);
- loader.setCacheMode(cacheMode); // Set the load mode to the mode used
- // for the current page.
+ // Set the load mode to the mode used for the current page.
+ // If WebKit wants validation, go to network directly.
+ loader.setCacheMode(headers.containsKey("If-Modified-Since")
+ || headers.containsKey("If-None-Match") ?
+ WebSettings.LOAD_NO_CACHE : cacheMode);
// Set referrer to current URL?
if (!loader.executeLoad()) {
checker.responseAlert("startLoadingResource fail");
@@ -751,7 +759,14 @@ class BrowserFrame extends Handler {
/**
* Stop loading the current page.
*/
- public native void stopLoading();
+ public void stopLoading() {
+ if (mIsMainFrame) {
+ resetLoadingStates();
+ }
+ nativeStopLoading();
+ }
+
+ private native void nativeStopLoading();
/**
* Return true if the document has images.
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index 5e323eb..42d03f0 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -21,7 +21,6 @@ import android.net.http.RequestHandle;
import android.util.Config;
import android.util.Log;
import android.webkit.CacheManager.CacheResult;
-import android.webkit.UrlInterceptRegistry;
import java.util.HashMap;
import java.util.Map;
@@ -234,12 +233,14 @@ class FrameLoader {
private boolean handleUrlIntercept() {
// Check if the URL can be served from UrlIntercept. If
// successful, return the data just like a cache hit.
- CacheResult result = UrlInterceptRegistry.getSurrogate(
+
+ PluginData data = UrlInterceptRegistry.getPluginData(
mListener.url(), mHeaders);
- if(result != null) {
- // Intercepted. The data is stored in result.stream. Setup
- // a load from the CacheResult.
- startCacheLoad(result);
+
+ if(data != null) {
+ PluginContentLoader loader =
+ new PluginContentLoader(mListener, data);
+ loader.load();
return true;
}
// Not intercepted. Carry on as normal.
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index dfae17d..8d4b220 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -1126,6 +1126,7 @@ class LoadListener extends Handler implements EventHandler {
mCacheResult = null;
}
+ // This will strip the anchor
setUrl(redirectTo);
// Redirect may be in the cache
@@ -1143,7 +1144,7 @@ class LoadListener extends Handler implements EventHandler {
// mRequestHandle can be null when the request was satisfied
// by the cache, and the cache returned a redirect
if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(redirectTo, mStatusCode,
+ mRequestHandle.setupRedirect(mUrl, mStatusCode,
mRequestHeaders);
} else {
// If the original request came from the cache, there is no
@@ -1336,19 +1337,16 @@ class LoadListener extends Handler implements EventHandler {
*/
void setUrl(String url) {
if (url != null) {
- if (URLUtil.isDataUrl(url)) {
- // Don't strip anchor as that is a valid part of the URL
- mUrl = url;
- } else {
- mUrl = URLUtil.stripAnchor(url);
- }
mUri = null;
- if (URLUtil.isNetworkUrl(mUrl)) {
+ if (URLUtil.isNetworkUrl(url)) {
+ mUrl = URLUtil.stripAnchor(url);
try {
mUri = new WebAddress(mUrl);
} catch (ParseException e) {
e.printStackTrace();
}
+ } else {
+ mUrl = url;
}
}
}
diff --git a/core/java/android/webkit/PluginContentLoader.java b/core/java/android/webkit/PluginContentLoader.java
new file mode 100644
index 0000000..2069599
--- /dev/null
+++ b/core/java/android/webkit/PluginContentLoader.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.net.http.Headers;
+
+import java.io.InputStream;
+import java.util.*;
+
+import org.apache.http.util.CharArrayBuffer;
+
+/**
+ * This class is a concrete implementation of StreamLoader that uses a
+ * PluginData object as the source for the stream.
+ */
+class PluginContentLoader extends StreamLoader {
+
+ private PluginData mData; // Content source
+
+ /**
+ * Constructs a PluginDataLoader for use when loading content from
+ * a plugin.
+ *
+ * @param loadListener LoadListener to pass the content to
+ * @param data PluginData used as the source for the content.
+ */
+ PluginContentLoader(LoadListener loadListener, PluginData data) {
+ super(loadListener);
+ mData = data;
+ }
+
+ @Override
+ protected boolean setupStreamAndSendStatus() {
+ mDataStream = mData.getInputStream();
+ mContentLength = mData.getContentLength();
+ mHandler.status(1, 1, mData.getStatusCode(), "OK");
+ return true;
+ }
+
+ @Override
+ protected void buildHeaders(Headers headers) {
+ // Crate a CharArrayBuffer with an arbitrary initial capacity.
+ CharArrayBuffer buffer = new CharArrayBuffer(100);
+ Iterator<Map.Entry<String, String[]>> responseHeadersIt =
+ mData.getHeaders().entrySet().iterator();
+ while (responseHeadersIt.hasNext()) {
+ Map.Entry<String, String[]> entry = responseHeadersIt.next();
+ // Headers.parseHeader() expects lowercase keys, so keys
+ // such as "Accept-Ranges" will fail to parse.
+ //
+ // UrlInterceptHandler instances supply a mapping of
+ // lowercase key to [ unmodified key, value ], so for
+ // Headers.parseHeader() to succeed, we need to construct
+ // a string using the key (i.e. entry.getKey()) and the
+ // element denoting the header value in the
+ // [ unmodified key, value ] pair (i.e. entry.getValue()[1).
+ //
+ // The reason why UrlInterceptHandler instances supply such a
+ // mapping in the first place is historical. Early versions of
+ // the Gears plugin used java.net.HttpURLConnection, which always
+ // returned headers names as capitalized strings. When these were
+ // fed back into webkit, they failed to parse.
+ //
+ // Mewanwhile, Gears was modified to use Apache HTTP library
+ // instead, so this design is now obsolete. Changing it however,
+ // would require changes to the Gears C++ codebase and QA-ing and
+ // submitting a new binary to the Android tree. Given the
+ // timelines for the next Android release, we will not do this
+ // for now.
+ //
+ // TODO: fix C++ Gears to remove the need for this
+ // design.
+ String keyValue = entry.getKey() + ": " + entry.getValue()[1];
+ buffer.ensureCapacity(keyValue.length());
+ buffer.append(keyValue);
+ // Parse it into the header container.
+ headers.parseHeader(buffer);
+ // Clear the buffer
+ buffer.clear();
+ }
+ }
+}
diff --git a/core/java/android/webkit/PluginData.java b/core/java/android/webkit/PluginData.java
new file mode 100644
index 0000000..2b539fe
--- /dev/null
+++ b/core/java/android/webkit/PluginData.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * This class encapsulates the content generated by a plugin. The
+ * data itself is meant to be loaded into webkit via the
+ * PluginContentLoader class, which needs to be able to construct an
+ * HTTP response. For this, it needs a stream with the response body,
+ * the length of the body, the response headers, and the response
+ * status code. The PluginData class is the container for all these
+ * parts.
+ *
+ */
+public final class PluginData {
+ /**
+ * The content stream.
+ */
+ private InputStream mStream;
+ /**
+ * The content length.
+ */
+ private long mContentLength;
+ /**
+ * The associated HTTP response headers stored as a map of
+ * lowercase header name to [ unmodified header name, header value].
+ * TODO: This design was always a hack. Remove (involves updating
+ * the Gears C++ side).
+ */
+ private Map<String, String[]> mHeaders;
+
+ /**
+ * The index of the header value in the above mapping.
+ */
+ private int mHeaderValueIndex;
+ /**
+ * The associated HTTP response code.
+ */
+ private int mStatusCode;
+
+ /**
+ * Creates a PluginData instance.
+ *
+ * @param stream The stream that supplies content for the plugin.
+ * @param length The length of the plugin content.
+ * @param headers The response headers. Map of
+ * lowercase header name to [ unmodified header name, header value]
+ * @param length The HTTP response status code.
+ */
+ public PluginData(
+ InputStream stream,
+ long length,
+ Map<String, String[]> headers,
+ int code) {
+ mStream = stream;
+ mContentLength = length;
+ mHeaders = headers;
+ mStatusCode = code;
+ }
+
+ /**
+ * Returns the input stream that contains the plugin content.
+ *
+ * @return An InputStream instance with the plugin content.
+ */
+ public InputStream getInputStream() {
+ return mStream;
+ }
+
+ /**
+ * Returns the length of the plugin content.
+ *
+ * @return the length of the plugin content.
+ */
+ public long getContentLength() {
+ return mContentLength;
+ }
+
+ /**
+ * Returns the HTTP response headers associated with the plugin
+ * content.
+ *
+ * @return A Map<String, String[]> containing all headers. The
+ * mapping is 'lowercase header name' to ['unmodified header
+ * name', header value].
+ */
+ public Map<String, String[]> getHeaders() {
+ return mHeaders;
+ }
+
+ /**
+ * Returns the HTTP status code for the response.
+ *
+ * @return The HTTP statue code, e.g 200.
+ */
+ public int getStatusCode() {
+ return mStatusCode;
+ }
+}
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index 8a82411..39806dc 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -113,13 +113,7 @@ import java.util.ArrayList;
// that other applications that use embedded WebViews will properly
// display the text in textfields.
setTextColor(Color.BLACK);
- }
-
- @Override
- protected boolean shouldAdvanceFocusOnEnter() {
- // In the browser, single line textfields use enter as a form submit,
- // so we never want to advance the focus on enter.
- return false;
+ setImeOptions(EditorInfo.IME_ACTION_NONE);
}
@Override
diff --git a/core/java/android/webkit/UrlInterceptHandler.java b/core/java/android/webkit/UrlInterceptHandler.java
index e1c9d61..9216413 100644
--- a/core/java/android/webkit/UrlInterceptHandler.java
+++ b/core/java/android/webkit/UrlInterceptHandler.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.webkit.CacheManager.CacheResult;
+import android.webkit.PluginData;
import java.util.Map;
public interface UrlInterceptHandler {
@@ -29,6 +30,20 @@ public interface UrlInterceptHandler {
* @param url URL string.
* @param headers The headers associated with the request. May be null.
* @return The CacheResult containing the surrogate response.
+ * @Deprecated Use PluginData getPluginData(String url,
+ * Map<String, String> headers); instead
*/
+ @Deprecated
public CacheResult service(String url, Map<String, String> headers);
+
+ /**
+ * Given an URL, returns the PluginData which contains the
+ * surrogate response for the request, or null if the handler is
+ * not interested.
+ *
+ * @param url URL string.
+ * @param headers The headers associated with the request. May be null.
+ * @return The PluginData containing the surrogate response.
+ */
+ public PluginData getPluginData(String url, Map<String, String> headers);
}
diff --git a/core/java/android/webkit/UrlInterceptRegistry.java b/core/java/android/webkit/UrlInterceptRegistry.java
index a218191..6051f29 100644
--- a/core/java/android/webkit/UrlInterceptRegistry.java
+++ b/core/java/android/webkit/UrlInterceptRegistry.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.webkit.CacheManager.CacheResult;
+import android.webkit.PluginData;
import android.webkit.UrlInterceptHandler;
import java.util.Iterator;
@@ -82,17 +83,21 @@ public final class UrlInterceptRegistry {
UrlInterceptHandler handler) {
return getHandlers().remove(handler);
}
-
+
/**
* Given an url, returns the CacheResult of the first
* UrlInterceptHandler interested, or null if none are.
- *
+ *
* @return A CacheResult containing surrogate content.
+ * @Deprecated Use PluginData getPluginData( String url,
+ * Map<String, String> headers) instead.
*/
+ @Deprecated
public static synchronized CacheResult getSurrogate(
String url, Map<String, String> headers) {
- if (urlInterceptDisabled())
+ if (urlInterceptDisabled()) {
return null;
+ }
Iterator iter = getHandlers().listIterator();
while (iter.hasNext()) {
UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
@@ -103,4 +108,27 @@ public final class UrlInterceptRegistry {
}
return null;
}
+
+ /**
+ * Given an url, returns the PluginData of the first
+ * UrlInterceptHandler interested, or null if none are or if
+ * intercepts are disabled.
+ *
+ * @return A PluginData instance containing surrogate content.
+ */
+ public static synchronized PluginData getPluginData(
+ String url, Map<String, String> headers) {
+ if (urlInterceptDisabled()) {
+ return null;
+ }
+ Iterator iter = getHandlers().listIterator();
+ while (iter.hasNext()) {
+ UrlInterceptHandler handler = (UrlInterceptHandler) iter.next();
+ PluginData data = handler.getPluginData(url, headers);
+ if (data != null) {
+ return data;
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5126ef0..91795a3 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -689,7 +689,10 @@ public class WebView extends AbsoluteLayout
return true;
}
- public void onSimpleZoom(boolean zoomIn) {
+ public void onSimpleZoom(boolean zoomIn, int centerX, int centerY) {
+ mZoomCenterX = (float) centerX;
+ mZoomCenterY = (float) centerY;
+
if (zoomIn) {
zoomIn();
} else {
@@ -736,12 +739,12 @@ public class WebView extends AbsoluteLayout
mFocusData.mY = 0;
mScroller = new Scroller(context);
mZoomRingController = new ZoomRingController(context, this);
- mZoomRingController.setResetThumbAutomatically(false);
mZoomRingController.setCallback(mZoomListener);
- mZoomRingController.setZoomRingTrack(
+ mZoomRingController.setTrackDrawable(
com.android.internal.R.drawable.zoom_ring_track_absolute);
- mZoomRingController.setPannerAcceleration(160);
- mZoomRingController.setPannerStartAcceleratingDuration(700);
+ float density = context.getResources().getDisplayMetrics().density;
+ mZoomRingController.setPannerAcceleration((int) (160 * density));
+ mZoomRingController.setPannerStartAcceleratingDuration((int) (700 * density));
createZoomRingOverviewTab();
mZoomButtonsController = new ZoomButtonsController(context, this);
mZoomButtonsController.setOverviewVisible(true);
@@ -760,7 +763,7 @@ public class WebView extends AbsoluteLayout
}
public void onZoom(boolean zoomIn) {
- mZoomListener.onSimpleZoom(zoomIn);
+ mZoomListener.onSimpleZoom(zoomIn, getWidth() / 2, getHeight() / 2);
}
});
}
@@ -4491,6 +4494,14 @@ public class WebView extends AbsoluteLayout
return zoomControls;
}
+ // This used to be the value returned by ViewConfiguration.getTouchSlop().
+ // We pass this to the navigation cache to find where the user clicked.
+ // TouchSlop has been increased for other purposes, but for the
+ // navigation cache it is too big, and may result in clicking the wrong
+ // spot. This is a concern when the cache is out of date, and clicking
+ // finds a node which is wrong but nearby.
+ private static final int NAV_SLOP = 12;
+
private void updateSelection() {
if (mNativeClass == 0) {
return;
@@ -4498,7 +4509,7 @@ public class WebView extends AbsoluteLayout
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- int contentSize = ViewConfiguration.getTouchSlop();
+ int contentSize = NAV_SLOP;
Rect rect = new Rect(contentX - contentSize, contentY - contentSize,
contentX + contentSize, contentY + contentSize);
// If we were already focused on a textfield, update its cache.
@@ -4513,7 +4524,7 @@ public class WebView extends AbsoluteLayout
View v = mTextEntry;
int x = viewToContent((v.getLeft() + v.getRight()) >> 1);
int y = viewToContent((v.getTop() + v.getBottom()) >> 1);
- int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ int contentSize = NAV_SLOP;
nativeMotionUp(x, y, contentSize, true);
}
}
@@ -4526,7 +4537,7 @@ public class WebView extends AbsoluteLayout
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ int contentSize = NAV_SLOP;
if (nativeMotionUp(contentX, contentY, contentSize, true)) {
if (mLogEvent) {
Checkin.updateStats(mContext.getContentResolver(),
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
index 2a5cbe9..43104bf 100644
--- a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -25,16 +25,14 @@
package android.webkit.gears;
-import android.net.http.Headers;
import android.util.Log;
-import android.webkit.CacheManager;
import android.webkit.CacheManager.CacheResult;
import android.webkit.Plugin;
+import android.webkit.PluginData;
import android.webkit.UrlInterceptRegistry;
import android.webkit.UrlInterceptHandler;
import android.webkit.WebView;
-import org.apache.http.impl.cookie.DateUtils;
import org.apache.http.util.CharArrayBuffer;
import java.io.*;
@@ -53,12 +51,6 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
private static final String LOG_TAG = "Gears-J";
/** Buffer size for reading/writing streams. */
private static final int BUFFER_SIZE = 4096;
- /**
- * Number of milliseconds to expire LocalServer temporary entries in
- * the browser's cache. Somewhat arbitrarily chosen as a compromise
- * between being a) long enough not to expire during page load and
- * b) short enough to evict entries during a session. */
- private static final int CACHE_EXPIRY_MS = 60000; // 1 minute.
/** Enable/disable all logging in this class. */
private static boolean logEnabled = false;
/** The unmodified (case-sensitive) key in the headers map is the
@@ -140,6 +132,8 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
private String encoding;
// The stream which contains the body when read().
private InputStream inputStream;
+ // The length of the content body.
+ private long contentLength;
/**
* Initialize members using an in-memory array to return the body.
@@ -160,6 +154,7 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
this.mimeType = mimeType;
this.encoding = encoding;
// Setup a stream to read out of the byte array.
+ this.contentLength = body.length;
this.inputStream = new ByteArrayInputStream(body);
}
@@ -185,7 +180,9 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
this.encoding = encoding;
try {
// Setup a stream to read out of a file on disk.
- this.inputStream = new FileInputStream(new File(path));
+ File file = new File(path);
+ this.contentLength = file.length();
+ this.inputStream = new FileInputStream(file);
return true;
} catch (java.io.FileNotFoundException ex) {
log("File not found: " + path);
@@ -274,6 +271,13 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
public InputStream getInputStream() {
return inputStream;
}
+
+ /**
+ * @return The length of the response body.
+ */
+ public long getContentLength() {
+ return contentLength;
+ }
}
/**
@@ -319,44 +323,32 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
UrlInterceptRegistry.unregisterHandler(this);
}
- /**
- * Copy the entire InputStream to OutputStream.
- * @param inputStream The stream to read from.
- * @param outputStream The stream to write to.
- * @return True if the entire stream copied successfully, false on error.
- */
- private boolean copyStream(InputStream inputStream,
- OutputStream outputStream) {
- try {
- // Temporary buffer to copy stream through.
- byte[] buf = new byte[BUFFER_SIZE];
- for (;;) {
- // Read up to BUFFER_SIZE bytes.
- int bytes = inputStream.read(buf);
- if (bytes < 0) {
- break;
- }
- // Write the number of bytes we just read.
- outputStream.write(buf, 0, bytes);
- }
- } catch (IOException ex) {
- log("Got IOException copying stream: " + ex);
- return false;
+ /**
+ * Given an URL, returns the CacheResult which contains the
+ * surrogate response for the request, or null if the handler is
+ * not interested.
+ *
+ * @param url URL string.
+ * @param headers The headers associated with the request. May be null.
+ * @return The CacheResult containing the surrogate response.
+ * @Deprecated Use PluginData getPluginData(String url,
+ * Map<String, String> headers); instead
+ */
+ @Deprecated
+ public CacheResult service(String url, Map<String, String> headers) {
+ throw new UnsupportedOperationException("unimplemented");
}
- return true;
- }
/**
- * Given an URL, returns a CacheResult which contains the response
- * for the request. This implements the UrlInterceptHandler interface.
+ * Given an URL, returns a PluginData instance which contains the
+ * response for the request. This implements the UrlInterceptHandler
+ * interface.
*
- * @param url The fully qualified URL being requested.
+ * @param url The fully qualified URL being requested.
* @param requestHeaders The request headers for this URL.
- * @return If a response can be crafted, a CacheResult initialized
- * to return the surrogate response. If this URL cannot
- * be serviced, returns null.
+ * @return a PluginData object.
*/
- public CacheResult service(String url, Map<String, String> requestHeaders) {
+ public PluginData getPluginData(String url, Map<String, String> requestHeaders) {
// Thankfully the browser does call us with case-sensitive
// headers. We just need to map it case-insensitive.
Map<String, String[]> lowercaseRequestHeaders =
@@ -374,86 +366,10 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
// No result for this URL.
return null;
}
- // Translate the ServiceResponse to a CacheResult.
- // Translate http -> gears, https -> gearss, so we don't overwrite
- // existing entries.
- String gearsUrl = "gears" + url.substring("http".length());
- // Set the result to expire, so that entries don't pollute the
- // browser's cache for too long.
- long now_ms = System.currentTimeMillis();
- String expires = DateUtils.formatDate(new Date(now_ms + CACHE_EXPIRY_MS));
- 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.
- Headers headers = new Headers();
- Iterator<Map.Entry<String, String[]>> responseHeadersIt =
- response.getResponseHeaders().entrySet().iterator();
- while (responseHeadersIt.hasNext()) {
- Map.Entry<String, String[]> entry = responseHeadersIt.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.
- headers.parseHeader(buffer);
- }
- CacheResult cacheResult = CacheManager.createCacheFile(
- gearsUrl,
- response.getStatusCode(),
- headers,
- response.getMimeType(),
- true); // forceCache
-
- if (cacheResult == null) {
- // With the no-cache policy we could end up
- // with a null result
- return null;
- }
-
- // Set encoding if specified.
- String encoding = response.getEncoding();
- if (encoding != null) {
- cacheResult.setEncoding(encoding);
- }
- // Copy the response body to the CacheResult. This handles all
- // combinations of memory vs on-disk on both sides.
- InputStream inputStream = response.getInputStream();
- OutputStream outputStream = cacheResult.getOutputStream();
- boolean copied = copyStream(inputStream, outputStream);
- // Close the input and output streams to relinquish their
- // resources earlier.
- try {
- inputStream.close();
- } catch (IOException ex) {
- log("IOException closing InputStream: " + ex);
- copied = false;
- }
- try {
- outputStream.close();
- } catch (IOException ex) {
- log("IOException closing OutputStream: " + ex);
- copied = false;
- }
- if (!copied) {
- log("copyStream of local result failed");
- return null;
- }
- // Save the entry into the browser's cache.
- CacheManager.saveCacheFile(gearsUrl, cacheResult);
- // Get it back from the cache, this time properly initialized to
- // be used for input.
- cacheResult = CacheManager.getCacheFile(gearsUrl, null);
- if (cacheResult != null) {
- log("Returning surrogate result");
- return cacheResult;
- } else {
- // Not an expected condition, but handle gracefully. Perhaps out
- // of memory or disk?
- Log.e(LOG_TAG, "Lost CacheResult between save and get. Can't serve.\n");
- return null;
- }
+ return new PluginData(response.getInputStream(),
+ response.getContentLength(),
+ response.getResponseHeaders(),
+ response.getStatusCode());
}
/**
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 9da78d0..d72570a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2766,6 +2766,7 @@ 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) {
+ createTextFilter(true);
positionPopup(false);
// Make sure we get focus if we are showing the popup
checkFocus();
@@ -2913,8 +2914,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mPopup == null) {
Context c = getContext();
PopupWindow p = new PopupWindow(c);
- LayoutInflater layoutInflater = (LayoutInflater) c
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LayoutInflater layoutInflater = (LayoutInflater)
+ c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mTextFilter = (EditText) layoutInflater.inflate(
com.android.internal.R.layout.typing_filter, null);
mTextFilter.addTextChangedListener(this);
@@ -3136,6 +3137,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
int viewType;
+ /**
+ * When this boolean is set, the view has been added to the AbsListView
+ * at least once. It is used to know whether headers/footers have already
+ * been added to the list view and whether they should be treated as
+ * recycled views or not.
+ */
+ boolean recycledHeaderFooter;
+
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
@@ -3269,7 +3278,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (lp != null && lp.viewType != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
- activeViews[i] = getChildAt(i);
+ activeViews[i] = child;
}
}
}
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 57e21e4..0a552e8 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -62,6 +62,8 @@ class FastScroller {
private int mVisibleItem;
private Paint mPaint;
private int mListOffset;
+ private int mItemCount = -1;
+ private boolean mLongList;
private Object [] mSections;
private String mSectionText;
@@ -219,8 +221,12 @@ class FastScroller {
void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
- // Are there enough pages to require fast scroll?
- if (visibleItemCount > 0 && totalItemCount / visibleItemCount < MIN_PAGES) {
+ // Are there enough pages to require fast scroll? Recompute only if total count changes
+ if (mItemCount != totalItemCount && visibleItemCount > 0) {
+ mItemCount = totalItemCount;
+ mLongList = mItemCount / visibleItemCount >= MIN_PAGES;
+ }
+ if (!mLongList) {
if (mState != STATE_NONE) {
setState(STATE_NONE);
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 6bbf062..11fab8f 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -929,6 +929,7 @@ public class GridView extends AbsListView {
if (p == null) {
p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ child.setLayoutParams(p);
}
p.viewType = mAdapter.getItemViewType(0);
@@ -1328,11 +1329,8 @@ public class GridView extends AbsListView {
*/
@Override
void setSelectionInt(int position) {
- mBlockLayoutRequests = true;
setNextSelectedPositionInt(position);
layoutChildren();
-
- mBlockLayoutRequests = false;
}
@Override
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index a4523b9..480b0b8 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -333,6 +333,12 @@ public class ImageView extends View {
resizeFromDrawable();
}
+ /**
+ * Sets the image level, when it is constructed from a
+ * {@link android.graphics.drawable.LevelListDrawable}.
+ *
+ * @param level The new level for the image.
+ */
@android.view.RemotableViewMethod
public void setImageLevel(int level) {
mLevel = level;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 404a4ee..c2f3a85 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -26,7 +26,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
-import android.util.SparseArray;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -1040,6 +1039,7 @@ public class ListView extends AbsListView {
if (p == null) {
p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
+ child.setLayoutParams(p);
}
p.viewType = mAdapter.getItemViewType(position);
@@ -1320,6 +1320,8 @@ public class ListView extends AbsListView {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (!blockLayoutRequests) {
mBlockLayoutRequests = true;
+ } else {
+ return;
}
try {
@@ -1429,15 +1431,12 @@ public class ListView extends AbsListView {
// we can remember the focused view to restore after relayout if the
// data hasn't changed, or if the focused position is a header or footer
if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
- focusLayoutRestoreDirectChild = getFocusedChild();
- if (focusLayoutRestoreDirectChild != null) {
-
- // remember the specific view that had focus
- focusLayoutRestoreView = findFocus();
- if (focusLayoutRestoreView != null) {
- // tell it we are going to mess with it
- focusLayoutRestoreView.onStartTemporaryDetach();
- }
+ focusLayoutRestoreDirectChild = focusedChild;
+ // remember the specific view that had focus
+ focusLayoutRestoreView = findFocus();
+ if (focusLayoutRestoreView != null) {
+ // tell it we are going to mess with it
+ focusLayoutRestoreView.onStartTemporaryDetach();
}
}
requestFocus();
@@ -1657,9 +1656,12 @@ public class ListView extends AbsListView {
}
p.viewType = mAdapter.getItemViewType(position);
- if (recycled) {
+ if (recycled || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
attachViewToParent(child, flowDown ? -1 : 0, p);
} else {
+ if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+ p.recycledHeaderFooter = true;
+ }
addViewInLayout(child, flowDown ? -1 : 0, p, true);
}
@@ -1766,10 +1768,8 @@ public class ListView extends AbsListView {
*/
@Override
void setSelectionInt(int position) {
- mBlockLayoutRequests = true;
setNextSelectedPositionInt(position);
layoutChildren();
- mBlockLayoutRequests = false;
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 080f3de..3f4912f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -33,6 +33,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.Message;
import android.text.BoringLayout;
@@ -234,6 +235,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private CharWrapper mCharWrapper = null;
private boolean mSelectionMoved = false;
+ private boolean mTouchFocusSelectedAll = false;
private Marquee mMarquee;
private boolean mRestartMarquee;
@@ -837,6 +839,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (password) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
typefaceIndex = MONOSPACE;
+ } else if ((mInputType&(EditorInfo.TYPE_MASK_CLASS
+ |EditorInfo.TYPE_MASK_VARIATION))
+ == (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+ typefaceIndex = MONOSPACE;
}
setTypefaceByIndex(typefaceIndex, styleIndex);
@@ -2244,6 +2251,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int selEnd;
CharSequence text;
boolean frozenWithFocus;
+ CharSequence error;
SavedState(Parcelable superState) {
super(superState);
@@ -2256,6 +2264,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
out.writeInt(selEnd);
out.writeInt(frozenWithFocus ? 1 : 0);
TextUtils.writeToParcel(text, out, flags);
+
+ if (error == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(1);
+ TextUtils.writeToParcel(error, out, flags);
+ }
}
@Override
@@ -2286,6 +2301,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
selEnd = in.readInt();
frozenWithFocus = (in.readInt() != 0);
text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+ if (in.readInt() != 0) {
+ error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
}
}
@@ -2338,6 +2357,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.frozenWithFocus = true;
}
+ ss.error = mError;
+
return ss;
}
@@ -2383,6 +2404,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
+
+ if (ss.error != null) {
+ setError(ss.error);
+ }
}
/**
@@ -2799,8 +2824,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public void setInputType(int type) {
setInputType(type, false);
- final boolean isPassword = (type&(EditorInfo.TYPE_MASK_CLASS
- |EditorInfo.TYPE_MASK_VARIATION))
+ final int variation = type&(EditorInfo.TYPE_MASK_CLASS
+ |EditorInfo.TYPE_MASK_VARIATION);
+ final boolean isPassword = variation
== (EditorInfo.TYPE_CLASS_TEXT
|EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
boolean forceUpdate = false;
@@ -2809,8 +2835,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setTypefaceByIndex(MONOSPACE, 0);
} else if (mTransformation == PasswordTransformationMethod.getInstance()) {
// We need to clean up if we were previously in password mode.
- setTypefaceByIndex(-1, -1);
+ if (variation != (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
+ setTypefaceByIndex(-1, -1);
+ }
forceUpdate = true;
+ } else if (variation == (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) {
+ setTypefaceByIndex(MONOSPACE, 0);
}
boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS
@@ -2999,23 +3031,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
-
- if (actionCode == EditorInfo.IME_ACTION_NEXT &&
- (ict != null || !shouldAdvanceFocusOnEnter())) {
- // This is the default handling for the NEXT action, to advance
- // focus. Note that for backwards compatibility we don't do this
+ if (ict != null || !shouldAdvanceFocusOnEnter()) {
+ // This is the handling for some default action.
+ // Note that for backwards compatibility we don't do this
// default handling if explicit ime options have not been given,
- // and we do not advance by default on an enter key -- in that
- // case, we want to turn this into the normal enter key codes that
- // an app may be expecting.
- View v = focusSearch(FOCUS_DOWN);
- if (v != null) {
- if (!v.requestFocus(FOCUS_DOWN)) {
- throw new IllegalStateException("focus search returned a view " +
- "that wasn't able to take focus!");
+ // to instead turn this into the normal enter key codes that an
+ // app may be expecting.
+ if (actionCode == EditorInfo.IME_ACTION_NEXT) {
+ View v = focusSearch(FOCUS_DOWN);
+ if (v != null) {
+ if (!v.requestFocus(FOCUS_DOWN)) {
+ throw new IllegalStateException("focus search returned a view " +
+ "that wasn't able to take focus!");
+ }
+ }
+ return;
+
+ } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
}
- return;
}
Handler h = getHandler();
@@ -3998,7 +4035,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* but also in mail addresses and subjects which will display on multiple
* lines but where it doesn't make sense to insert newlines.
*/
- protected boolean shouldAdvanceFocusOnEnter() {
+ private boolean shouldAdvanceFocusOnEnter() {
if (mInput == null) {
return false;
}
@@ -4192,6 +4229,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
super.onKeyUp(keyCode, event);
return true;
+ } else if ((event.getFlags()
+ & KeyEvent.FLAG_SOFT_KEYBOARD) != 0) {
+ // No target for next focus, but make sure the IME
+ // if this came from it.
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
}
}
@@ -4234,9 +4279,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// An action has not been set, but the enter key will move to
// the next focus, so set the action to that.
outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
- if (!shouldAdvanceFocusOnEnter()) {
- outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
- }
+ } else {
+ // An action has not been set, and there is no focus to move
+ // to, so let's just supply a "done" action.
+ outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
+ }
+ if (!shouldAdvanceFocusOnEnter()) {
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
}
outAttrs.hintText = mHint;
@@ -5977,6 +6026,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mSelectAllOnFocus) {
Selection.setSelection((Spannable) mText, 0, mText.length());
+ mTouchFocusSelectedAll = true;
}
if (selMoved && selStart >= 0 && selEnd >= 0) {
@@ -6078,12 +6128,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ class CommitSelectionReceiver extends ResultReceiver {
+ int mNewStart;
+ int mNewEnd;
+
+ CommitSelectionReceiver() {
+ super(getHandler());
+ }
+
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode != InputMethodManager.RESULT_SHOWN) {
+ Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
+ }
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
- final boolean superResult = super.onTouchEvent(event);
-
final int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN) {
+ // Reset this state; it will be re-set if super.onTouchEvent
+ // causes focus to move to the view.
+ mTouchFocusSelectedAll = false;
+ }
+ final boolean superResult = super.onTouchEvent(event);
+
/*
* Don't handle the release after a long press, because it will
* move the selection away from whatever the menu action was
@@ -6100,17 +6170,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mScrolled = false;
}
- boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event);
+ boolean handled = false;
+
+ int oldSelStart = Selection.getSelectionStart(mText);
+ int oldSelEnd = Selection.getSelectionEnd(mText);
+
+ handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
if (mText instanceof Editable && onCheckIsTextEditor()) {
if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(this, 0);
+
+ // This is going to be gross... if tapping on the text view
+ // causes the IME to be displayed, we don't want the selection
+ // to change. But the selection has already changed, and
+ // we won't know right away whether the IME is getting
+ // displayed, so...
+
+ int newSelStart = Selection.getSelectionStart(mText);
+ int newSelEnd = Selection.getSelectionEnd(mText);
+ CommitSelectionReceiver csr = null;
+ if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
+ csr = new CommitSelectionReceiver();
+ csr.mNewStart = newSelStart;
+ csr.mNewEnd = newSelEnd;
+ }
+
+ if (imm.showSoftInput(this, 0, csr) && csr != null) {
+ // The IME might get shown -- revert to the old
+ // selection, and change to the new when we finally
+ // find out of it is okay.
+ Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
+ handled = true;
+ }
}
}
- if (moved) {
+ if (handled) {
return true;
}
}
@@ -6118,6 +6215,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return superResult;
}
+ /**
+ * Returns true, only while processing a touch gesture, if the initial
+ * touch down event caused focus to move to the text view and as a result
+ * it selected all of its text.
+ */
+ public boolean didTouchFocusSelectAll() {
+ return mTouchFocusSelectedAll;
+ }
+
@Override
public void cancelLongPress() {
super.cancelLongPress();
diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java
index a5a867b..83a1225 100644
--- a/core/java/android/widget/ZoomRing.java
+++ b/core/java/android/widget/ZoomRing.java
@@ -1,9 +1,26 @@
+/*
+ * Copyright (C) 2009 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.widget;
import com.android.internal.R;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RotateDrawable;
@@ -18,36 +35,47 @@ import android.view.View;
import android.view.ViewConfiguration;
/**
+ * A view that has a draggable thumb on a circle.
+ *
* @hide
*/
public class ZoomRing extends View {
-
- // TODO: move to ViewConfiguration?
- static final int DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
- // TODO: get from theme
private static final String TAG = "ZoomRing";
// TODO: Temporary until the trail is done
private static final boolean DRAW_TRAIL = false;
- // TODO: xml
- private static final int THUMB_DISTANCE = 63;
-
- /** To avoid floating point calculations, we multiply radians by this value. */
+ /**
+ * To avoid floating point calculations and int round-offs, we multiply
+ * radians by this value.
+ */
public static final int RADIAN_INT_MULTIPLIER = 10000;
+ /** The allowable margin of error when comparing two angles. */
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 DOUBLE_TAP_DISMISS_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+ private final int mTouchSlop;
+
+ /** The slop when the user is grabbing the thumb. */
private static final int THUMB_GRAB_SLOP = PI_INT_MULTIPLIED / 8;
+ /** The slop until a user starts dragging the thumb. */
private static final int THUMB_DRAG_SLOP = PI_INT_MULTIPLIED / 12;
+ /** The distance (in px) from the center of the ring to the center of the thumb. */
+ private int mThumbDistance;
+
+ /** The angle on a unit circle that is considered to be the zoom ring's 0 degree. */
+ private int mZeroAngle = HALF_PI_INT_MULTIPLIED * 3;
+
/**
+ * The maximum delta angle that the thumb can move. The primary use is to
+ * ensure that when a user taps on the ring, the movement to reach that
+ * target angle is not ambiguous (for example, if the thumb is at 0 and he
+ * taps 180, should the thumb go clockwise or counterclockwise?
+ * <p>
* Includes error because we compare this to the result of
* getDelta(getClosestTickeAngle(..), oldAngle) which ends up having some
* rounding error.
@@ -55,58 +83,93 @@ public class ZoomRing extends View {
private static final int MAX_ABS_JUMP_DELTA_ANGLE = (2 * PI_INT_MULTIPLIED / 3) +
RADIAN_INT_ERROR;
- /** The cached X of our center. */
+ /** The cached X of the zoom ring's center (in zoom ring coordinates). */
private int mCenterX;
- /** The cached Y of our center. */
+ /** The cached Y of the zoom ring's center (in zoom ring coordinates). */
private int mCenterY;
/** The angle of the thumb (in int radians) */
private int mThumbAngle;
+ /** The cached width/2 of the zoom ring. */
private int mThumbHalfWidth;
+ /** The cached height/2 of the zoom ring. */
private int mThumbHalfHeight;
+ /**
+ * The bound for the thumb's movement when it is being dragged clockwise.
+ * Can be Integer.MIN_VALUE if there is no bound in this direction.
+ */
private int mThumbCwBound = Integer.MIN_VALUE;
+ /**
+ * The bound for the thumb's movement when it is being dragged
+ * counterclockwise. Can be Integer.MIN_VALUE if there is no bound in this
+ * direction.
+ */
private int mThumbCcwBound = Integer.MIN_VALUE;
+
+ /**
+ * Whether to enforce the maximum absolute jump delta. See
+ * {@link #MAX_ABS_JUMP_DELTA_ANGLE}.
+ */
private boolean mEnforceMaxAbsJump = true;
/** The inner radius of the track. */
- private int mBoundInnerRadiusSquared = 0;
+ private int mTrackInnerRadius;
+ /** Cached square of the inner radius of the track. */
+ private int mTrackInnerRadiusSquared;
/** The outer radius of the track. */
- private int mBoundOuterRadiusSquared = Integer.MAX_VALUE;
+ private int mTrackOuterRadius;
+ /** Cached square of the outer radius of the track. */
+ private int mTrackOuterRadiusSquared;
+ /** The raw X of where the widget previously was located. */
private int mPreviousWidgetDragX;
+ /** The raw Y of where the widget previously was located. */
private int mPreviousWidgetDragY;
+ /** Whether the thumb should be visible. */
private boolean mThumbVisible = true;
+
+ /** The drawable for the thumb. */
private Drawable mThumbDrawable;
/** Shown beneath the thumb if we can still zoom in. */
- private Drawable mThumbPlusArrowDrawable;
+ private Drawable mZoomInArrowDrawable;
/** Shown beneath the thumb if we can still zoom out. */
- private Drawable mThumbMinusArrowDrawable;
+ private Drawable mZoomOutArrowDrawable;
+
+ /** @see #mThumbArrowsToDraw */
private static final int THUMB_ARROW_PLUS = 1 << 0;
+ /** @see #mThumbArrowsToDraw */
private static final int THUMB_ARROW_MINUS = 1 << 1;
/** Bitwise-OR of {@link #THUMB_ARROW_MINUS} and {@link #THUMB_ARROW_PLUS} */
private int mThumbArrowsToDraw;
+
+ /** The duration for the thumb arrows fading out */
private static final int THUMB_ARROWS_FADE_DURATION = 300;
+ /** The time when the fade out started. */
private long mThumbArrowsFadeStartTime;
+ /** The current alpha for the thumb arrows. */
private int mThumbArrowsAlpha = 255;
- private static final int THUMB_PLUS_MINUS_DISTANCE = 69;
- private static final int THUMB_PLUS_MINUS_OFFSET_ANGLE = TWO_PI_INT_MULTIPLIED / 11;
+ /** The distance from the center to the zoom arrow hints (usually plus and minus). */
+ private int mZoomArrowHintDistance;
+ /** The offset angle from the thumb angle to draw the zoom arrow hints. */
+ private int mZoomArrowHintOffsetAngle = TWO_PI_INT_MULTIPLIED / 11;
/** Drawn (without rotation) on top of the arrow. */
- private Drawable mThumbPlusDrawable;
+ private Drawable mZoomInArrowHintDrawable;
/** Drawn (without rotation) on top of the arrow. */
- private Drawable mThumbMinusDrawable;
+ private Drawable mZoomOutArrowHintDrawable;
+ /** Zoom ring is just chillin' */
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_AFTER_JUMP = 5;
+ /** User is dragging the thumb. */
private static final int MODE_DRAG_THUMB = 1;
/**
* User has his finger down, but we are waiting for him to pass the touch
@@ -114,51 +177,65 @@ public class ZoomRing extends View {
* show the movable hint.
*/
private static final int MODE_WAITING_FOR_MOVE_ZOOM_RING = 4;
+ /** User is moving the zoom ring. */
private static final int MODE_MOVE_ZOOM_RING = 2;
+ /** User is dragging the thumb via tap-drag. */
private static final int MODE_TAP_DRAG = 3;
/** Ignore the touch interaction until the user touches the thumb again. */
private static final int MODE_IGNORE_UNTIL_TOUCHES_THUMB = 6;
+ /** The current mode of interaction. */
private int mMode;
-
/** Records the last mode the user was in. */
private int mPreviousMode;
-
+
+ /** The previous time of the up-touch on the center. */
private long mPreviousCenterUpTime;
+ /** The previous X of down-touch. */
private int mPreviousDownX;
+ /** The previous Y of down-touch. */
private int mPreviousDownY;
- private int mWaitingForDragThumbDownAngle;
+ /** The angle where the user first grabbed the thumb. */
+ private int mInitialGrabThumbAngle;
+ /** The callback. */
private OnZoomRingCallback mCallback;
- private int mPreviousCallbackAngle;
- private int mCallbackThreshold = Integer.MAX_VALUE;
+ /** The tick angle that we previously called back with. */
+ private int mPreviousCallbackTickAngle;
+ /** The delta angle between ticks. A tick is a callback point. */
+ private int mTickDelta = Integer.MAX_VALUE;
/** If the user drags to within __% of a tick, snap to that tick. */
- private int mFuzzyCallbackThreshold = Integer.MAX_VALUE;
+ private int mFuzzyTickDelta = Integer.MAX_VALUE;
- private boolean mResetThumbAutomatically = true;
+ /** The angle where the thumb is officially starting to be dragged. */
private int mThumbDragStartAngle;
- private final int mTouchSlop;
-
+ /** The drawable for the zoom trail. */
private Drawable mTrail;
+ /** The accumulated angle for the trail. */
private double mAcculumalatedTrailAngle;
+ /** The animation-step tracker for scrolling the thumb to a particular position. */
private Scroller mThumbScroller;
+ /** Whether to ever vibrate when passing a tick. */
private boolean mVibration = true;
- private static final int MSG_THUMB_SCROLLER_TICK = 1;
- private static final int MSG_THUMB_ARROWS_FADE_TICK = 2;
+ /** The drawable used to hint that this can pan its owner. */
+ private Drawable mPanningArrowsDrawable;
+
+ private static final int MSG_THUMB_SCROLLER_STEP = 1;
+ private static final int MSG_THUMB_ARROWS_FADE_STEP = 2;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_THUMB_SCROLLER_TICK:
- onThumbScrollerTick();
+ case MSG_THUMB_SCROLLER_STEP:
+ onThumbScrollerStep();
break;
- case MSG_THUMB_ARROWS_FADE_TICK:
- onThumbArrowsFadeTick();
+ case MSG_THUMB_ARROWS_FADE_STEP:
+ onThumbArrowsFadeStep();
break;
}
}
@@ -170,50 +247,64 @@ public class ZoomRing extends View {
ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
mTouchSlop = viewConfiguration.getScaledTouchSlop();
- // TODO get drawables from style instead
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ZoomRing, defStyle, 0);
+ mThumbDistance = (int) a.getDimension(R.styleable.ZoomRing_thumbDistance, 0);
+ setTrackRadii(
+ (int) a.getDimension(R.styleable.ZoomRing_trackInnerRadius, 0),
+ (int) a.getDimension(R.styleable.ZoomRing_trackOuterRadius, Integer.MAX_VALUE));
+ mThumbDrawable = a.getDrawable(R.styleable.ZoomRing_thumbDrawable);
+ mZoomInArrowDrawable = a.getDrawable(R.styleable.ZoomRing_zoomInArrowDrawable);
+ mZoomOutArrowDrawable = a.getDrawable(R.styleable.ZoomRing_zoomOutArrowDrawable);
+ mZoomInArrowHintDrawable = a.getDrawable(R.styleable.ZoomRing_zoomInArrowHintDrawable);
+ mZoomOutArrowHintDrawable = a.getDrawable(R.styleable.ZoomRing_zoomOutArrowHintDrawable);
+ mZoomArrowHintDistance =
+ (int) a.getDimension(R.styleable.ZoomRing_zoomArrowHintDistance, 0);
+ mZoomArrowHintOffsetAngle =
+ (int) (a.getInteger(R.styleable.ZoomRing_zoomArrowHintOffsetAngle, 0)
+ * TWO_PI_INT_MULTIPLIED / 360);
+ mPanningArrowsDrawable = a.getDrawable(R.styleable.ZoomRing_panningArrowsDrawable);
+ a.recycle();
+
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();
- mThumbPlusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_plus);
- mThumbMinusDrawable = res.getDrawable(R.drawable.zoom_ring_thumb_minus);
if (DRAW_TRAIL) {
+ // TODO get drawables from style instead
mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate();
}
- // TODO: add padding to drawable
- setBackgroundResource(R.drawable.zoom_ring_track);
- // TODO get from style
- setRingBounds(43, Integer.MAX_VALUE);
-
mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2;
mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2;
- setCallbackThreshold(PI_INT_MULTIPLIED / 6);
+ setTickDelta(PI_INT_MULTIPLIED / 6);
}
public ZoomRing(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, com.android.internal.R.attr.zoomRingStyle);
}
public ZoomRing(Context context) {
this(context, null);
}
+ public void setTrackDrawable(Drawable drawable) {
+ setBackgroundDrawable(drawable);
+ }
+
public void setCallback(OnZoomRingCallback callback) {
mCallback = callback;
}
- // TODO: rename
- public void setCallbackThreshold(int callbackThreshold) {
- mCallbackThreshold = callbackThreshold;
- mFuzzyCallbackThreshold = (int) (callbackThreshold * 0.65f);
+ /**
+ * Sets the distance between ticks. This will be used as a callback threshold.
+ *
+ * @param angle The angle between ticks.
+ */
+ public void setTickDelta(int angle) {
+ mTickDelta = angle;
+ mFuzzyTickDelta = (int) (angle * 0.65f);
}
- public void setVibration(boolean vibrate) {
- mVibration = vibrate;
+ public void setVibration(boolean vibration) {
+ mVibration = vibration;
}
public void setThumbVisible(boolean thumbVisible) {
@@ -223,28 +314,42 @@ public class ZoomRing extends View {
}
}
- // TODO: from XML too
- public void setRingBounds(int innerRadius, int outerRadius) {
- mBoundInnerRadiusSquared = innerRadius * innerRadius;
- if (mBoundInnerRadiusSquared < innerRadius) {
+ public Drawable getPanningArrowsDrawable() {
+ return mPanningArrowsDrawable;
+ }
+
+ public void setTrackRadii(int innerRadius, int outerRadius) {
+ mTrackInnerRadius = innerRadius;
+ mTrackOuterRadius = outerRadius;
+
+ mTrackInnerRadiusSquared = innerRadius * innerRadius;
+ if (mTrackInnerRadiusSquared < innerRadius) {
// Prevent overflow
- mBoundInnerRadiusSquared = Integer.MAX_VALUE;
+ mTrackInnerRadiusSquared = Integer.MAX_VALUE;
}
- mBoundOuterRadiusSquared = outerRadius * outerRadius;
- if (mBoundOuterRadiusSquared < outerRadius) {
+ mTrackOuterRadiusSquared = outerRadius * outerRadius;
+ if (mTrackOuterRadiusSquared < outerRadius) {
// Prevent overflow
- mBoundOuterRadiusSquared = Integer.MAX_VALUE;
+ mTrackOuterRadiusSquared = Integer.MAX_VALUE;
}
}
+ public int getTrackInnerRadius() {
+ return mTrackInnerRadius;
+ }
+
+ public int getTrackOuterRadius() {
+ return mTrackOuterRadius;
+ }
+
public void setThumbClockwiseBound(int angle) {
if (angle < 0) {
mThumbCwBound = Integer.MIN_VALUE;
} else {
mThumbCwBound = getClosestTickAngle(angle);
}
- setEnforceMaxAbsJump();
+ updateEnforceMaxAbsJump();
}
public void setThumbCounterclockwiseBound(int angle) {
@@ -253,14 +358,14 @@ public class ZoomRing extends View {
} else {
mThumbCcwBound = getClosestTickAngle(angle);
}
- setEnforceMaxAbsJump();
+ updateEnforceMaxAbsJump();
}
- private void setEnforceMaxAbsJump() {
+ private void updateEnforceMaxAbsJump() {
// 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;
+ mThumbCcwBound == Integer.MIN_VALUE || mThumbCwBound == Integer.MIN_VALUE;
}
public int getThumbAngle() {
@@ -269,7 +374,7 @@ public class ZoomRing extends View {
public void setThumbAngle(int angle) {
angle = getValidAngle(angle);
- mPreviousCallbackAngle = getClosestTickAngle(angle);
+ mPreviousCallbackTickAngle = getClosestTickAngle(angle);
setThumbAngleAuto(angle, false, false);
}
@@ -299,9 +404,9 @@ public class ZoomRing extends View {
mThumbAngle = angle;
int unoffsetAngle = angle + mZeroAngle;
int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) *
- THUMB_DISTANCE) + mCenterX;
+ mThumbDistance) + mCenterX;
int thumbCenterY = (int) (Math.sin(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) *
- THUMB_DISTANCE) * -1 + mCenterY;
+ mThumbDistance) * -1 + mCenterY;
mThumbDrawable.setBounds(thumbCenterX - mThumbHalfWidth,
thumbCenterY - mThumbHalfHeight,
@@ -356,7 +461,7 @@ public class ZoomRing extends View {
duration = getAnimationDuration(deltaAngle);
}
mThumbScroller.startScroll(startAngle, 0, deltaAngle, 0, duration);
- onThumbScrollerTick();
+ onThumbScrollerStep();
}
private int getAnimationDuration(int deltaAngle) {
@@ -364,10 +469,10 @@ public class ZoomRing extends View {
return 300 + deltaAngle * 300 / RADIAN_INT_MULTIPLIER;
}
- private void onThumbScrollerTick() {
+ private void onThumbScrollerStep() {
if (!mThumbScroller.computeScrollOffset()) return;
setThumbAngleInt(getThumbScrollerAngle());
- mHandler.sendEmptyMessage(MSG_THUMB_SCROLLER_TICK);
+ mHandler.sendEmptyMessage(MSG_THUMB_SCROLLER_STEP);
}
private int getThumbScrollerAngle() {
@@ -375,16 +480,10 @@ public class ZoomRing extends View {
}
public void resetThumbAngle() {
- if (mResetThumbAutomatically) {
- mPreviousCallbackAngle = 0;
- setThumbAngleInt(0);
- }
+ mPreviousCallbackTickAngle = 0;
+ setThumbAngleInt(0);
}
- public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
- mResetThumbAutomatically = resetThumbAutomatically;
- }
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
@@ -411,14 +510,12 @@ public class ZoomRing extends View {
}
// These drawables are the same size as the track
- mThumbPlusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
- mThumbMinusArrowDrawable.setBounds(0, 0, right - left, bottom - top);
+ mZoomInArrowDrawable.setBounds(0, 0, right - left, bottom - top);
+ mZoomOutArrowDrawable.setBounds(0, 0, right - left, bottom - top);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
-// Log.d(TAG, "History size: " + event.getHistorySize());
-
return handleTouch(event.getAction(), event.getEventTime(),
(int) event.getX(), (int) event.getY(), (int) event.getRawX(),
(int) event.getRawY());
@@ -457,15 +554,10 @@ public class ZoomRing extends View {
boolean isTouchingRing = mThumbVisible;
int touchAngle = getAngle(localX, localY);
-// printAngle("touchAngle", touchAngle);
-// printAngle("mThumbAngle", mThumbAngle);
-// printAngle("mPreviousCallbackAngle", mPreviousCallbackAngle);
-// Log.d(TAG, "");
-
int radiusSquared = localX * localX + localY * localY;
- if (radiusSquared < mBoundInnerRadiusSquared ||
- radiusSquared > mBoundOuterRadiusSquared) {
+ if (radiusSquared < mTrackInnerRadiusSquared ||
+ radiusSquared > mTrackOuterRadiusSquared) {
// Out-of-bounds
isTouchingThumb = false;
isTouchingRing = false;
@@ -486,7 +578,7 @@ public class ZoomRing extends View {
if (!isTouchingRing &&
(time - mPreviousCenterUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT)) {
// Make sure the double-tap is in the center of the widget (and not on the ring)
- mCallback.onZoomRingDismissed(true);
+ mCallback.onZoomRingDismissed();
onTouchUp(time, isTouchingRing);
// Dismissing, so halt here
@@ -557,7 +649,7 @@ public class ZoomRing extends View {
}
setMode(MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP);
- mWaitingForDragThumbDownAngle = touchAngle;
+ mInitialGrabThumbAngle = touchAngle;
boolean ccw = deltaThumbAndTick > 0;
setThumbAngleAnimated(tickAngle, 0, ccw);
@@ -577,9 +669,9 @@ public class ZoomRing extends View {
}
} else if (mMode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) {
- int deltaDownAngle = getDelta(mWaitingForDragThumbDownAngle, touchAngle);
+ int deltaDownAngle = getDelta(mInitialGrabThumbAngle, touchAngle);
if ((deltaDownAngle < -THUMB_DRAG_SLOP || deltaDownAngle > THUMB_DRAG_SLOP) &&
- isDeltaInBounds(mWaitingForDragThumbDownAngle, deltaDownAngle)) {
+ isDeltaInBounds(mInitialGrabThumbAngle, deltaDownAngle)) {
setMode(MODE_DRAG_THUMB);
// No need to call onThumbDragStarted, since that was done when they tapped-to-jump
@@ -591,6 +683,8 @@ public class ZoomRing extends View {
/* Make sure the user has moved the slop amount before going into that mode. */
setMode(MODE_MOVE_ZOOM_RING);
mCallback.onZoomRingMovingStarted();
+ // Move the zoom ring so it is under the finger where the user first touched
+ mCallback.onZoomRingMoved(x - mPreviousDownX, y - mPreviousDownY, rawX, rawY);
}
} else if (mMode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) {
if (isTouchingThumb) {
@@ -629,7 +723,7 @@ public class ZoomRing extends View {
if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG) {
// Animate back to a tick
- setThumbAngleAnimated(mPreviousCallbackAngle, 0);
+ setThumbAngleAnimated(mPreviousCallbackTickAngle, 0);
}
}
mCallback.onUserInteractionStopped();
@@ -741,9 +835,9 @@ public class ZoomRing extends View {
boolean animateThumbToNewAngle = false;
int totalDeltaAngle;
- totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
- if (totalDeltaAngle >= mFuzzyCallbackThreshold
- || totalDeltaAngle <= -mFuzzyCallbackThreshold) {
+ totalDeltaAngle = getDelta(mPreviousCallbackTickAngle, touchAngle, useDirection, ccw);
+ if (totalDeltaAngle >= mFuzzyTickDelta
+ || totalDeltaAngle <= -mFuzzyTickDelta) {
if (!useDirection) {
// Set ccw to match the direction found by getDelta
@@ -763,9 +857,9 @@ public class ZoomRing extends View {
if (ccw && mThumbCcwBound != Integer.MIN_VALUE) {
int deltaCcwBoundAndTouch =
getDelta(mThumbCcwBound, touchAngle, useDirection, true);
- if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) {
+ if (deltaCcwBoundAndTouch >= mTickDelta / 2) {
// The touch has past a bound
- int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+ int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackTickAngle,
touchAngle, useDirection, true);
if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) {
// The bound is between the previous callback angle and the touch
@@ -778,8 +872,8 @@ public class ZoomRing extends View {
// See block above for general comments
int deltaCwBoundAndTouch =
getDelta(mThumbCwBound, touchAngle, useDirection, false);
- if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) {
- int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle,
+ if (deltaCwBoundAndTouch <= -mTickDelta / 2) {
+ int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackTickAngle,
touchAngle, useDirection, false);
/*
* Both of these will be negative since we got delta in
@@ -795,7 +889,7 @@ public class ZoomRing extends View {
}
if (touchAngle != oldTouchAngle) {
// We bounded the touch angle
- totalDeltaAngle = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw);
+ totalDeltaAngle = getDelta(mPreviousCallbackTickAngle, touchAngle, useDirection, ccw);
animateThumbToNewAngle = true;
setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB);
}
@@ -819,7 +913,7 @@ public class ZoomRing extends View {
* 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);
+ int deltaLevels = Math.round((float) totalDeltaAngle / mTickDelta);
if (deltaLevels != 0) {
boolean canStillZoom = mCallback.onZoomRingThumbDragged(
deltaLevels, mThumbDragStartAngle, touchAngle);
@@ -833,8 +927,8 @@ public class ZoomRing extends View {
}
// Set the callback angle to the actual angle based on how many delta levels we gave
- mPreviousCallbackAngle = getValidAngle(
- mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold));
+ mPreviousCallbackTickAngle = getValidAngle(
+ mPreviousCallbackTickAngle + (deltaLevels * mTickDelta));
}
}
@@ -993,14 +1087,14 @@ public class ZoomRing extends View {
}
private int getClosestTickAngle(int angle) {
- int smallerAngleDistance = angle % mCallbackThreshold;
+ int smallerAngleDistance = angle % mTickDelta;
int smallerAngle = angle - smallerAngleDistance;
- if (smallerAngleDistance < mCallbackThreshold / 2) {
+ if (smallerAngleDistance < mTickDelta / 2) {
// Closer to the smaller angle
return smallerAngle;
} else {
// Closer to the bigger angle (premodding)
- return (smallerAngle + mCallbackThreshold) % TWO_PI_INT_MULTIPLIED;
+ return (smallerAngle + mTickDelta) % TWO_PI_INT_MULTIPLIED;
}
}
@@ -1025,7 +1119,7 @@ public class ZoomRing extends View {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
- mCallback.onZoomRingDismissed(true);
+ mCallback.onZoomRingDismissed();
}
}
@@ -1054,12 +1148,12 @@ public class ZoomRing extends View {
mTrail.draw(canvas);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
- mThumbPlusArrowDrawable.draw(canvas);
- mThumbPlusDrawable.draw(canvas);
+ mZoomInArrowDrawable.draw(canvas);
+ mZoomInArrowHintDrawable.draw(canvas);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
- mThumbMinusArrowDrawable.draw(canvas);
- mThumbMinusDrawable.draw(canvas);
+ mZoomOutArrowDrawable.draw(canvas);
+ mZoomOutArrowHintDrawable.draw(canvas);
}
mThumbDrawable.draw(canvas);
}
@@ -1067,48 +1161,48 @@ public class ZoomRing extends View {
private void setThumbArrowsAngle(int angle) {
int level = -angle * 10000 / ZoomRing.TWO_PI_INT_MULTIPLIED;
- mThumbPlusArrowDrawable.setLevel(level);
- mThumbMinusArrowDrawable.setLevel(level);
+ mZoomInArrowDrawable.setLevel(level);
+ mZoomOutArrowDrawable.setLevel(level);
// Assume it is a square
- int halfSideLength = mThumbPlusDrawable.getIntrinsicHeight() / 2;
+ int halfSideLength = mZoomInArrowHintDrawable.getIntrinsicHeight() / 2;
int unoffsetAngle = angle + mZeroAngle;
- int plusCenterX = (int) (Math.cos(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
- / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
- int plusCenterY = (int) (Math.sin(1f * (unoffsetAngle - THUMB_PLUS_MINUS_OFFSET_ANGLE)
- / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
- mThumbPlusDrawable.setBounds(plusCenterX - halfSideLength,
+ int plusCenterX = (int) (Math.cos(1f * (unoffsetAngle - mZoomArrowHintOffsetAngle)
+ / RADIAN_INT_MULTIPLIER) * mZoomArrowHintDistance) + mCenterX;
+ int plusCenterY = (int) (Math.sin(1f * (unoffsetAngle - mZoomArrowHintOffsetAngle)
+ / RADIAN_INT_MULTIPLIER) * mZoomArrowHintDistance) * -1 + mCenterY;
+ mZoomInArrowHintDrawable.setBounds(plusCenterX - halfSideLength,
plusCenterY - halfSideLength,
plusCenterX + halfSideLength,
plusCenterY + halfSideLength);
- int minusCenterX = (int) (Math.cos(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
- / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) + mCenterX;
- int minusCenterY = (int) (Math.sin(1f * (unoffsetAngle + THUMB_PLUS_MINUS_OFFSET_ANGLE)
- / RADIAN_INT_MULTIPLIER) * THUMB_PLUS_MINUS_DISTANCE) * -1 + mCenterY;
- mThumbMinusDrawable.setBounds(minusCenterX - halfSideLength,
+ int minusCenterX = (int) (Math.cos(1f * (unoffsetAngle + mZoomArrowHintOffsetAngle)
+ / RADIAN_INT_MULTIPLIER) * mZoomArrowHintDistance) + mCenterX;
+ int minusCenterY = (int) (Math.sin(1f * (unoffsetAngle + mZoomArrowHintOffsetAngle)
+ / RADIAN_INT_MULTIPLIER) * mZoomArrowHintDistance) * -1 + mCenterY;
+ mZoomOutArrowHintDrawable.setBounds(minusCenterX - halfSideLength,
minusCenterY - halfSideLength,
minusCenterX + halfSideLength,
minusCenterY + halfSideLength);
}
- public void setThumbArrowsVisible(boolean visible) {
+ void setThumbArrowsVisible(boolean visible) {
if (visible) {
mThumbArrowsAlpha = 255;
- int callbackAngle = mPreviousCallbackAngle;
+ int callbackAngle = mPreviousCallbackTickAngle;
if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR ||
callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) {
- mThumbPlusArrowDrawable.setAlpha(255);
- mThumbPlusDrawable.setAlpha(255);
+ mZoomInArrowDrawable.setAlpha(255);
+ mZoomInArrowHintDrawable.setAlpha(255);
mThumbArrowsToDraw |= THUMB_ARROW_PLUS;
} else {
mThumbArrowsToDraw &= ~THUMB_ARROW_PLUS;
}
if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR ||
callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) {
- mThumbMinusArrowDrawable.setAlpha(255);
- mThumbMinusDrawable.setAlpha(255);
+ mZoomOutArrowDrawable.setAlpha(255);
+ mZoomOutArrowHintDrawable.setAlpha(255);
mThumbArrowsToDraw |= THUMB_ARROW_MINUS;
} else {
mThumbArrowsToDraw &= ~THUMB_ARROW_MINUS;
@@ -1117,11 +1211,11 @@ public class ZoomRing extends View {
} else if (mThumbArrowsAlpha == 255) {
// Only start fade if we're fully visible (otherwise another fade is happening already)
mThumbArrowsFadeStartTime = SystemClock.elapsedRealtime();
- onThumbArrowsFadeTick();
+ onThumbArrowsFadeStep();
}
}
- private void onThumbArrowsFadeTick() {
+ private void onThumbArrowsFadeStep() {
if (mThumbArrowsAlpha <= 0) {
mThumbArrowsToDraw = 0;
return;
@@ -1132,20 +1226,20 @@ public class ZoomRing extends View {
/ THUMB_ARROWS_FADE_DURATION));
if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0;
if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) {
- mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha);
- mThumbPlusDrawable.setAlpha(mThumbArrowsAlpha);
- invalidateDrawable(mThumbPlusDrawable);
- invalidateDrawable(mThumbPlusArrowDrawable);
+ mZoomInArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mZoomInArrowHintDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mZoomInArrowHintDrawable);
+ invalidateDrawable(mZoomInArrowDrawable);
}
if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) {
- mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha);
- mThumbMinusDrawable.setAlpha(mThumbArrowsAlpha);
- invalidateDrawable(mThumbMinusDrawable);
- invalidateDrawable(mThumbMinusArrowDrawable);
+ mZoomOutArrowDrawable.setAlpha(mThumbArrowsAlpha);
+ mZoomOutArrowHintDrawable.setAlpha(mThumbArrowsAlpha);
+ invalidateDrawable(mZoomOutArrowHintDrawable);
+ invalidateDrawable(mZoomOutArrowDrawable);
}
- if (!mHandler.hasMessages(MSG_THUMB_ARROWS_FADE_TICK)) {
- mHandler.sendEmptyMessage(MSG_THUMB_ARROWS_FADE_TICK);
+ if (!mHandler.hasMessages(MSG_THUMB_ARROWS_FADE_STEP)) {
+ mHandler.sendEmptyMessage(MSG_THUMB_ARROWS_FADE_STEP);
}
}
@@ -1168,7 +1262,7 @@ public class ZoomRing extends View {
boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle);
void onZoomRingThumbDraggingStopped();
- void onZoomRingDismissed(boolean dismissImmediately);
+ void onZoomRingDismissed();
void onUserInteractionStarted();
void onUserInteractionStopped();
diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java
index 19f66a0..3bf3b22 100644
--- a/core/java/android/widget/ZoomRingController.java
+++ b/core/java/android/widget/ZoomRingController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2009 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.
@@ -30,7 +30,8 @@ import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings;
-import android.util.Log;
+import android.util.DisplayMetrics;
+import android.view.GestureDetector;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -44,11 +45,9 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
-// TODO: make sure no px values exist, only dip (scale if necessary from Viewconfiguration)
-
/**
- * TODO: Docs
- *
+ * A controller to simplify the use of the zoom ring widget.
+ * <p>
* If you are using this with a custom View, please call
* {@link #setVisible(boolean) setVisible(false)} from the
* {@link View#onDetachedFromWindow}.
@@ -58,13 +57,7 @@ import android.view.animation.DecelerateInterpolator;
public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
View.OnTouchListener, View.OnKeyListener {
- private static final int ZOOM_RING_RADIUS_INSET = 24;
-
- private static final int ZOOM_RING_RECENTERING_DURATION = 500;
-
- private static final String TAG = "ZoomRing";
-
- public static final boolean USE_OLD_ZOOM = false;
+ // Temporary methods for different zoom types
static int getZoomType(Context context) {
return Settings.System.getInt(context.getContentResolver(), "zoom", 1);
}
@@ -75,19 +68,43 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return getZoomType(context) == 1;
}
- private static final int ZOOM_CONTROLS_TIMEOUT =
- (int) ViewConfiguration.getZoomControlsTimeout();
+ /** The duration for the animation to re-center the zoom ring. */
+ private static final int RECENTERING_DURATION = 500;
- // TODO: move these to ViewConfiguration or re-use existing ones
- // 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;
- // 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
+ /** The inactivity timeout for the zoom ring. */
+ private static final int INACTIVITY_TIMEOUT =
+ (int) ViewConfiguration.getZoomControlsTimeout();
+
+ /**
+ * The delay when the user taps outside to dismiss the zoom ring. This is
+ * because the user can do a second-tap to recenter the owner view instead
+ * of dismissing the zoom ring.
+ */
+ private static final int OUTSIDE_TAP_DISMISS_DELAY =
+ ViewConfiguration.getDoubleTapTimeout() / 2;
+
+ /**
+ * When the zoom ring is on the edge, this is the delay before we actually
+ * start panning the owner.
+ * @see #mInitiatePanGap
+ */
private static final int INITIATE_PAN_DELAY = 300;
+ /**
+ * While already panning, if the zoom ring remains this close to an edge,
+ * the owner will continue to be panned.
+ */
+ private int mPanGap;
+
+ /** To begin a pan, the zoom ring must be this close to an edge. */
+ private int mInitiatePanGap;
+
+ /** Initialized from ViewConfiguration. */
+ private int mScaledTouchSlop;
+
+ /**
+ * The setting name that tracks whether we've shown the zoom ring toast.
+ */
private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast";
private Context mContext;
@@ -137,25 +154,37 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* screen once (for the first tap down) instead of twice (for the first tap
* down and then to grab the thumb).
*/
+ /** The X where the tap-drag started. */
private int mTapDragStartX;
+ /** The Y where the tap-drag started. */
private int mTapDragStartY;
+ /** The controller is idle */
private static final int TOUCH_MODE_IDLE = 0;
- private static final int TOUCH_MODE_WAITING_FOR_SECOND_TAP = 1;
+ /**
+ * In the middle of a second-tap interaction, waiting for either an up-touch
+ * or the user to start dragging to go into tap-drag mode.
+ */
private static final int TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT = 2;
+ /** In the middle of a tap-drag. */
private static final int TOUCH_MODE_FORWARDING_FOR_TAP_DRAG = 3;
private int mTouchMode;
+ /** Whether the zoom ring is visible. */
private boolean mIsZoomRingVisible;
private ZoomRing mZoomRing;
+ /** Cached width of the zoom ring. */
private int mZoomRingWidth;
+ /** Cached height of the zoom ring. */
private int mZoomRingHeight;
/** Invokes panning of owner view if the zoom ring is touching an edge. */
private Panner mPanner;
+ /** The time when the zoom ring first touched the edge. */
private long mTouchingEdgeStartTime;
- private boolean mPanningEnabledForThisInteraction;
+ /** Whether the user has already initiated the panning. */
+ private boolean mPanningInitiated;
/**
* When the finger moves the zoom ring to an edge, this is the horizontal
@@ -167,16 +196,21 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/** Vertical accumulator, see {@link #mMovingZoomRingOobX} */
private int mMovingZoomRingOobY;
+ /** Arrows that hint that the zoom ring is movable. */
private ImageView mPanningArrows;
+ /** The animation shown when the panning arrows are being shown. */
private Animation mPanningArrowsEnterAnimation;
+ /** The animation shown when the panning arrows are being hidden. */
private Animation mPanningArrowsExitAnimation;
+ /**
+ * Temporary rectangle, only use from the UI thread (and ideally don't rely
+ * on it being unused across many method calls.)
+ */
private Rect mTempRect = new Rect();
private OnZoomListener mCallback;
- private ViewConfiguration mViewConfig;
-
/**
* When the zoom ring is centered on screen, this will be the x value used
* for the container's layout params.
@@ -217,6 +251,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
private IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
+ /** Listens for configuration changes so we can make sure we're still in a reasonable state. */
private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -228,7 +263,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
};
/** Keeps the scroller going (or starts it). */
- private static final int MSG_SCROLLER_TICK = 1;
+ private static final int MSG_SCROLLER_STEP = 1;
/** When configuration changes, this is called after the UI thread is idle. */
private static final int MSG_POST_CONFIGURATION_CHANGED = 2;
/** Used to delay the zoom ring dismissal. */
@@ -244,8 +279,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_SCROLLER_TICK:
- onScrollerTick();
+ case MSG_SCROLLER_STEP:
+ onScrollerStep();
break;
case MSG_POST_CONFIGURATION_CHANGED:
@@ -296,8 +331,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mContainerLayoutParams.width = LayoutParams.WRAP_CONTENT;
mContainerLayoutParams.type = LayoutParams.TYPE_APPLICATION_PANEL;
mContainerLayoutParams.format = PixelFormat.TRANSPARENT;
- // TODO: make a new animation for this
- mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Dialog;
+ mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_ZoomRing;
mContainer = new FrameLayout(context);
mContainer.setLayoutParams(mContainerLayoutParams);
@@ -308,13 +342,17 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mScroller = new Scroller(context, new DecelerateInterpolator());
- mViewConfig = ViewConfiguration.get(context);
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ mScaledTouchSlop = vc.getScaledTouchSlop();
+
+ float density = context.getResources().getDisplayMetrics().density;
+ mPanGap = (int) (20 * density);
+ mInitiatePanGap = (int) (10 * density);
}
private void createPanningArrows() {
- // TODO: style
mPanningArrows = new ImageView(mContext);
- mPanningArrows.setImageResource(com.android.internal.R.drawable.zoom_ring_arrows);
+ mPanningArrows.setImageDrawable(mZoomRing.getPanningArrowsDrawable());
mPanningArrows.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
@@ -328,15 +366,16 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
/**
- * Sets the angle (in radians) a user must travel in order for the client to
- * get a callback. Once there is a callback, the accumulator resets. For
- * example, if you set this to PI/6, it will give a callback every time the
- * user moves PI/6 amount on the ring.
- *
- * @param callbackThreshold The angle for the callback threshold, in radians
+ * Sets the angle (in radians) between ticks. This is also the angle a user
+ * must move the thumb in order for the client to get a callback. Once there
+ * is a callback, the accumulator resets. For example, if you set this to
+ * PI/6, it will give a callback every time the user moves PI/6 amount on
+ * the ring.
+ *
+ * @param angle The angle for the callback threshold, in radians
*/
- public void setZoomCallbackThreshold(float callbackThreshold) {
- mZoomRing.setCallbackThreshold((int) (callbackThreshold * ZoomRing.RADIAN_INT_MULTIPLIER));
+ public void setTickDelta(float angle) {
+ mZoomRing.setTickDelta((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
}
/**
@@ -346,14 +385,23 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
* @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) {
+ public void setTrackDrawable(int drawable) {
mZoomRing.setBackgroundResource(drawable);
}
-
+
+ /**
+ * Sets the callback for the zoom ring controller.
+ *
+ * @param callback The callback.
+ */
public void setCallback(OnZoomListener callback) {
mCallback = callback;
}
+ public void setVibration(boolean vibrate) {
+ mZoomRing.setVibration(vibrate);
+ }
+
public void setThumbAngle(float angle) {
mZoomRing.setThumbAngle((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER));
}
@@ -362,14 +410,6 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0);
}
- public void setResetThumbAutomatically(boolean resetThumbAutomatically) {
- mZoomRing.setResetThumbAutomatically(resetThumbAutomatically);
- }
-
- public void setVibration(boolean vibrate) {
- mZoomRing.setVibration(vibrate);
- }
-
public void setThumbVisible(boolean thumbVisible) {
mZoomRing.setThumbVisible(thumbVisible);
}
@@ -407,7 +447,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return;
}
- dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ dismissZoomRingDelayed(INACTIVITY_TIMEOUT);
} else {
mPanner.stop();
}
@@ -429,12 +469,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
public void run() {
refreshPositioningVariables();
resetZoomRing();
-
- // TODO: remove this 'update' and just center zoom ring before the
- // 'add', but need to make sure we have the width and height (which
- // probably can only be retrieved after it's measured, which happens
- // after it's added).
- mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ refreshContainerLayout();
if (mCallback != null) {
mCallback.onVisibilityChanged(true);
@@ -479,19 +514,34 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
+ private void refreshContainerLayout() {
+ if (mIsZoomRingVisible) {
+ mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ }
+ }
+
/**
- * TODO: docs
- *
+ * Returns the container of the zoom ring widget. The client can add views
+ * here to be shown alongside the zoom ring. See {@link #getZoomRingId()}.
+ * <p>
* 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
+ * <ul>
+ * <li> The controller dispatches touch events differently than the regular view
+ * framework.
+ * <li> Please ensure you set your view to INVISIBLE not GONE when hiding it.
+ * </ul>
+ *
+ * @return The layout used by the container.
*/
public FrameLayout getContainer() {
return mContainer;
}
+ /**
+ * Returns the id of the zoom ring widget.
+ *
+ * @return The id of the zoom ring widget.
+ */
public int getZoomRingId() {
return mZoomRing.getId();
}
@@ -514,7 +564,10 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
/**
* Should be called by the client for each event belonging to the second tap
* (the down, move, up, and cancel events).
- *
+ * <p>
+ * In most cases, the client can use a {@link GestureDetector} and forward events from
+ * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)}.
+ *
* @param event The event belonging to the second tap.
* @return Whether the event was consumed.
*/
@@ -550,9 +603,9 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
- if (Math.abs(x - mTapDragStartX) > mViewConfig.getScaledTouchSlop() ||
+ if (Math.abs(x - mTapDragStartX) > mScaledTouchSlop ||
Math.abs(y - mTapDragStartY) >
- mViewConfig.getScaledTouchSlop()) {
+ mScaledTouchSlop) {
mZoomRing.setTapDragMode(true, x, y);
mTouchMode = TOUCH_MODE_FORWARDING_FOR_TAP_DRAG;
setTouchTargetView(mZoomRing);
@@ -600,8 +653,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
int width = mContainer.getWidth();
int height = mContainer.getHeight();
mScroller.startScroll(lp.x, lp.y, mCenteredContainerX - lp.x,
- mCenteredContainerY - lp.y, ZOOM_RING_RECENTERING_DURATION);
- mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
+ mCenteredContainerY - lp.y, RECENTERING_DURATION);
+ mHandler.sendEmptyMessage(MSG_SCROLLER_STEP);
}
}
@@ -636,18 +689,22 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mZoomRing.handleTouch(event.getAction(), event.getEventTime(), x, y, rawX, rawY);
}
+ /** @hide */
public void onZoomRingSetMovableHintVisible(boolean visible) {
setPanningArrowsVisible(visible);
}
+ /** @hide */
public void onUserInteractionStarted() {
mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
}
+ /** @hide */
public void onUserInteractionStopped() {
- dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ dismissZoomRingDelayed(INACTIVITY_TIMEOUT);
}
+ /** @hide */
public void onZoomRingMovingStarted() {
mScroller.abortAnimation();
mTouchingEdgeStartTime = 0;
@@ -664,6 +721,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
+ /** @hide */
public boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY) {
if (mMovingZoomRingOobX != 0) {
@@ -721,12 +779,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
ownerBounds.bottom - mZoomRingHeight : newZoomRingY;
lp.y = newZoomRingY - zoomRingTop;
- mWindowManager.updateViewLayout(mContainer, lp);
-
+ refreshContainerLayout();
+
// Check for pan
boolean horizontalPanning = true;
int leftGap = newZoomRingX - ownerBounds.left;
- if (leftGap < MAX_PAN_GAP) {
+ if (leftGap < mPanGap) {
if (leftGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
// Future moves in this direction should be accumulated in mMovingZoomRingOobX
mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
@@ -736,7 +794,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
} else {
int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft);
- if (rightGap < MAX_PAN_GAP) {
+ if (rightGap < mPanGap) {
if (rightGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) {
mMovingZoomRingOobX = deltaX / Math.abs(deltaX);
}
@@ -750,7 +808,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
int topGap = newZoomRingY - ownerBounds.top;
- if (topGap < MAX_PAN_GAP) {
+ if (topGap < mPanGap) {
if (topGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
}
@@ -759,7 +817,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
} else {
int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop);
- if (bottomGap < MAX_PAN_GAP) {
+ if (bottomGap < mPanGap) {
if (bottomGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) {
mMovingZoomRingOobY = deltaY / Math.abs(deltaY);
}
@@ -771,7 +829,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
if (!horizontalPanning) {
// Neither are panning, reset any timer to start pan mode
mTouchingEdgeStartTime = 0;
- mPanningEnabledForThisInteraction = false;
+ mPanningInitiated = false;
mPanner.stop();
}
}
@@ -781,13 +839,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
private boolean shouldPan(int gap) {
- if (mPanningEnabledForThisInteraction) return true;
+ if (mPanningInitiated) return true;
- if (gap < MAX_INITIATE_PAN_GAP) {
+ if (gap < mInitiatePanGap) {
long time = SystemClock.elapsedRealtime();
if (mTouchingEdgeStartTime != 0 &&
mTouchingEdgeStartTime + INITIATE_PAN_DELAY < time) {
- mPanningEnabledForThisInteraction = true;
+ mPanningInitiated = true;
return true;
} else if (mTouchingEdgeStartTime == 0) {
mTouchingEdgeStartTime = time;
@@ -800,6 +858,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return false;
}
+ /** @hide */
public void onZoomRingMovingStopped() {
mPanner.stop();
setPanningArrowsVisible(false);
@@ -809,27 +868,25 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
private int getStrengthFromGap(int gap) {
- return gap > MAX_PAN_GAP ? 0 :
- (MAX_PAN_GAP - gap) * 100 / MAX_PAN_GAP;
+ return gap > mPanGap ? 0 :
+ (mPanGap - gap) * 100 / mPanGap;
}
+ /** @hide */
public void onZoomRingThumbDraggingStarted() {
if (mCallback != null) {
mCallback.onBeginDrag();
}
}
+ /** @hide */
public boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle) {
if (mCallback != null) {
int deltaZoomLevel = -numLevels;
- int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() +
- mZoomRingWidth / 2;
- int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() +
- mZoomRingHeight / 2;
return mCallback.onDragZoom(deltaZoomLevel,
- globalZoomCenterX - mOwnerViewBounds.left,
- globalZoomCenterY - mOwnerViewBounds.top,
+ getZoomRingCenterXInOwnerCoordinates(),
+ getZoomRingCenterYInOwnerCoordinates(),
(float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER,
(float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER);
}
@@ -837,24 +894,36 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return false;
}
+ private int getZoomRingCenterXInOwnerCoordinates() {
+ int globalZoomCenterX = mContainerLayoutParams.x + mZoomRing.getLeft() +
+ mZoomRingWidth / 2;
+ return globalZoomCenterX - mOwnerViewBounds.left;
+ }
+
+ private int getZoomRingCenterYInOwnerCoordinates() {
+ int globalZoomCenterY = mContainerLayoutParams.y + mZoomRing.getTop() +
+ mZoomRingHeight / 2;
+ return globalZoomCenterY - mOwnerViewBounds.top;
+ }
+
+ /** @hide */
public void onZoomRingThumbDraggingStopped() {
if (mCallback != null) {
mCallback.onEndDrag();
}
}
- public void onZoomRingDismissed(boolean dismissImmediately) {
- if (dismissImmediately) {
- mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
- setVisible(false);
- } else {
- dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
- }
+ /** @hide */
+ public void onZoomRingDismissed() {
+ mHandler.removeMessages(MSG_DISMISS_ZOOM_RING);
+ setVisible(false);
}
+ /** @hide */
public void onRingDown(int tickAngle, int touchAngle) {
}
+ /** @hide */
public boolean onTouch(View v, MotionEvent event) {
if (sTutorialDialog != null && sTutorialDialog.isShowing() &&
SystemClock.elapsedRealtime() - sTutorialShowTime >= TUTORIAL_MIN_DISPLAY_TIME) {
@@ -904,8 +973,9 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return retValue;
} else {
+// dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
if (action == MotionEvent.ACTION_DOWN) {
- dismissZoomRingDelayed(ZOOM_RING_DISMISS_DELAY);
+ dismissZoomRingDelayed(OUTSIDE_TAP_DISMISS_DELAY);
}
return false;
@@ -932,7 +1002,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2;
int distanceFromCenterX = rawX - containerCenterX;
int distanceFromCenterY = rawY - containerCenterY;
- int zoomRingRadius = mZoomRingWidth / 2 - ZOOM_RING_RADIUS_INSET;
+ int zoomRingRadius = mZoomRing.getTrackOuterRadius();
if (distanceFromCenterX * distanceFromCenterX +
distanceFromCenterY * distanceFromCenterY <=
zoomRingRadius * zoomRingRadius) {
@@ -960,7 +1030,11 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return null;
}
- /** Steals key events from the owner view. */
+ /**
+ * Steals key events from the owner view.
+ *
+ * @hide
+ */
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -971,12 +1045,14 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
// Keep the zoom alive a little longer
- dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ dismissZoomRingDelayed(INACTIVITY_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);
+ mCallback.onSimpleZoom(keyCode == KeyEvent.KEYCODE_DPAD_UP,
+ getZoomRingCenterXInOwnerCoordinates(),
+ getZoomRingCenterYInOwnerCoordinates());
}
return true;
@@ -985,18 +1061,18 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
return false;
}
- private void onScrollerTick() {
+ private void onScrollerStep() {
if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return;
mContainerLayoutParams.x = mScroller.getCurrX();
mContainerLayoutParams.y = mScroller.getCurrY();
- mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams);
+ refreshContainerLayout();
- mHandler.sendEmptyMessage(MSG_SCROLLER_TICK);
+ mHandler.sendEmptyMessage(MSG_SCROLLER_STEP);
}
private void onPostConfigurationChanged() {
- dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT);
+ dismissZoomRingDelayed(INACTIVITY_TIMEOUT);
refreshPositioningVariables();
ensureZoomRingIsCentered();
}
@@ -1056,6 +1132,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
sTutorialShowTime = SystemClock.elapsedRealtime();
}
+ /** @hide Should only be used by Android platform apps */
public static void finishZoomTutorial(Context context, boolean userNotified) {
if (sTutorialDialog == null) return;
@@ -1078,22 +1155,45 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
}
+ /** @hide Should only be used by Android platform apps */
public void finishZoomTutorial() {
finishZoomTutorial(mContext, true);
}
+ /**
+ * Sets the initial velocity of a pan.
+ *
+ * @param startVelocity The initial velocity to move the owner view, in
+ * pixels per second.
+ */
public void setPannerStartVelocity(float startVelocity) {
mPanner.mStartVelocity = startVelocity;
}
+ /**
+ * Sets the accelartion of the pan.
+ *
+ * @param acceleration The acceleration, in pixels per second squared.
+ */
public void setPannerAcceleration(float acceleration) {
mPanner.mAcceleration = acceleration;
}
+ /**
+ * Sets the maximum velocity of a pan.
+ *
+ * @param maxVelocity The max velocity to move the owner view, in pixels per
+ * second.
+ */
public void setPannerMaxVelocity(float maxVelocity) {
mPanner.mMaxVelocity = maxVelocity;
}
+ /**
+ * Sets the duration before acceleration will be applied.
+ *
+ * @param duration The duration, in milliseconds.
+ */
public void setPannerStartAcceleratingDuration(int duration) {
mPanner.mStartAcceleratingDuration = duration;
}
@@ -1201,16 +1301,83 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback,
}
+ /**
+ * Interface used to inform the client of zoom events that the user
+ * triggers.
+ */
public interface OnZoomListener {
+ /**
+ * Called when the user begins dragging the thumb on the zoom ring.
+ */
void onBeginDrag();
+
+ /**
+ * Called when the user drags the thumb and passes a tick causing a
+ * zoom.
+ *
+ * @param deltaZoomLevel The number of levels to be zoomed. Positive to
+ * zoom in, negative to zoom out.
+ * @param centerX The point about which to zoom. The zoom should pin
+ * this point, leaving it at the same coordinate. This is
+ * relative to the owner view's upper-left.
+ * @param centerY The point about which to zoom. The zoom should pin
+ * this point, leaving it at the same coordinate. This is
+ * relative to the owner view's upper-left.
+ * @param startAngle The angle where the user started dragging the thumb.
+ * @param curAngle The current angle of the thumb.
+ * @return Whether the owner was zoomed.
+ */
boolean onDragZoom(int deltaZoomLevel, int centerX, int centerY, float startAngle,
float curAngle);
+
+ /**
+ * Called when the user releases the thumb.
+ */
void onEndDrag();
- void onSimpleZoom(boolean deltaZoomLevel);
+
+ /**
+ * Called when the user zooms via some other mechanism, for example
+ * arrow keys or a trackball.
+ *
+ * @param zoomIn Whether to zoom in (true) or out (false).
+ * @param centerX See {@link #onDragZoom(int, int, int, float, float)}.
+ * @param centerY See {@link #onDragZoom(int, int, int, float, float)}.
+ */
+ void onSimpleZoom(boolean zoomIn, int centerX, int centerY);
+
+ /**
+ * Called when the user begins moving the zoom ring in order to pan the
+ * owner.
+ */
void onBeginPan();
+
+ /**
+ * Called when the owner should pan as a result of the user moving the zoom ring.
+ *
+ * @param deltaX The amount to pan horizontally.
+ * @param deltaY The amount to pan vertically.
+ * @return Whether the owner was panned.
+ */
boolean onPan(int deltaX, int deltaY);
+
+ /**
+ * Called when the user releases the zoom ring.
+ */
void onEndPan();
+
+ /**
+ * Called when the client should center the owner on the given point.
+ *
+ * @param x The x to center on, relative to the owner view's upper-left.
+ * @param y The y to center on, relative to the owner view's upper-left.
+ */
void onCenter(int x, int y);
+
+ /**
+ * Called when the zoom ring's visibility changes.
+ *
+ * @param visible Whether the zoom ring is visible (true) or not (false).
+ */
void onVisibilityChanged(boolean visible);
}
}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index bab1e21..932555d 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -120,6 +120,10 @@ public class HandlerCaller {
return mH.obtainMessage(what, arg1, 0);
}
+ public Message obtainMessageII(int what, int arg1, int arg2) {
+ return mH.obtainMessage(what, arg1, arg2);
+ }
+
public Message obtainMessageIO(int what, int arg1, Object arg2) {
return mH.obtainMessage(what, arg1, 0, arg2);
}
diff --git a/core/java/com/android/internal/os/IResultReceiver.aidl b/core/java/com/android/internal/os/IResultReceiver.aidl
new file mode 100644
index 0000000..2b70f95
--- /dev/null
+++ b/core/java/com/android/internal/os/IResultReceiver.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 2009, 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 com.android.internal.os;
+
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IResultReceiver {
+ void send(int resultCode, in Bundle resultData);
+}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index 9b00402..8ff18ed 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -18,6 +18,7 @@ package com.android.internal.view;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.ResultReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
@@ -48,7 +49,7 @@ oneway interface IInputMethod {
void revokeSession(IInputMethodSession session);
- void showSoftInput(int flags);
+ void showSoftInput(int flags, in ResultReceiver resultReceiver);
- void hideSoftInput();
+ void hideSoftInput(int flags, in ResultReceiver resultReceiver);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 1b1c7f7..9030a3e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -16,6 +16,7 @@
package com.android.internal.view;
+import android.os.ResultReceiver;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.EditorInfo;
import com.android.internal.view.InputBindResult;
@@ -38,8 +39,10 @@ interface IInputMethodManager {
IInputContext inputContext, in EditorInfo attribute,
boolean initial, boolean needResult);
void finishInput(in IInputMethodClient client);
- void showSoftInput(in IInputMethodClient client, int flags);
- void hideSoftInput(in IInputMethodClient client, int flags);
+ boolean showSoftInput(in IInputMethodClient client, int flags,
+ in ResultReceiver resultReceiver);
+ boolean hideSoftInput(in IInputMethodClient client, int flags,
+ in ResultReceiver resultReceiver);
void windowGainedFocus(in IInputMethodClient client,
boolean viewHasFocus, boolean isTextEditor,
int softInputMode, boolean first, int windowFlags);
@@ -47,6 +50,7 @@ interface IInputMethodManager {
void showInputMethodPickerFromClient(in IInputMethodClient client);
void setInputMethod(in IBinder token, String id);
void hideMySoftInput(in IBinder token, int flags);
+ void showMySoftInput(in IBinder token, int flags);
void updateStatusIcon(in IBinder token, String packageName, int iconId);
boolean setInputMethodEnabled(String id, boolean enabled);
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index 8a44976..a05ff14 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -46,4 +46,6 @@ oneway interface IInputMethodSession {
void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
void appPrivateCommand(String action, in Bundle data);
+
+ void toggleSoftInput(int showFlags, int hideFlags);
}
diff --git a/core/java/com/android/internal/widget/EditStyledText.java b/core/java/com/android/internal/widget/EditStyledText.java
new file mode 100644
index 0000000..48b4780
--- /dev/null
+++ b/core/java/com/android/internal/widget/EditStyledText.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2009 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 com.android.internal.widget;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.EditText;
+
+/**
+ * EditStyledText extends EditText for managing the flow and status
+ * to edit the styled text. This manages the states and flows of editing,
+ * supports inserting image, import/export HTML.
+ */
+public class EditStyledText extends EditText {
+
+ private static final String LOG_TAG = "EditStyledText";
+ private static final boolean DBG = true;
+
+ /**
+ * The modes of editing actions.
+ */
+ /** The mode that no editing action is done. */
+ public static final int MODE_NOTHING = 0;
+ /** The mode of copy. */
+ public static final int MODE_COPY = 1;
+ /** The mode of paste. */
+ public static final int MODE_PASTE = 2;
+ /** The mode of changing size. */
+ public static final int MODE_SIZE = 3;
+ /** The mode of changing color. */
+ public static final int MODE_COLOR = 4;
+ /** The mode of selection. */
+ public static final int MODE_SELECT = 5;
+
+ /**
+ * The state of selection.
+ */
+ /** The state that selection isn't started. */
+ public static final int STATE_SELECT_OFF = 0;
+ /** The state that selection is started. */
+ public static final int STATE_SELECT_ON = 1;
+ /** The state that selection is done, but not fixed. */
+ public static final int STATE_SELECTED = 2;
+ /** The state that selection is done and not fixed.*/
+ public static final int STATE_SELECT_FIX = 3;
+
+ /**
+ * The help message strings.
+ */
+ public static final int HINT_MSG_NULL = 0;
+ public static final int HINT_MSG_COPY_BUF_BLANK = 1;
+ public static final int HINT_MSG_SELECT_START = 2;
+ public static final int HINT_MSG_SELECT_END = 3;
+ public static final int HINT_MSG_PUSH_COMPETE = 4;
+
+
+ /**
+ * EditStyledTextInterface provides functions for notifying messages
+ * to calling class.
+ */
+ public interface EditStyledTextInterface {
+ public void notifyHintMsg(int msg_id);
+ }
+ private EditStyledTextInterface mESTInterface;
+
+ /**
+ * EditStyledTextEditorManager manages the flow and status of
+ * each function for editing styled text.
+ */
+ private EditStyledTextEditorManager mManager;
+
+ /**
+ * EditStyledText extends EditText for managing flow of each editing
+ * action.
+ */
+ public EditStyledText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ public EditStyledText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public EditStyledText(Context context) {
+ super(context);
+ init();
+ }
+
+ /**
+ * Set View objects used in EditStyledText.
+ * @param helptext The view shows help messages.
+ */
+ public void setParts(EditStyledTextInterface est_interface) {
+ mESTInterface = est_interface;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final boolean superResult = super.onTouchEvent(event);
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onTouchEvent");
+ }
+ mManager.onTouchScreen();
+ }
+ return superResult;
+ }
+
+ /**
+ * Start editing. This function have to be called before other
+ * editing actions.
+ */
+ public void onStartEdit() {
+ mManager.onStartEdit();
+ }
+
+ /**
+ * End editing.
+ */
+ public void onEndEdit() {
+ mManager.onEndEdit();
+ }
+
+ /**
+ * Start "Copy" action.
+ */
+ public void onStartCopy() {
+ mManager.onStartCopy();
+ }
+
+ /**
+ * Start "Paste" action.
+ */
+ public void onStartPaste() {
+ mManager.onStartPaste();
+ }
+
+ /**
+ * Start changing "Size" action.
+ */
+ public void onStartSize() {
+ mManager.onStartSize();
+ }
+
+ /**
+ * Start changing "Color" action.
+ */
+ public void onStartColor() {
+ mManager.onStartColor();
+ }
+
+ /**
+ * Start "Select" action.
+ */
+ public void onStartSelect() {
+ mManager.onStartSelect();
+ }
+
+ /**
+ * Start "SelectAll" action.
+ */
+ public void onStartSelectAll() {
+ mManager.onStartSelectAll();
+ }
+
+ /**
+ * Fix Selected Item.
+ */
+ public void fixSelectedItem() {
+ mManager.onFixSelectItem();
+ }
+
+ /**
+ * Set Size of the Item.
+ * @param size The size of the Item.
+ */
+ public void setItemSize(int size) {
+ mManager.setItemSize(size);
+ }
+
+ /**
+ * Set Color of the Item.
+ * @param color The color of the Item.
+ */
+ public void setItemColor(int color) {
+ mManager.setItemColor(color);
+ }
+
+ /**
+ * Check editing is started.
+ * @return Whether editing is started or not.
+ */
+ public boolean isEditting() {
+ return mManager.isEditting();
+ }
+
+ /**
+ * Get the mode of the action.
+ * @return The mode of the action.
+ */
+ public int getEditMode() {
+ return mManager.getEditMode();
+ }
+
+ /**
+ * Get the state of the selection.
+ * @return The state of the selection.
+ */
+ public int getSelectState() {
+ return mManager.getSelectState();
+ }
+
+ /**
+ * Initialize members.
+ */
+ private void init() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- init");
+ requestFocus();
+ }
+ mManager = new EditStyledTextEditorManager(this);
+ }
+
+ /**
+ * Notify hint messages what action is expected to calling class.
+ * @param msg
+ */
+ private void setHintMessage(int msg_id) {
+ if (mESTInterface != null) {
+ mESTInterface.notifyHintMsg(msg_id);
+ }
+ }
+
+ /**
+ * Object which manages the flow and status of editing actions.
+ */
+ private class EditStyledTextEditorManager {
+ private boolean mEditFlag = false;
+ private int mMode = 0;
+ private int mState = 0;
+ private int mCurStart = 0;
+ private int mCurEnd = 0;
+ private EditStyledText mEST;
+ private Editable mTextSelectBuffer;
+ private CharSequence mTextCopyBufer;
+
+ EditStyledTextEditorManager(EditStyledText est) {
+ mEST = est;
+ }
+
+ public void onStartEdit() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onEdit");
+ }
+ handleResetEdit();
+ }
+
+ public void onEndEdit() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickCancel");
+ }
+ handleCancel();
+ }
+
+ public void onStartCopy() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickCopy");
+ }
+ handleCopy();
+ }
+
+ public void onStartPaste() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickPaste");
+ }
+ handlePaste();
+ }
+
+ public void onStartSize() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSize");
+ }
+ handleSize();
+ }
+
+ public void setItemSize(int size) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSizeItem");
+ }
+ if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
+ changeSizeSelectedText(size);
+ handleResetEdit();
+ }
+ }
+
+ public void setItemColor(int color) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickColorItem");
+ }
+ if (mState == STATE_SELECTED || mState == STATE_SELECT_FIX) {
+ changeColorSelectedText(color);
+ handleResetEdit();
+ }
+ }
+
+ public void onStartColor() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickColor");
+ }
+ handleColor();
+ }
+
+ public void onStartSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSelect");
+ }
+ mMode = MODE_SELECT;
+ if (mState == STATE_SELECT_OFF) {
+ handleSelect();
+ } else {
+ offSelect();
+ handleSelect();
+ }
+ }
+
+ public void onStartSelectAll() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickSelectAll");
+ }
+ handleSelectAll();
+ }
+
+ public void onTouchScreen() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickView");
+ }
+ if (mState == STATE_SELECT_ON || mState == STATE_SELECTED) {
+ handleSelect();
+ }
+ }
+
+ public void onFixSelectItem() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onClickComplete");
+ }
+ handleComplete();
+ }
+
+ public boolean isEditting() {
+ return mEditFlag;
+ }
+
+ public int getEditMode() {
+ return mMode;
+ }
+
+ public int getSelectState() {
+ return mState;
+ }
+
+ private void handleCancel() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleCancel");
+ }
+ mMode = MODE_NOTHING;
+ mState = STATE_SELECT_OFF;
+ mEditFlag = false;
+ offSelect();
+ }
+
+ private void handleComplete() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleComplete");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ }
+ switch (mMode) {
+ case MODE_COPY:
+ handleCopy();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handleCopy() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleCopy: " + mMode + "," + mState);
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
+ mMode = MODE_COPY;
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ storeSelectedText();
+ } else {
+ handleSelect();
+ }
+ } else if (mMode != MODE_COPY) {
+ handleCancel();
+ mMode = MODE_COPY;
+ handleCopy();
+ } else if (mState == STATE_SELECT_FIX) {
+ mEST.setHintMessage(HINT_MSG_NULL);
+ storeSelectedText();
+ handleResetEdit();
+ }
+ }
+
+ private void handlePaste() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handlePaste");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mTextSelectBuffer != null && mTextCopyBufer.length() > 0) {
+ mTextSelectBuffer.insert(mEST.getSelectionStart(),
+ mTextCopyBufer);
+ } else {
+ mEST.setHintMessage(HINT_MSG_COPY_BUF_BLANK);
+ }
+ }
+
+ private void handleSize() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSize: " + mMode + "," + mState);
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
+ mMode = MODE_SIZE;
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ } else {
+ handleSelect();
+ }
+ } else if (mMode != MODE_SIZE) {
+ handleCancel();
+ mMode = MODE_SIZE;
+ handleSize();
+ } else if (mState == STATE_SELECT_FIX) {
+ mEST.setHintMessage(HINT_MSG_NULL);
+ }
+ }
+
+ private void handleColor() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleColor");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mMode == MODE_NOTHING || mMode == MODE_SELECT) {
+ mMode = MODE_COLOR;
+ if (mState == STATE_SELECTED) {
+ mState = STATE_SELECT_FIX;
+ } else {
+ handleSelect();
+ }
+ } else if (mMode != MODE_COLOR) {
+ handleCancel();
+ mMode = MODE_COLOR;
+ handleSize();
+ } else if (mState == STATE_SELECT_FIX) {
+ mEST.setHintMessage(HINT_MSG_NULL);
+ }
+ }
+
+ private void handleSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSelect" + mEditFlag + "," + mState);
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ if (mState == STATE_SELECT_OFF) {
+ if (isTextSelected()) {
+ Log.e(LOG_TAG, "Selection state is off, but selected");
+ }
+ setSelectStartPos();
+ mEST.setHintMessage(HINT_MSG_SELECT_END);
+ } else if (mState == STATE_SELECT_ON) {
+ if (isTextSelected()) {
+ Log.e(LOG_TAG, "Selection state now start, but selected");
+ }
+ setSelectEndPos();
+ mEST.setHintMessage(HINT_MSG_PUSH_COMPETE);
+ doNextHandle();
+ } else if (mState == STATE_SELECTED) {
+ if (!isTextSelected()) {
+ Log.e(LOG_TAG,
+ "Selection state is done, but not selected");
+ }
+ setSelectEndPos();
+ doNextHandle();
+ }
+ }
+
+ private void handleSelectAll() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- handleSelectAll");
+ }
+ if (!mEditFlag) {
+ return;
+ }
+ mEST.selectAll();
+ }
+
+ private void doNextHandle() {
+ switch (mMode) {
+ case MODE_COPY:
+ handleCopy();
+ break;
+ case MODE_PASTE:
+ handlePaste();
+ break;
+ case MODE_SIZE:
+ handleSize();
+ break;
+ case MODE_COLOR:
+ handleColor();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handleResetEdit() {
+ handleCancel();
+ mEditFlag = true;
+ mEST.setHintMessage(HINT_MSG_SELECT_START);
+ }
+
+ // Methods of selection
+ private void onSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- onSelect");
+ }
+ if (mCurStart >= 0 && mCurStart <= mEST.getText().length()
+ && mCurEnd >= 0 && mCurEnd <= mEST.getText().length()) {
+ mEST.setSelection(mCurStart, mCurEnd);
+ mState = STATE_SELECTED;
+ } else {
+ Log.e(LOG_TAG,
+ "Select is on, but cursor positions are illigal.:"
+ + mEST.getText().length() + "," + mCurStart
+ + "," + mCurEnd);
+ }
+ }
+
+ private void offSelect() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- offSelect");
+ }
+ int currpos = mEST.getSelectionStart();
+ mEST.setSelection(currpos, currpos);
+ mState = STATE_SELECT_OFF;
+ }
+
+ private void setSelectStartPos() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- setSelectStartPos");
+ }
+ mCurStart = mEST.getSelectionStart();
+ mState = STATE_SELECT_ON;
+ }
+
+ private void setSelectEndPos() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- setSelectEndPos:"
+ + mEST.getSelectionStart());
+ }
+ int curpos = mEST.getSelectionStart();
+ if (curpos < mCurStart) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- setSelectEndPos: swap is done.");
+ }
+ mCurEnd = mCurStart;
+ mCurStart = curpos;
+ } else {
+ mCurEnd = curpos;
+ }
+ onSelect();
+ }
+
+ private boolean isTextSelected() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- isTextSelected:" + mCurStart + ","
+ + mCurEnd);
+ }
+ return (mCurStart != mCurEnd)
+ && (mState == STATE_SELECTED ||
+ mState == STATE_SELECT_FIX);
+ }
+
+ private void storeSelectedText() {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- storeSelectedText");
+ }
+ mTextSelectBuffer = mEST.getText();
+ mTextCopyBufer = mTextSelectBuffer.subSequence(mCurStart, mCurEnd);
+ }
+
+ private void changeSizeSelectedText(int size) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- changeSizeSelectedText:" + size + ","
+ + mCurStart + "," + mCurEnd);
+ }
+ mEST.getText().setSpan(new AbsoluteSizeSpan(size), mCurStart,
+ mCurEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ private void changeColorSelectedText(int color) {
+ if (DBG) {
+ Log.d(LOG_TAG, "--- changeCollorSelectedText:" + color + ","
+ + mCurStart + "," + mCurEnd);
+ }
+ mEST.getText().setSpan(new ForegroundColorSpan(color), mCurStart,
+ mCurEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+
+}
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index 32954ce..e951431 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -32,11 +32,11 @@ static SkTypeface* Typeface_create(JNIEnv* env, jobject, jstring name,
SkTypeface* face;
if (NULL == name) {
- face = SkTypeface::Create(NULL, (SkTypeface::Style)style);
+ face = SkTypeface::CreateFromName(NULL, (SkTypeface::Style)style);
}
else {
AutoJavaStringToUTF8 str(env, name);
- face = SkTypeface::Create(str.c_str(), style);
+ face = SkTypeface::CreateFromName(str.c_str(), style);
}
return face;
}
@@ -50,7 +50,7 @@ static void Typeface_unref(JNIEnv* env, jobject obj, SkTypeface* face) {
}
static int Typeface_getStyle(JNIEnv* env, jobject obj, SkTypeface* face) {
- return face->getStyle();
+ return face->style();
}
class AssetStream : public SkStream {
diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp
index 8e41ec7..923e1aa 100644
--- a/core/jni/android_text_format_Time.cpp
+++ b/core/jni/android_text_format_Time.cpp
@@ -52,6 +52,9 @@ static jfieldID g_dateTimeFormatField = 0;
static jfieldID g_amField = 0;
static jfieldID g_pmField = 0;
static jfieldID g_dateCommandField = 0;
+static jfieldID g_localeField = 0;
+
+static jclass g_timeClass = NULL;
static inline bool java2time(JNIEnv* env, Time* t, jobject o)
{
@@ -183,56 +186,101 @@ static jstring android_text_format_Time_format2445(JNIEnv* env, jobject This)
static jstring android_text_format_Time_format(JNIEnv* env, jobject This,
jstring formatObject)
{
- Time t;
- struct strftime_locale locale;
- jclass timeClass = env->FindClass("android/text/format/Time");
- jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7];
- jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt;
- jobjectArray ja;
+ // We only teardown and setup our 'locale' struct and other state
+ // when the Java-side locale changed. This is safe to do here
+ // without locking because we're always called from Java code
+ // synchronized on the class instance.
+ static jobject js_locale_previous = NULL;
+ static struct strftime_locale locale;
+ static jstring js_mon[12], js_month[12], js_wday[7], js_weekday[7];
+ static jstring js_X_fmt, js_x_fmt, js_c_fmt, js_am, js_pm, js_date_fmt;
+ Time t;
if (!java2time(env, &t, This)) return env->NewStringUTF("");
- ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField);
- for (int i = 0; i < 12; i++) {
- js_mon[i] = (jstring) env->GetObjectArrayElement(ja, i);
- locale.mon[i] = env->GetStringUTFChars(js_mon[i], NULL);
- }
+ jclass timeClass = g_timeClass;
+ jobject js_locale = (jobject) env->GetStaticObjectField(timeClass, g_localeField);
+ if (js_locale_previous != js_locale) {
+ if (js_locale_previous != NULL) {
+ // Free the old one.
+ for (int i = 0; i < 12; i++) {
+ env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]);
+ env->ReleaseStringUTFChars(js_month[i], locale.month[i]);
+ env->DeleteGlobalRef(js_mon[i]);
+ env->DeleteGlobalRef(js_month[i]);
+ }
+
+ for (int i = 0; i < 7; i++) {
+ env->ReleaseStringUTFChars(js_wday[i], locale.wday[i]);
+ env->ReleaseStringUTFChars(js_weekday[i], locale.weekday[i]);
+ env->DeleteGlobalRef(js_wday[i]);
+ env->DeleteGlobalRef(js_weekday[i]);
+ }
+
+ env->ReleaseStringUTFChars(js_X_fmt, locale.X_fmt);
+ env->ReleaseStringUTFChars(js_x_fmt, locale.x_fmt);
+ env->ReleaseStringUTFChars(js_c_fmt, locale.c_fmt);
+ env->ReleaseStringUTFChars(js_am, locale.am);
+ env->ReleaseStringUTFChars(js_pm, locale.pm);
+ env->ReleaseStringUTFChars(js_date_fmt, locale.date_fmt);
+ env->DeleteGlobalRef(js_X_fmt);
+ env->DeleteGlobalRef(js_x_fmt);
+ env->DeleteGlobalRef(js_c_fmt);
+ env->DeleteGlobalRef(js_am);
+ env->DeleteGlobalRef(js_pm);
+ env->DeleteGlobalRef(js_date_fmt);
+ }
+ js_locale_previous = js_locale;
- ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longMonthsField);
- for (int i = 0; i < 12; i++) {
- js_month[i] = (jstring) env->GetObjectArrayElement(ja, i);
- locale.month[i] = env->GetStringUTFChars(js_month[i], NULL);
- }
+ jobjectArray ja;
+ ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortMonthsField);
+ for (int i = 0; i < 12; i++) {
+ js_mon[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
+ locale.mon[i] = env->GetStringUTFChars(js_mon[i], NULL);
+ }
- ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField);
- for (int i = 0; i < 7; i++) {
- js_wday[i] = (jstring) env->GetObjectArrayElement(ja, i);
- locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL);
- }
+ ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longMonthsField);
+ for (int i = 0; i < 12; i++) {
+ js_month[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
+ locale.month[i] = env->GetStringUTFChars(js_month[i], NULL);
+ }
- ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField);
- for (int i = 0; i < 7; i++) {
- js_weekday[i] = (jstring) env->GetObjectArrayElement(ja, i);
- locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL);
- }
+ ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_shortWeekdaysField);
+ for (int i = 0; i < 7; i++) {
+ js_wday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
+ locale.wday[i] = env->GetStringUTFChars(js_wday[i], NULL);
+ }
- js_X_fmt = (jstring) env->GetStaticObjectField(timeClass, g_timeOnlyFormatField);
- locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL);
+ ja = (jobjectArray) env->GetStaticObjectField(timeClass, g_longWeekdaysField);
+ for (int i = 0; i < 7; i++) {
+ js_weekday[i] = (jstring) env->NewGlobalRef(env->GetObjectArrayElement(ja, i));
+ locale.weekday[i] = env->GetStringUTFChars(js_weekday[i], NULL);
+ }
+
+ js_X_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
+ timeClass, g_timeOnlyFormatField));
+ locale.X_fmt = env->GetStringUTFChars(js_X_fmt, NULL);
- js_x_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateOnlyFormatField);
- locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL);
+ js_x_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
+ timeClass, g_dateOnlyFormatField));
+ locale.x_fmt = env->GetStringUTFChars(js_x_fmt, NULL);
- js_c_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateTimeFormatField);
- locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL);
+ js_c_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
+ timeClass, g_dateTimeFormatField));
+ locale.c_fmt = env->GetStringUTFChars(js_c_fmt, NULL);
- js_am = (jstring) env->GetStaticObjectField(timeClass, g_amField);
- locale.am = env->GetStringUTFChars(js_am, NULL);
+ js_am = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
+ timeClass, g_amField));
+ locale.am = env->GetStringUTFChars(js_am, NULL);
- js_pm = (jstring) env->GetStaticObjectField(timeClass, g_pmField);
- locale.pm = env->GetStringUTFChars(js_pm, NULL);
+ js_pm = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
+ timeClass, g_pmField));
+ locale.pm = env->GetStringUTFChars(js_pm, NULL);
- js_date_fmt = (jstring) env->GetStaticObjectField(timeClass, g_dateCommandField);
- locale.date_fmt = env->GetStringUTFChars(js_date_fmt, NULL);
+ js_date_fmt = (jstring) env->NewGlobalRef(env->GetStaticObjectField(
+ timeClass, g_dateCommandField));
+ locale.date_fmt = env->GetStringUTFChars(js_date_fmt, NULL);
+ }
ACQUIRE_TIMEZONE(This, t)
@@ -243,23 +291,6 @@ static jstring android_text_format_Time_format(JNIEnv* env, jobject This,
env->ReleaseStringUTFChars(formatObject, format);
RELEASE_TIMEZONE(This, t)
- for (int i = 0; i < 12; i++) {
- env->ReleaseStringUTFChars(js_mon[i], locale.mon[i]);
- env->ReleaseStringUTFChars(js_month[i], locale.month[i]);
- }
-
- for (int i = 0; i < 7; i++) {
- env->ReleaseStringUTFChars(js_wday[i], locale.wday[i]);
- env->ReleaseStringUTFChars(js_weekday[i], locale.weekday[i]);
- }
-
- env->ReleaseStringUTFChars(js_X_fmt, locale.X_fmt);
- env->ReleaseStringUTFChars(js_x_fmt, locale.x_fmt);
- env->ReleaseStringUTFChars(js_c_fmt, locale.c_fmt);
- env->ReleaseStringUTFChars(js_am, locale.am);
- env->ReleaseStringUTFChars(js_pm, locale.pm);
- env->ReleaseStringUTFChars(js_date_fmt, locale.date_fmt);
-
return env->NewStringUTF(r.string());
}
@@ -307,7 +338,6 @@ static void android_text_format_Time_set(JNIEnv* env, jobject This, jlong millis
{
env->SetBooleanField(This, g_allDayField, JNI_FALSE);
Time t;
- if (!java2time(env, &t, This)) return;
ACQUIRE_TIMEZONE(This, t)
t.set(millis);
@@ -592,6 +622,8 @@ int register_android_text_format_Time(JNIEnv* env)
{
jclass timeClass = env->FindClass("android/text/format/Time");
+ g_timeClass = (jclass) env->NewGlobalRef(timeClass);
+
g_allDayField = env->GetFieldID(timeClass, "allDay", "Z");
g_secField = env->GetFieldID(timeClass, "second", "I");
g_minField = env->GetFieldID(timeClass, "minute", "I");
@@ -615,9 +647,9 @@ int register_android_text_format_Time(JNIEnv* env)
g_amField = env->GetStaticFieldID(timeClass, "sAm", "Ljava/lang/String;");
g_pmField = env->GetStaticFieldID(timeClass, "sPm", "Ljava/lang/String;");
g_dateCommandField = env->GetStaticFieldID(timeClass, "sDateCommand", "Ljava/lang/String;");
+ g_localeField = env->GetStaticFieldID(timeClass, "sLocale", "Ljava/util/Locale;");
return AndroidRuntime::registerNativeMethods(env, "android/text/format/Time", gMethods, NELEM(gMethods));
}
}; // namespace android
-
diff --git a/core/res/assets/webkit/nullplugin.png b/core/res/assets/webkit/nullPlugin.png
index 96a52e3..96a52e3 100644
--- a/core/res/assets/webkit/nullplugin.png
+++ b/core/res/assets/webkit/nullPlugin.png
Binary files differ
diff --git a/core/res/res/anim/zoom_ring_enter.xml b/core/res/res/anim/zoom_ring_enter.xml
new file mode 100644
index 0000000..13d89b2
--- /dev/null
+++ b/core/res/res/anim/zoom_ring_enter.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_in.xml
+**
+** Copyright 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.
+** 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@anim/decelerate_interpolator">
+ <scale android:fromXScale="0.75" android:toXScale="1.0"
+ android:fromYScale="0.75" android:toYScale="1.0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:duration="75" />
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="75" />
+</set>
diff --git a/core/res/res/anim/zoom_ring_exit.xml b/core/res/res/anim/zoom_ring_exit.xml
new file mode 100644
index 0000000..177a4c3
--- /dev/null
+++ b/core/res/res/anim/zoom_ring_exit.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/anim/fade_out.xml
+**
+** Copyright 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.
+** 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@anim/accelerate_interpolator">
+ <scale android:fromXScale="1.0" android:toXScale="0.75"
+ android:fromYScale="1.0" android:toYScale="0.75"
+ android:pivotX="50%" android:pivotY="50%"
+ android:duration="75" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:duration="75"/>
+</set>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 44da1d5..30b26fa 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -371,6 +371,8 @@
<attr name="spinnerItemStyle" format="reference" />
<!-- Default MapView style. -->
<attr name="mapViewStyle" format="reference" />
+ <!-- Default ZoomRing style. -->
+ <attr name="zoomRingStyle" format="reference" />
<!-- =================== -->
<!-- Preference styles -->
@@ -529,10 +531,23 @@
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_PASSWORD}. -->
<flag name="textPassword" value="0x00000081" />
+ <!-- Text that is a password that should be visible. Corresponds to
+ {@link android.text.InputType#TYPE_CLASS_TEXT} |
+ {@link android.text.InputType#TYPE_TEXT_VARIATION_VISIBLE_PASSWORD}. -->
+ <flag name="textVisiblePassword" value="0x00000091" />
<!-- Text that is being supplied as text in a web form. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. -->
- <flag name="textWebEditText" value="0x00000091" />
+ <flag name="textWebEditText" value="0x000000a1" />
+ <!-- Text that is filtering some other data. Corresponds to
+ {@link android.text.InputType#TYPE_CLASS_TEXT} |
+ {@link android.text.InputType#TYPE_TEXT_VARIATION_FILTER}. -->
+ <flag name="textFilter" value="0x000000b1" />
+ <!-- Text that is for phonetic pronunciation, such as a phonetic name
+ field in a contact entry. Corresponds to
+ {@link android.text.InputType#TYPE_CLASS_TEXT} |
+ {@link android.text.InputType#TYPE_TEXT_VARIATION_PHONETIC}. -->
+ <flag name="textPhonetic" value="0x000000c1" />
<!-- A numeric only field. Corresponds to
{@link android.text.InputType#TYPE_CLASS_NUMBER}. -->
<flag name="number" value="0x00000002" />
@@ -1920,6 +1935,34 @@
</attr>
<attr name="inputType" />
</declare-styleable>
+ <declare-styleable name="ZoomRing">
+ <!-- Defines the drawable used as the thumb. -->
+ <attr name="thumbDrawable" format="reference" />
+ <!-- Defines the distance of the thumb from the center of the ring. -->
+ <attr name="thumbDistance" format="dimension" />
+ <!-- Defines the distance from the center of the ring to the beginning of the track. -->
+ <attr name="trackInnerRadius" format="dimension" />
+ <!-- Defines the distance from the center of the ring to the end of the track. -->
+ <attr name="trackOuterRadius" format="dimension" />
+ <!-- Defines the drawable used as a hint to show which direction is zoom in. This should be
+ the same size as the zoom ring's asset. It will be rotated programmatically. -->
+ <attr name="zoomInArrowDrawable" format="reference" />
+ <!-- Defines the drawable used as a hint to show which direction is zoom out. This should be
+ the same size as the zoom ring's asset. It will be rotated programmatically. -->
+ <attr name="zoomOutArrowDrawable" format="reference" />
+ <!-- Defines the drawable that is laid on top of the zoom in arrow.
+ For example, this could be a +. -->
+ <attr name="zoomInArrowHintDrawable" format="reference" />
+ <!-- Defines the drawable that is laid on top of the zoom out arrow.
+ For example, this could be a -. -->
+ <attr name="zoomOutArrowHintDrawable" format="reference" />
+ <!-- Defines the distance of the zoom arrow hint from the center of the ring. -->
+ <attr name="zoomArrowHintDistance" format="dimension" />
+ <!-- Defines the offset of the zoom arrow hints from the thumb. Valid ranges are [0, 359] -->
+ <attr name="zoomArrowHintOffsetAngle" format="integer" />
+ <!-- Defines the drawable used to hint that the zoom ring can pan its owner. -->
+ <attr name="panningArrowsDrawable" format="reference" />
+ </declare-styleable>
<declare-styleable name="PopupWindow">
<attr name="popupBackground" format="reference|color" />
</declare-styleable>
@@ -2183,7 +2226,9 @@
</declare-styleable>
<declare-styleable name="LevelListDrawableItem">
+ <!-- The minimum level allowed for this item. -->
<attr name="minLevel" format="integer" />
+ <!-- The maximum level allowed for this item. -->
<attr name="maxLevel" format="integer" />
<attr name="drawable" />
</declare-styleable>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 346fa80..d54bca3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1565,7 +1565,7 @@
want a 24-hour clock.
You can remove the colon
or make other punctuation changes appropriate for your locale. -->
- <string name="twenty_four_hour_time_format" format="date"><xliff:g id="hour" example="23">H</xliff:g>:<xliff:g id="minute" example="59">mm</xliff:g></string>
+ <string name="twenty_four_hour_time_format" format="date"><xliff:g id="hour" example="23">HH</xliff:g>:<xliff:g id="minute" example="59">mm</xliff:g></string>
<!-- Quoted name for 12pm, lowercase -->
<string name="noon">"noon"</string>
@@ -2293,6 +2293,9 @@
<!-- Long label for a button on a full-screen input method for the "Next" action. -->
<string name="ime_action_next">Next</string>
+ <!-- Long label for a button on a full-screen input method for the "Done" action. -->
+ <string name="ime_action_done">Done</string>
+
<!-- Long label for a button on a full-screen input method for an unknown action. -->
<string name="ime_action_default">Execute</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 54eba62..9f4d82e 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -144,6 +144,11 @@
<item name="windowExitAnimation">@anim/search_bar_exit</item>
</style>
+ <!-- Standard animations for the zoom ring. -->
+ <style name="Animation.ZoomRing">
+ <item name="windowEnterAnimation">@anim/zoom_ring_enter</item>
+ <item name="windowExitAnimation">@anim/zoom_ring_exit</item>
+ </style>
<!-- Status Bar Styles -->
<style name="TextAppearance.StatusBarTitle">
@@ -456,6 +461,21 @@
<item name="android:shadowRadius">2.75</item>
</style>
+ <style name="Widget.ZoomRing">
+ <item name="android:background">@android:drawable/zoom_ring_track</item>
+ <item name="android:thumbDrawable">@android:drawable/zoom_ring_thumb</item>
+ <item name="android:thumbDistance">63dip</item>
+ <item name="android:trackInnerRadius">43dip</item>
+ <item name="android:trackOuterRadius">91dip</item>
+ <item name="android:zoomInArrowDrawable">@android:drawable/zoom_ring_thumb_plus_arrow_rotatable</item>
+ <item name="android:zoomOutArrowDrawable">@android:drawable/zoom_ring_thumb_minus_arrow_rotatable</item>
+ <item name="android:zoomInArrowHintDrawable">@android:drawable/zoom_ring_thumb_plus</item>
+ <item name="android:zoomOutArrowHintDrawable">@android:drawable/zoom_ring_thumb_minus</item>
+ <item name="android:zoomArrowHintDistance">69dip</item>
+ <item name="android:zoomArrowHintOffsetAngle">33</item>
+ <item name="android:panningArrowsDrawable">@android:drawable/zoom_ring_arrows</item>
+ </style>
+
<!-- Text Appearances -->
<eat-comment />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 01c46de..bde6b2a 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -162,6 +162,7 @@
<item name="spinnerItemStyle">@android:style/Widget.TextView.SpinnerItem</item>
<item name="dropDownHintAppearance">@android:style/TextAppearance.Widget.DropDownHint</item>
<item name="keyboardViewStyle">@android:style/Widget.KeyboardView</item>
+ <item name="zoomRingStyle">@android:style/Widget.ZoomRing</item>
<!-- Preference styles -->
<item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item>