diff options
Diffstat (limited to 'core')
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 Binary files differindex 96a52e3..96a52e3 100644 --- a/core/res/assets/webkit/nullplugin.png +++ b/core/res/assets/webkit/nullPlugin.png 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> |