diff --git a/api/current.xml b/api/current.xml index 55c775677370c..96998ed0e3508 100644 --- a/api/current.xml +++ b/api/current.xml @@ -27761,6 +27761,17 @@ visibility="public" > + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -not receive this through components declared diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4ae8b08847086..2dcb4830bf484 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -260,8 +260,9 @@ public class PackageParser { boolean assetError = true; try { assmgr = new AssetManager(); - if(assmgr.addAssetPath(mArchiveSourcePath) != 0) { - parser = assmgr.openXmlResourceParser("AndroidManifest.xml"); + int cookie = assmgr.addAssetPath(mArchiveSourcePath); + if(cookie != 0) { + parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml"); assetError = false; } else { Log.w(TAG, "Failed adding asset path:"+mArchiveSourcePath); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index fadcb352261ff..1c9173694d2bc 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -567,8 +567,8 @@ public final class AssetManager { /** * Add an additional set of assets to the asset manager. This can be - * either a directory or ZIP file. Not for use by applications. A - * zero return value indicates failure. + * either a directory or ZIP file. Not for use by applications. Returns + * the cookie of the added asset, or 0 on failure. * {@hide} */ public native final int addAssetPath(String path); diff --git a/core/java/android/emoji/EmojiFactory.java b/core/java/android/emoji/EmojiFactory.java new file mode 100644 index 0000000000000..389bd07d6b735 --- /dev/null +++ b/core/java/android/emoji/EmojiFactory.java @@ -0,0 +1,273 @@ +/* + * 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.emoji; + +import android.graphics.Bitmap; + +import java.lang.ref.WeakReference; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A class for the factories which produce Emoji (pictgram) images. + * This is intended to be used by IME, Email app, etc. + * There's no plan to make this public for now. + * @hide + */ +public final class EmojiFactory { + // private static final String LOG_TAG = "EmojiFactory"; + + private int sCacheSize = 100; + + // HashMap for caching Bitmap object. In order not to make an cache object + // blow up, we use LinkedHashMap with size limit. + private class CustomLinkedHashMap extends LinkedHashMap { + public CustomLinkedHashMap() { + // These magic numbers are gotten from the source code of + // LinkedHashMap.java and HashMap.java. + super(16, 0.75f, true); + } + + /* + * If size() becomes more than sCacheSize, least recently used cache + * is erased. + * @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry) + */ + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > sCacheSize; + } + } + + // A pointer to native EmojiFactory object. + private int mNativeEmojiFactory; + private String mName; + // Cache. + private Map> mCache; + + /** + * @noinspection UnusedDeclaration + */ + /* + * Private constructor that must received an already allocated native + * EmojiFactory int (pointer). + * + * This can be called from JNI code. + */ + private EmojiFactory(int nativeEmojiFactory, String name) { + mNativeEmojiFactory = nativeEmojiFactory; + mName = name; + mCache = new CustomLinkedHashMap>(); + } + + @Override + protected void finalize() throws Throwable { + try { + nativeDestructor(mNativeEmojiFactory); + } finally { + super.finalize(); + } + } + + public String name() { + return mName; + } + + /** + * Returns Bitmap object corresponding to the AndroidPua. + * + * Note that each Bitmap is cached by this class, which means that, if you modify a + * Bitmap object (using setPos() method), all same emoji Bitmap will be modified. + * If it is unacceptable, please copy the object before modifying it. + * + * @param pua A unicode codepoint. + * @return Bitmap object when this factory knows the Bitmap relevant to the codepoint. + * Otherwise null is returned. + */ + public synchronized Bitmap getBitmapFromAndroidPua(int pua) { + WeakReference cache = mCache.get(pua); + if (cache == null) { + Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua); + // There is no need to cache returned null, since in most cases it means there + // is no map from the AndroidPua to a specific image. In other words, it usually does + // not include the cost of creating Bitmap object. + if (ret != null) { + mCache.put(pua, new WeakReference(ret)); + } + return ret; + } else { + Bitmap tmp = cache.get(); + if (tmp == null) { + Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua); + mCache.put(pua, new WeakReference(ret)); + return ret; + } else { + return tmp; + } + } + } + + /** + * Returns Bitmap object corresponding to the vendor specified sjis. + * + * See comments in getBitmapFromAndroidPua(). + * + * @param sjis sjis code specific to each career(vendor) + * @return Bitmap object when this factory knows the Bitmap relevant to the code. Otherwise + * null is returned. + */ + public synchronized Bitmap getBitmapFromVendorSpecificSjis(char sjis) { + return getBitmapFromAndroidPua(getAndroidPuaFromVendorSpecificSjis(sjis)); + } + + /** + * Returns Bitmap object corresponding to the vendor specific Unicode. + * + * See comments in getBitmapFromAndroidPua(). + * + * @param vsp vendor specific PUA. + * @return Bitmap object when this factory knows the Bitmap relevant to the code. Otherwise + * null is returned. + */ + public synchronized Bitmap getBitmapFromVendorSpecificPua(int vsp) { + return getBitmapFromAndroidPua(getAndroidPuaFromVendorSpecificPua(vsp)); + } + + /** + * Returns Unicode PUA for Android corresponding to the vendor specific sjis. + * + * @param sjis vendor specific sjis + * @return Unicode PUA for Android, or -1 if there's no map for the sjis. + */ + public int getAndroidPuaFromVendorSpecificSjis(char sjis) { + return nativeGetAndroidPuaFromVendorSpecificSjis(mNativeEmojiFactory, sjis); + } + + /** + * Returns vendor specific sjis corresponding to the Unicode AndroidPua. + * + * @param pua Unicode PUA for Android, + * @return vendor specific sjis, or -1 if there's no map for the AndroidPua. + */ + public int getVendorSpecificSjisFromAndroidPua(int pua) { + return nativeGetVendorSpecificSjisFromAndroidPua(mNativeEmojiFactory, pua); + } + + /** + * Returns Unicode PUA for Android corresponding to the vendor specific Unicode. + * + * @param vsp vendor specific PUA. + * @return Unicode PUA for Android, or -1 if there's no map for the + * Unicode. + */ + public int getAndroidPuaFromVendorSpecificPua(int vsp) { + return nativeGetAndroidPuaFromVendorSpecificPua(mNativeEmojiFactory, vsp); + } + + public String getAndroidPuaFromVendorSpecificPua(String vspString) { + if (vspString == null) { + return null; + } + int minVsp = nativeGetMinimumVendorSpecificPua(mNativeEmojiFactory); + int maxVsp = nativeGetMaximumVendorSpecificPua(mNativeEmojiFactory); + int len = vspString.length(); + int[] codePoints = new int[vspString.codePointCount(0, len)]; + + int new_len = 0; + for (int i = 0; i < len; i = vspString.offsetByCodePoints(i, 1), new_len++) { + int codePoint = vspString.codePointAt(i); + if (minVsp <= codePoint && codePoint <= maxVsp) { + int newCodePoint = getAndroidPuaFromVendorSpecificPua(codePoint); + if (newCodePoint > 0) { + codePoints[new_len] = newCodePoint; + continue; + } + } + codePoints[new_len] = codePoint; + } + return new String(codePoints, 0, new_len); + } + + /** + * Returns vendor specific Unicode corresponding to the Unicode AndroidPua. + * + * @param pua Unicode PUA for Android, + * @return vendor specific sjis, or -1 if there's no map for the AndroidPua. + */ + public int getVendorSpecificPuaFromAndroidPua(int pua) { + return nativeGetVendorSpecificPuaFromAndroidPua(mNativeEmojiFactory, pua); + } + + public String getVendorSpecificPuaFromAndroidPua(String puaString) { + if (puaString == null) { + return null; + } + int minVsp = nativeGetMinimumAndroidPua(mNativeEmojiFactory); + int maxVsp = nativeGetMaximumAndroidPua(mNativeEmojiFactory); + int len = puaString.length(); + int[] codePoints = new int[puaString.codePointCount(0, len)]; + + int new_len = 0; + for (int i = 0; i < len; i = puaString.offsetByCodePoints(i, 1), new_len++) { + int codePoint = puaString.codePointAt(i); + if (minVsp <= codePoint && codePoint <= maxVsp) { + int newCodePoint = getVendorSpecificPuaFromAndroidPua(codePoint); + if (newCodePoint > 0) { + codePoints[new_len] = newCodePoint; + continue; + } + } + codePoints[new_len] = codePoint; + } + return new String(codePoints, 0, new_len); + } + + /** + * Constructs an instance of EmojiFactory corresponding to the name. + * + * @param class_name Name of the factory. This must include complete package name. + * @return A concrete EmojiFactory instance corresponding to factory_name. + * If factory_name is invalid, null is returned. + */ + public static native EmojiFactory newInstance(String class_name); + + /** + * Constructs an instance of available EmojiFactory. + * + * @return A concrete EmojiFactory instance. If there are several available + * EmojiFactory class, preferred one is chosen by the system. If there isn't, null + * is returned. + */ + public static native EmojiFactory newAvailableInstance(); + + // native methods + + private native void nativeDestructor(int factory); + private native Bitmap nativeGetBitmapFromAndroidPua(int nativeEmojiFactory, int AndroidPua); + private native int nativeGetAndroidPuaFromVendorSpecificSjis(int nativeEmojiFactory, + char sjis); + private native int nativeGetVendorSpecificSjisFromAndroidPua(int nativeEmojiFactory, + int pua); + private native int nativeGetAndroidPuaFromVendorSpecificPua(int nativeEmojiFactory, + int vsp); + private native int nativeGetVendorSpecificPuaFromAndroidPua(int nativeEmojiFactory, + int pua); + private native int nativeGetMaximumVendorSpecificPua(int nativeEmojiFactory); + private native int nativeGetMinimumVendorSpecificPua(int nativeEmojiFactory); + private native int nativeGetMaximumAndroidPua(int nativeEmojiFactory); + private native int nativeGetMinimumAndroidPua(int nativeEmojiFactory); +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index f1e613eea5dc6..34d569592fd3d 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1377,9 +1377,11 @@ public class InputMethodService extends AbstractInputMethodService { if (mExtractedToken != token) { return; } - if (mExtractEditText != null && text != null) { - mExtractedText = text; - mExtractEditText.setExtractedText(text); + if (text != null) { + if (mExtractEditText != null) { + mExtractedText = text; + mExtractEditText.setExtractedText(text); + } } } diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java index 1454089e70baa..d11a9c52f86dd 100644 --- a/core/java/android/provider/Checkin.java +++ b/core/java/android/provider/Checkin.java @@ -132,8 +132,6 @@ public final class Checkin { BROWSER_SNAP_CENTER, BROWSER_TEXT_SIZE_CHANGE, BROWSER_ZOOM_OVERVIEW, - BROWSER_ZOOM_RING, - BROWSER_ZOOM_RING_DRAG, CRASHES_REPORTED, CRASHES_TRUNCATED, ELAPSED_REALTIME_SEC, diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 87153cf340fa8..6f5513a369ae9 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -237,7 +237,28 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public void run() { boolean res = (enableNative() == 0); if (res) { - mEventLoop.start(); + int retryCount = 2; + boolean running = false; + while ((retryCount-- > 0) && !running) { + mEventLoop.start(); + // it may take a momement for the other thread to do its + // thing. Check periodically for a while. + int pollCount = 5; + while ((pollCount-- > 0) && !running) { + if (mEventLoop.isEventLoopRunning()) { + running = true; + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + } + } + if (!running) { + log("bt EnableThread giving up"); + res = false; + disableNative(); + } } if (mEnableCallback != null) { @@ -254,14 +275,20 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { persistBluetoothOnSetting(true); } mIsDiscovering = false; - Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION); mBondState.loadBondState(); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000); // Update mode mEventLoop.onModeChanged(getModeNative()); } + Intent intent = null; + if (res) { + intent = new Intent(BluetoothIntent.ENABLED_ACTION); + } else { + intent = new Intent(BluetoothIntent.DISABLED_ACTION); + } + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + mEnableThread = null; } } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 8b09583492d4b..e4ebcca4d0805 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -44,6 +44,7 @@ class BluetoothEventLoop { private int mNativeData; private Thread mThread; + private boolean mStarted; private boolean mInterrupted; private HashMap mPasskeyAgentRequestData; private HashMap mGetRemoteServiceChannelCallbacks; @@ -122,14 +123,18 @@ class BluetoothEventLoop { public void run() { try { if (setUpEventLoopNative()) { + mStarted = true; while (!mInterrupted) { waitForAndDispatchEvent(0); sleep(500); } - tearDownEventLoopNative(); } + // tear down even in the error case to clean + // up anything we started to setup + tearDownEventLoopNative(); } catch (InterruptedException e) { } if (DBG) log("Event Loop thread finished"); + mThread = null; } }; if (DBG) log("Starting Event Loop thread"); @@ -152,7 +157,7 @@ class BluetoothEventLoop { } public synchronized boolean isEventLoopRunning() { - return mThread != null; + return mThread != null && mStarted; } /*package*/ void onModeChanged(String bluezMode) { diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 8aa49afe45c5b..17c7a6cd8c880 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() && !widget.didTouchFocusSelectAll()) { + if (widget.isFocused() && !widget.didTouchFocusSelect()) { if (event.getAction() == MotionEvent.ACTION_UP) { int x = (int) event.getX(); int y = (int) event.getY(); diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index 39ad97689a5dc..61ec67fce237c 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -159,13 +159,21 @@ public abstract class MetaKeyKeyListener { /** * Returns true if this object is one that this class would use to - * keep track of meta state in the specified text. + * keep track of any meta state in the specified text. */ public static boolean isMetaTracker(CharSequence text, Object what) { return what == CAP || what == ALT || what == SYM || what == SELECTING; } + /** + * Returns true if this object is one that this class would use to + * keep track of the selecting meta state in the specified text. + */ + public static boolean isSelectingMetaTracker(CharSequence text, Object what) { + return what == SELECTING; + } + private static void adjust(Spannable content, Object what) { int current = content.getSpanFlags(what); diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 88ff3c5a69382..f7ac522aba6e0 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -498,7 +498,6 @@ public class GestureDetector { if (mIsDoubleTapping) { // Finally, give the up event of the double-tap handled |= mDoubleTapListener.onDoubleTapEvent(ev); - mIsDoubleTapping = false; } else if (mInLongPress) { mHandler.removeMessages(TAP); mInLongPress = false; @@ -520,6 +519,7 @@ public class GestureDetector { mPreviousUpEvent = MotionEvent.obtain(ev); mVelocityTracker.recycle(); mVelocityTracker = null; + mIsDoubleTapping = false; mHandler.removeMessages(SHOW_PRESS); mHandler.removeMessages(LONG_PRESS); break; @@ -529,6 +529,7 @@ public class GestureDetector { mHandler.removeMessages(TAP); mVelocityTracker.recycle(); mVelocityTracker = null; + mIsDoubleTapping = false; mStillDown = false; if (mInLongPress) { mInLongPress = false; diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index cc3563c40f8da..841066c7ba525 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -26,9 +26,6 @@ public class HapticFeedbackConstants { public static final int LONG_PRESS = 0; - /** @hide pending API council */ - public static final int ZOOM_RING_TICK = 1; - /** * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the setting in the diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3e762f570fc95..d78320a01b87e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3663,8 +3663,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * would make sense to automatically display a soft input window for * it. Subclasses should override this if they implement * {@link #onCreateInputConnection(EditorInfo)} to return true if - * a call on that method would return a non-null InputConnection. The - * default implementation always returns false. + * a call on that method would return a non-null InputConnection, and + * they are really a first-class editor that the user would normally + * start typing on when the go into a window containing your view. + * + *

The default implementation always returns false. This does + * not mean that its {@link #onCreateInputConnection(EditorInfo)} + * will not be called or the user can not otherwise perform edits on your + * view; it is just a hint to the system that this is not the primary + * purpose of this view. * * @return Returns true if this view is a text editor, else false. */ @@ -3688,6 +3695,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback { return null; } + /** + * Called by the {@link android.view.inputmethod.InputMethodManager} + * when a view who is not the current + * input connection target is trying to make a call on the manager. The + * default implementation returns false; you can override this to return + * true for certain views if you are performing InputConnection proxying + * to them. + * @param view The View that is making the InputMethodManager call. + * @return Return true to allow the call, false to reject. + */ + public boolean checkInputConnectionProxy(View view) { + return false; + } + /** * Show the context menu for this view. It is not safe to hold on to the * menu after returning from this method. @@ -5016,7 +5037,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL ? getVerticalScrollbarWidth() : 0; - scrollBar.setBounds(scrollX + (mPaddingLeft & inside) + getScrollBarPaddingLeft(), top, + scrollBar.setBounds(scrollX + (mPaddingLeft & inside), top, scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap, top + size); scrollBar.setParameters( computeHorizontalScrollRange(), @@ -6503,32 +6524,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback { return mBGDrawable; } - private int getScrollBarPaddingLeft() { - // TODO: Deal with RTL languages - return 0; - } - - /* - * Returns the pixels occupied by the vertical scrollbar, if not overlaid - */ - private int getScrollBarPaddingRight() { - // TODO: Deal with RTL languages - if ((mViewFlags & SCROLLBARS_VERTICAL) == 0) { - return 0; - } - return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getVerticalScrollbarWidth(); - } - - /* - * Returns the pixels occupied by the horizontal scrollbar, if not overlaid - */ - private int getScrollBarPaddingBottom() { - if ((mViewFlags & SCROLLBARS_HORIZONTAL) == 0) { - return 0; - } - return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getHorizontalScrollbarHeight(); - } - /** * Sets the padding. The view may add on the space required to display * the scrollbars, depending on the style and visibility of the scrollbars. @@ -6552,7 +6547,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback { mUserPaddingRight = right; mUserPaddingBottom = bottom; - if (mPaddingLeft != left + getScrollBarPaddingLeft()) { + final int viewFlags = mViewFlags; + + // Common case is there are no scroll bars. + if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) { + // TODO: Deal with RTL languages to adjust left padding instead of right. + if ((viewFlags & SCROLLBARS_VERTICAL) != 0) { + right += (viewFlags & SCROLLBARS_INSET_MASK) == 0 + ? 0 : getVerticalScrollbarWidth(); + } + if ((viewFlags & SCROLLBARS_HORIZONTAL) == 0) { + bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0 + ? 0 : getHorizontalScrollbarHeight(); + } + } + + if (mPaddingLeft != left) { changed = true; mPaddingLeft = left; } @@ -6560,13 +6570,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback { changed = true; mPaddingTop = top; } - if (mPaddingRight != right + getScrollBarPaddingRight()) { + if (mPaddingRight != right) { changed = true; - mPaddingRight = right + getScrollBarPaddingRight(); + mPaddingRight = right; } - if (mPaddingBottom != bottom + getScrollBarPaddingBottom()) { + if (mPaddingBottom != bottom) { changed = true; - mPaddingBottom = bottom + getScrollBarPaddingBottom(); + mPaddingBottom = bottom; } if (changed) { diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 52b4107348082..deca910c6fbfb 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -386,7 +386,14 @@ public class BaseInputConnection implements InputConnection { // anyway. return true; } - Selection.setSelection(content, start, end); + if (start == end && MetaKeyKeyListener.getMetaState(content, + MetaKeyKeyListener.META_SELECTING) != 0) { + // If we are in selection mode, then we want to extend the + // selection instead of replacing it. + Selection.extendSelection(content, start); + } else { + Selection.setSelection(content, start, end); + } return true; } diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 1c0d42a2c8ef6..b00e565956175 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -33,43 +33,49 @@ public class EditorInfo implements InputType, Parcelable { public static final int IME_MASK_ACTION = 0x000000ff; /** - * Bits of {@link #IME_MASK_ACTION}: there is no special action - * associated with this editor. + * Bits of {@link #IME_MASK_ACTION}: no specific action has been + * associated with this editor, let the editor come up with its own if + * it can. */ - public static final int IME_ACTION_NONE = 0x00000000; + public static final int IME_ACTION_UNSPECIFIED = 0x00000000; + + /** + * Bits of {@link #IME_MASK_ACTION}: there is no available action. + */ + public static final int IME_ACTION_NONE = 0x00000001; /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go" * operation to take the user to the target of the text they typed. * Typically used, for example, when entering a URL. */ - public static final int IME_ACTION_GO = 0x00000001; + public static final int IME_ACTION_GO = 0x00000002; /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search" * operation, taking the user to the results of searching for the text * the have typed (in whatever context is appropriate). */ - public static final int IME_ACTION_SEARCH = 0x00000002; + public static final int IME_ACTION_SEARCH = 0x00000003; /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send" * operation, delivering the text to its target. This is typically used * when composing a message. */ - public static final int IME_ACTION_SEND = 0x00000003; + public static final int IME_ACTION_SEND = 0x00000004; /** * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next" * operation, taking the user to the next field that will accept text. */ - public static final int IME_ACTION_NEXT = 0x00000004; + public static final int IME_ACTION_NEXT = 0x00000005; /** * 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; + public static final int IME_ACTION_DONE = 0x00000006; /** * Flag of {@link #imeOptions}: used in conjunction with @@ -82,21 +88,15 @@ public class EditorInfo implements InputType, Parcelable { public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000; /** - * Generic non-special type for {@link #imeOptions}. + * Generic unspecified type for {@link #imeOptions}. */ - public static final int IME_NORMAL = 0x00000000; - - /** - * Special code for when the ime option has been undefined. This is not - * used with the EditorInfo structure, but can be used elsewhere. - */ - public static final int IME_UNDEFINED = 0x80000000; + public static final int IME_NULL = 0x00000000; /** * Extended type information for the editor, to help the IME better * integrate with it. */ - public int imeOptions = IME_NORMAL; + public int imeOptions = IME_NULL; /** * A string supplying additional information options that are diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java index e5d3caeef1ced..c2851d6ca6632 100644 --- a/core/java/android/view/inputmethod/ExtractedText.java +++ b/core/java/android/view/inputmethod/ExtractedText.java @@ -54,6 +54,11 @@ public class ExtractedText implements Parcelable { */ public static final int FLAG_SINGLE_LINE = 0x0001; + /** + * Bit for {@link #flags}: set if the editor is currently in selection mode. + */ + public static final int FLAG_SELECTING = 0x0002; + /** * Additional bit flags of information about the edited text. */ @@ -72,7 +77,7 @@ public class ExtractedText implements Parcelable { dest.writeInt(partialEndOffset); dest.writeInt(selectionStart); dest.writeInt(selectionEnd); - dest.writeInt(flags); + dest.writeInt(this.flags); } /** diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 32cce35608d41..8b6831e655395 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -17,7 +17,6 @@ package android.view.inputmethod; import android.os.Bundle; -import android.text.Spanned; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -135,7 +134,7 @@ public interface InputConnection { * @return Returns true on success, false if the input connection is no longer * valid. */ - boolean deleteSurroundingText(int leftLength, int rightLength); + public boolean deleteSurroundingText(int leftLength, int rightLength); /** * Set composing text around the current cursor position with the given text, diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java new file mode 100644 index 0000000000000..e3d5e62151b10 --- /dev/null +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007-2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.view.inputmethod; + +import android.os.Bundle; +import android.view.KeyEvent; + +/** + *

Wrapper class for proxying calls to another InputConnection. Subclass + * and have fun! + */ +public class InputConnectionWrapper implements InputConnection { + private final InputConnection mTarget; + + public InputConnectionWrapper(InputConnection target) { + mTarget = target; + } + + public CharSequence getTextBeforeCursor(int n, int flags) { + return mTarget.getTextBeforeCursor(n, flags); + } + + public CharSequence getTextAfterCursor(int n, int flags) { + return mTarget.getTextAfterCursor(n, flags); + } + + public int getCursorCapsMode(int reqModes) { + return mTarget.getCursorCapsMode(reqModes); + } + + public ExtractedText getExtractedText(ExtractedTextRequest request, + int flags) { + return mTarget.getExtractedText(request, flags); + } + + public boolean deleteSurroundingText(int leftLength, int rightLength) { + return mTarget.deleteSurroundingText(leftLength, rightLength); + } + + public boolean setComposingText(CharSequence text, int newCursorPosition) { + return mTarget.setComposingText(text, newCursorPosition); + } + + public boolean finishComposingText() { + return mTarget.finishComposingText(); + } + + public boolean commitText(CharSequence text, int newCursorPosition) { + return mTarget.commitText(text, newCursorPosition); + } + + public boolean commitCompletion(CompletionInfo text) { + return mTarget.commitCompletion(text); + } + + public boolean setSelection(int start, int end) { + return mTarget.setSelection(start, end); + } + + public boolean performEditorAction(int editorAction) { + return mTarget.performEditorAction(editorAction); + } + + public boolean performContextMenuAction(int id) { + return mTarget.performContextMenuAction(id); + } + + public boolean beginBatchEdit() { + return mTarget.beginBatchEdit(); + } + + public boolean endBatchEdit() { + return mTarget.endBatchEdit(); + } + + public boolean sendKeyEvent(KeyEvent event) { + return mTarget.sendKeyEvent(event); + } + + public boolean clearMetaKeyStates(int states) { + return mTarget.clearMetaKeyStates(states); + } + + public boolean reportFullscreenMode(boolean enabled) { + return mTarget.reportFullscreenMode(enabled); + } + + public boolean performPrivateCommand(String action, Bundle data) { + return mTarget.performPrivateCommand(action, data); + } +} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 916ffea6a7a82..7f2142eda646d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -529,7 +529,10 @@ public final class InputMethodManager { public boolean isActive(View view) { checkFocus(); synchronized (mH) { - return mServedView == view && mCurrentTextBoxAttribute != null; + return (mServedView == view + || (mServedView != null + && mServedView.checkInputConnectionProxy(view))) + && mCurrentTextBoxAttribute != null; } } @@ -620,7 +623,8 @@ public final class InputMethodManager { public void displayCompletions(View view, CompletionInfo[] completions) { checkFocus(); synchronized (mH) { - if (mServedView != view) { + if (mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) { return; } @@ -637,7 +641,8 @@ public final class InputMethodManager { public void updateExtractedText(View view, int token, ExtractedText text) { checkFocus(); synchronized (mH) { - if (mServedView != view) { + if (mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) { return; } @@ -730,7 +735,8 @@ public final class InputMethodManager { ResultReceiver resultReceiver) { checkFocus(); synchronized (mH) { - if (mServedView != view) { + if (mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) { return false; } @@ -871,7 +877,8 @@ public final class InputMethodManager { public void restartInput(View view) { checkFocus(); synchronized (mH) { - if (mServedView != view) { + if (mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) { return; } @@ -1032,7 +1039,7 @@ public final class InputMethodManager { if (DEBUG) Log.v(TAG, "focusOut: " + view + " mServedView=" + mServedView + " winFocus=" + view.hasWindowFocus()); - if (mServedView == view) { + if (mServedView != view) { // The following code would auto-hide the IME if we end up // with no more views with focus. This can happen, however, // whenever we go into touch mode, so it ends up hiding @@ -1129,8 +1136,9 @@ public final class InputMethodManager { try { final boolean isTextEditor = focusedView != null && focusedView.onCheckIsTextEditor(); - mService.windowGainedFocus(mClient, focusedView != null, - isTextEditor, softInputMode, first, windowFlags); + mService.windowGainedFocus(mClient, rootView.getWindowToken(), + focusedView != null, isTextEditor, softInputMode, first, + windowFlags); } catch (RemoteException e) { } } @@ -1150,8 +1158,9 @@ public final class InputMethodManager { int candidatesStart, int candidatesEnd) { checkFocus(); synchronized (mH) { - if (mServedView != view || mCurrentTextBoxAttribute == null - || mCurMethod == null) { + if ((mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) + || mCurrentTextBoxAttribute == null || mCurMethod == null) { return; } @@ -1189,8 +1198,9 @@ public final class InputMethodManager { public void updateCursor(View view, int left, int top, int right, int bottom) { checkFocus(); synchronized (mH) { - if (mServedView != view || mCurrentTextBoxAttribute == null - || mCurMethod == null) { + if ((mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) + || mCurrentTextBoxAttribute == null || mCurMethod == null) { return; } @@ -1223,7 +1233,8 @@ public final class InputMethodManager { public void sendAppPrivateCommand(View view, String action, Bundle data) { checkFocus(); synchronized (mH) { - if ((view != null && mServedView != view) + if ((mServedView != view && (mServedView == null + || !mServedView.checkInputConnectionProxy(view))) || mCurrentTextBoxAttribute == null || mCurMethod == null) { return; } diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java index 39806dc4b299d..efc131f526279 100644 --- a/core/java/android/webkit/TextDialog.java +++ b/core/java/android/webkit/TextDialog.java @@ -32,8 +32,6 @@ import android.text.Spannable; import android.text.TextPaint; import android.text.TextUtils; import android.text.method.MovementMethod; -import android.text.method.PasswordTransformationMethod; -import android.text.method.TextKeyListener; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.view.KeyCharacterMap; @@ -475,15 +473,10 @@ import java.util.ArrayList; * @param inPassword True if the textfield is a password field. */ /* package */ void setInPassword(boolean inPassword) { - PasswordTransformationMethod method; if (inPassword) { - method = PasswordTransformationMethod.getInstance(); - setInputType(EditorInfo.TYPE_CLASS_TEXT|EditorInfo. + setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo. TYPE_TEXT_VARIATION_PASSWORD); - } else { - method = null; } - setTransformationMethod(method); } /* package */ void setMaxLength(int maxLength) { @@ -545,20 +538,13 @@ import java.util.ArrayList; * removing the password input type. */ public void setSingleLine(boolean single) { - if (mSingle != single) { - TextKeyListener.Capitalize cap; - int inputType = EditorInfo.TYPE_CLASS_TEXT; - if (single) { - cap = TextKeyListener.Capitalize.NONE; - } else { - cap = TextKeyListener.Capitalize.SENTENCES; - inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; - } - setKeyListener(TextKeyListener.getInstance(!single, cap)); - mSingle = single; - setHorizontallyScrolling(single); - setInputType(inputType); + int inputType = EditorInfo.TYPE_CLASS_TEXT; + if (!single) { + inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; } + mSingle = single; + setHorizontallyScrolling(single); + setInputType(inputType); } /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 91795a3dc25f7..dc39b90bcc3c6 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -73,7 +73,6 @@ import android.widget.Scroller; import android.widget.Toast; import android.widget.ZoomButtonsController; import android.widget.ZoomControls; -import android.widget.ZoomRingController; import android.widget.FrameLayout; import android.widget.AdapterView.OnItemClickListener; @@ -239,8 +238,8 @@ public class WebView extends AbsoluteLayout */ VelocityTracker mVelocityTracker; - private static boolean mShowZoomRingTutorial = true; - private static final int ZOOM_RING_TUTORIAL_DURATION = 3000; + private static boolean mShowZoomTutorial = true; + private static final int ZOOM_TUTORIAL_DURATION = 3000; /** * Touch mode @@ -265,11 +264,6 @@ public class WebView extends AbsoluteLayout // Whether to forward the touch events to WebCore private boolean mForwardTouchEvents = false; - // Whether we are in the drag tap mode, which exists starting at the second - // tap's down, through its move, and includes its up. These events should be - // given to the method on the zoom controller. - private boolean mInZoomTapDragMode = false; - // Whether to prevent drag during touch. The initial value depends on // mForwardTouchEvents. If WebCore wants touch events, we assume it will // take control of touch events unless it says no for touch down event. @@ -361,7 +355,7 @@ public class WebView extends AbsoluteLayout private static final int UPDATE_TEXT_ENTRY_ADAPTER = 6; private static final int SWITCH_TO_ENTER = 7; private static final int RESUME_WEBCORE_UPDATE = 8; - private static final int DISMISS_ZOOM_RING_TUTORIAL = 9; + private static final int DISMISS_ZOOM_TUTORIAL = 9; //! arg1=x, arg2=y static final int SCROLL_TO_MSG_ID = 10; @@ -417,11 +411,6 @@ public class WebView extends AbsoluteLayout // width which view is considered to be fully zoomed out static final int ZOOM_OUT_WIDTH = 1024; - private static final float MAX_ZOOM_RING_ANGLE = (float) (Math.PI * 2 / 3); - private static final int ZOOM_RING_STEPS = 4; - private static final float ZOOM_RING_ANGLE_UNIT = MAX_ZOOM_RING_ANGLE - / ZOOM_RING_STEPS; - private static final float DEFAULT_MAX_ZOOM_SCALE = 2; private static final float DEFAULT_MIN_ZOOM_SCALE = (float) 1/3; // scale limit, which can be set through viewport meta tag in the web page @@ -560,146 +549,52 @@ public class WebView extends AbsoluteLayout } private ZoomButtonsController mZoomButtonsController; + private ImageView mZoomOverviewButton; - private ZoomRingController mZoomRingController; - private ImageView mZoomRingOverview; - private Animation mZoomRingOverviewExitAnimation; - - // These keep track of the center point of the zoom ring. They are used to + // These keep track of the center point of the zoom. They are used to // determine the point around which we should zoom. private float mZoomCenterX; private float mZoomCenterY; - private ZoomRingController.OnZoomListener mZoomListener = - new ZoomRingController.OnZoomListener() { - - private float mClockwiseBound; - private float mCounterClockwiseBound; - private float mStartScale; + private ZoomButtonsController.OnZoomListener mZoomListener = + new ZoomButtonsController.OnZoomListener() { public void onCenter(int x, int y) { // Don't translate when the control is invoked, hence we do nothing // in this callback } - public void onBeginPan() { - setZoomOverviewVisible(false); + public void onOverview() { + mZoomButtonsController.setVisible(false); + zoomScrollOut(); if (mLogEvent) { Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_RING_DRAG, 1, 0.0); + Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0); } } - public boolean onPan(int deltaX, int deltaY) { - return pinScrollBy(deltaX, deltaY, false, 0); - } - - public void onEndPan() { - } - public void onVisibilityChanged(boolean visible) { if (visible) { switchOutDrawHistory(); - if (mMaxZoomScale - 1 > ZOOM_RING_STEPS * 0.01f) { - mClockwiseBound = (float) (2 * Math.PI - MAX_ZOOM_RING_ANGLE); - } else { - mClockwiseBound = (float) (2 * Math.PI); - } - mZoomRingController.setThumbClockwiseBound(mClockwiseBound); - if (1 - mMinZoomScale > ZOOM_RING_STEPS * 0.01f) { - mCounterClockwiseBound = MAX_ZOOM_RING_ANGLE; - } else { - mCounterClockwiseBound = 0; - } - mZoomRingController - .setThumbCounterclockwiseBound(mCounterClockwiseBound); - float angle = 0f; - if (mActualScale > 1 && mClockwiseBound < (float) (2 * Math.PI)) { - angle = -(float) Math.round(ZOOM_RING_STEPS - * (mActualScale - 1) / (mMaxZoomScale - 1)) - / ZOOM_RING_STEPS; - } else if (mActualScale < 1 && mCounterClockwiseBound > 0) { - angle = (float) Math.round(ZOOM_RING_STEPS - * (1 - mActualScale) / (1 - mMinZoomScale)) - / ZOOM_RING_STEPS; - } - mZoomRingController.setThumbAngle(angle * MAX_ZOOM_RING_ANGLE); - - // Don't show a thumb if the user cannot zoom - mZoomRingController.setThumbVisible(mMinZoomScale != mMaxZoomScale); - - // Show the zoom overview tab on the ring - setZoomOverviewVisible(true); - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_RING, 1, 0.0); - } + mZoomButtonsController.setOverviewVisible(true); + updateButtonsEnabled(); } } - public void onBeginDrag() { - mPreviewZoomOnly = true; - mStartScale = mActualScale; - setZoomOverviewVisible(false); - } - - public void onEndDrag() { - mPreviewZoomOnly = false; - if (mLogEvent) { - EventLog.writeEvent(EVENT_LOG_ZOOM_LEVEL_CHANGE, - (int) mStartScale * 100, (int) mActualScale * 100, - System.currentTimeMillis()); - } - setNewZoomScale(mActualScale, true); + private void updateButtonsEnabled() { + mZoomButtonsController.setZoomInEnabled(mActualScale < mMaxZoomScale); + mZoomButtonsController.setZoomOutEnabled(mActualScale > mMinZoomScale); } - public boolean onDragZoom(int deltaZoomLevel, int centerX, - int centerY, float startAngle, float curAngle) { - if (deltaZoomLevel < 0 - && Math.abs(mActualScale - mMinZoomScale) < 0.01f - || deltaZoomLevel > 0 - && Math.abs(mActualScale - mMaxZoomScale) < 0.01f - || deltaZoomLevel == 0) { - return false; - } - mZoomCenterX = (float) centerX; - mZoomCenterY = (float) centerY; - - float scale = 1.0f; - // curAngle is [0, 2 * Math.PI) - if (curAngle < (float) Math.PI) { - if (curAngle >= mCounterClockwiseBound) { - scale = mMinZoomScale; - } else { - scale = 1 - (float) Math.round(curAngle - / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS - * (1 - mMinZoomScale); - } - } else { - if (curAngle <= mClockwiseBound) { - scale = mMaxZoomScale; - } else { - scale = 1 + (float) Math.round( - ((float) 2 * Math.PI - curAngle) - / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS - * (mMaxZoomScale - 1); - } - } - zoomWithPreview(scale); - return true; - } - - public void onSimpleZoom(boolean zoomIn, int centerX, int centerY) { - mZoomCenterX = (float) centerX; - mZoomCenterY = (float) centerY; - + public void onZoom(boolean zoomIn) { if (zoomIn) { zoomIn(); } else { zoomOut(); } + + updateButtonsEnabled(); } - }; /** @@ -738,34 +633,8 @@ public class WebView extends AbsoluteLayout mFocusData.mX = 0; mFocusData.mY = 0; mScroller = new Scroller(context); - mZoomRingController = new ZoomRingController(context, this); - mZoomRingController.setCallback(mZoomListener); - mZoomRingController.setTrackDrawable( - com.android.internal.R.drawable.zoom_ring_track_absolute); - 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); - mZoomButtonsController.setCallback(new ZoomButtonsController.OnZoomListener() { - public void onCenter(int x, int y) { - mZoomListener.onCenter(x, y); - } - - public void onOverview() { - mZoomButtonsController.setVisible(false); - zoomScrollOut(); - } - - public void onVisibilityChanged(boolean visible) { - mZoomListener.onVisibilityChanged(visible); - } - - public void onZoom(boolean zoomIn) { - mZoomListener.onSimpleZoom(zoomIn, getWidth() / 2, getHeight() / 2); - } - }); + mZoomButtonsController.setCallback(mZoomListener); } private void init() { @@ -783,67 +652,6 @@ public class WebView extends AbsoluteLayout mDoubleTapSlopSquare = doubleTapslop * doubleTapslop; } - private void createZoomRingOverviewTab() { - Context context = getContext(); - - mZoomRingOverviewExitAnimation = AnimationUtils.loadAnimation(context, - com.android.internal.R.anim.fade_out); - - mZoomRingOverview = new ImageView(context); - mZoomRingOverview.setBackgroundResource( - com.android.internal.R.drawable.zoom_ring_overview_tab); - mZoomRingOverview.setImageResource(com.android.internal.R.drawable.btn_zoom_page); - - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.CENTER); - // TODO: magic constant that's based on the zoom ring radius + some offset - lp.topMargin = 200; - mZoomRingOverview.setLayoutParams(lp); - mZoomRingOverview.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - // Hide the zoom ring - mZoomRingController.setVisible(false); - if (mLogEvent) { - Checkin.updateStats(mContext.getContentResolver(), - Checkin.Stats.Tag.BROWSER_ZOOM_OVERVIEW, 1, 0.0); - } - zoomScrollOut(); - }}); - - // Measure the overview View to figure out its height - mZoomRingOverview.forceLayout(); - mZoomRingOverview.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - - ViewGroup container = mZoomRingController.getContainer(); - // Find the index of the zoom ring in the container - View zoomRing = container.findViewById(mZoomRingController.getZoomRingId()); - int zoomRingIndex; - for (zoomRingIndex = container.getChildCount() - 1; zoomRingIndex >= 0; zoomRingIndex--) { - if (container.getChildAt(zoomRingIndex) == zoomRing) break; - } - // Add the overview tab below the zoom ring (so we don't steal its events) - container.addView(mZoomRingOverview, zoomRingIndex); - // Since we use margins to adjust the vertical placement of the tab, the widget - // ends up getting clipped off. Ensure the container is big enough for - // us. - int myHeight = mZoomRingOverview.getMeasuredHeight() + lp.topMargin / 2; - // Multiplied by 2 b/c the zoom ring needs to be centered on the screen - container.setMinimumHeight(myHeight * 2); - } - - private void setZoomOverviewVisible(boolean visible) { - int newVisibility = visible ? View.VISIBLE : View.INVISIBLE; - if (mZoomRingOverview.getVisibility() == newVisibility) return; - - if (!visible) { - mZoomRingOverview.startAnimation(mZoomRingOverviewExitAnimation); - } - mZoomRingOverview.setVisibility(newVisibility); - } - /* package */ boolean onSavePassword(String schemePlusHost, String username, String password, final Message resumeMsg) { boolean rVal = false; @@ -2988,6 +2796,7 @@ public class WebView extends AbsoluteLayout * @param end End of selection to delete. */ /* package */ void deleteSelection(int start, int end) { + mTextGeneration++; mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, start, end, new WebViewCore.FocusData(mFocusData)); } @@ -3452,8 +3261,7 @@ public class WebView extends AbsoluteLayout p.setOnHierarchyChangeListener(null); } - // Clean up the zoom ring - mZoomRingController.setVisible(false); + // Clean up the zoom controller mZoomButtonsController.setVisible(false); } @@ -3565,9 +3373,7 @@ public class WebView extends AbsoluteLayout @Override protected void onSizeChanged(int w, int h, int ow, int oh) { super.onSizeChanged(w, h, ow, oh); - // Center zooming to the center of the screen. This is appropriate for - // this case of zooming, and it also sets us up properly if we remove - // the new zoom ring controller + // Center zooming to the center of the screen. mZoomCenterX = getViewWidth() * .5f; mZoomCenterY = getViewHeight() * .5f; @@ -3641,13 +3447,13 @@ public class WebView extends AbsoluteLayout return false; } - if (mShowZoomRingTutorial && getSettings().supportZoom() - && (mMaxZoomScale - mMinZoomScale) > ZOOM_RING_STEPS * 0.01f) { - ZoomRingController.showZoomTutorialOnce(mContext); - mShowZoomRingTutorial = false; + if (mShowZoomTutorial && getSettings().supportZoom() + && (mMaxZoomScale != mMinZoomScale)) { + ZoomButtonsController.showZoomTutorialOnce(mContext); + mShowZoomTutorial = false; mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(DISMISS_ZOOM_RING_TUTORIAL), - ZOOM_RING_TUTORIAL_DURATION); + .obtainMessage(DISMISS_ZOOM_TUTORIAL), + ZOOM_TUTORIAL_DURATION); } if (LOGV_ENABLED) { @@ -3655,15 +3461,6 @@ public class WebView extends AbsoluteLayout + mTouchMode); } - if ((mZoomRingController.isVisible() || mZoomButtonsController.isVisible()) - && mInZoomTapDragMode) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - // Just released the second tap, no longer in tap-drag mode - mInZoomTapDragMode = false; - } - return mZoomRingController.handleDoubleTapEvent(ev); - } - int action = ev.getAction(); float x = ev.getX(); float y = ev.getY(); @@ -3689,7 +3486,7 @@ public class WebView extends AbsoluteLayout WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData(); ted.mAction = action; ted.mX = viewToContent((int) x + mScrollX); - ted.mY = viewToContent((int) y + mScrollY);; + ted.mY = viewToContent((int) y + mScrollY); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); mLastSentTouchTime = eventTime; } @@ -3721,7 +3518,7 @@ public class WebView extends AbsoluteLayout nativeMoveSelection(viewToContent(mSelectX) , viewToContent(mSelectY), false); mTouchSelection = mExtendSelection = true; - } else if (!ZoomRingController.useOldZoom(mContext) && + } else if (!ZoomButtonsController.useOldZoom(mContext) && mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP) && (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare)) { // Found doubletap, invoke the zoom controller @@ -3732,13 +3529,11 @@ public class WebView extends AbsoluteLayout mTextEntry.updateCachedTextfield(); } nativeClearFocus(contentX, contentY); - mInZoomTapDragMode = true; if (mLogEvent) { EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, (eventTime - mLastTouchUpTime), eventTime); } - return mZoomRingController.handleDoubleTapEvent(ev) || - mZoomButtonsController.handleDoubleTapEvent(ev); + return mZoomButtonsController.handleDoubleTapEvent(ev); } else { mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents; @@ -3747,9 +3542,8 @@ public class WebView extends AbsoluteLayout (eventTime - mLastTouchUpTime), eventTime); } } - // don't trigger the link if zoom ring is visible - if (mTouchMode == TOUCH_INIT_MODE - && !mZoomRingController.isVisible()) { + // Trigger the link + if (mTouchMode == TOUCH_INIT_MODE) { mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); } @@ -3885,7 +3679,7 @@ public class WebView extends AbsoluteLayout mUserScroll = true; } - if (ZoomRingController.useOldZoom(mContext)) { + if (ZoomButtonsController.useOldZoom(mContext)) { boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; boolean showMagnify = canZoomScrollOut(); if (mZoomControls != null && (showPlusMinus || showMagnify)) { @@ -3909,15 +3703,6 @@ public class WebView extends AbsoluteLayout mLastTouchUpTime = eventTime; switch (mTouchMode) { case TOUCH_INIT_MODE: // tap - if (mZoomRingController.isVisible()) { - // don't trigger the link if zoom ring is visible, - // but still allow the double tap - mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP, - new Boolean(false)), - DOUBLE_TAP_TIMEOUT); - break; - } mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); if (getSettings().supportZoom()) { mPrivateHandler.sendMessageDelayed(mPrivateHandler @@ -4431,14 +4216,6 @@ public class WebView extends AbsoluteLayout return mZoomControls; } - /** - * @hide pending API council? Assuming we make ZoomRingController itself - * public, which I think we will. - */ - public ZoomRingController getZoomRingController() { - return mZoomRingController; - } - /** * Perform zoom in in the webview * @return TRUE if zoom in succeeds. FALSE if no zoom changes. @@ -4482,9 +4259,6 @@ public class WebView extends AbsoluteLayout }); zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() { public void onClick(View v) { - // Hide the zoom ring - mZoomRingController.setVisible(false); - mPrivateHandler.removeCallbacks(mZoomControlRunnable); mPrivateHandler.postDelayed(mZoomControlRunnable, ZOOM_CONTROLS_TIMEOUT); @@ -4693,6 +4467,7 @@ public class WebView extends AbsoluteLayout arg.put("replace", replace); arg.put("start", new Integer(newStart)); arg.put("end", new Integer(newEnd)); + mTextGeneration++; mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); } @@ -5006,8 +4781,8 @@ public class WebView extends AbsoluteLayout } break; - case DISMISS_ZOOM_RING_TUTORIAL: - mZoomRingController.finishZoomTutorial(); + case DISMISS_ZOOM_TUTORIAL: + mZoomButtonsController.finishZoomTutorial(); break; default: diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index d72570aca1c33..f517dc9240926 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -41,6 +41,10 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputConnectionWrapper; import android.view.inputmethod.InputMethodManager; import android.view.ContextMenu.ContextMenuInfo; @@ -899,6 +903,10 @@ public abstract class AbsListView extends AdapterView implements Te } private boolean acceptFilter() { + if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) || + ((Filterable) getAdapter()).getFilter() == null) { + return false; + } final Context context = mContext; final InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); @@ -1107,7 +1115,7 @@ public abstract class AbsListView extends AdapterView implements Te // filter popup needs to follow the widget. if (mFiltered && changed && getWindowVisibility() == View.VISIBLE && mPopup != null && mPopup.isShowing()) { - positionPopup(true); + positionPopup(); } return changed; @@ -2767,20 +2775,20 @@ public abstract class AbsListView extends AdapterView implements Te // Make sure we have a window before showing the popup if (getWindowVisibility() == View.VISIBLE) { createTextFilter(true); - positionPopup(false); + positionPopup(); // Make sure we get focus if we are showing the popup checkFocus(); } } - private void positionPopup(boolean update) { + private void positionPopup() { int screenHeight = getResources().getDisplayMetrics().heightPixels; final int[] xy = new int[2]; getLocationOnScreen(xy); // TODO: The 20 below should come from the theme and be expressed in dip // TODO: And the gravity should be defined in the theme as well final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20); - if (!update) { + if (!mPopup.isShowing()) { mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, xy[0], bottomGap); } else { @@ -2849,8 +2857,7 @@ public abstract class AbsListView extends AdapterView implements Te * @return True if the text filter handled the event, false otherwise. */ boolean sendToTextFilter(int keyCode, int count, KeyEvent event) { - if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) || - ((Filterable) getAdapter()).getFilter() == null) { + if (!acceptFilter()) { return false; } @@ -2879,7 +2886,7 @@ public abstract class AbsListView extends AdapterView implements Te break; } - if (okToSend && acceptFilter()) { + if (okToSend) { createTextFilter(true); KeyEvent forwardEvent = event; @@ -2905,6 +2912,27 @@ public abstract class AbsListView extends AdapterView implements Te return handled; } + /** + * Return an InputConnection for editing of the filter text. + */ + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + // XXX we need to have the text filter created, so we can get an + // InputConnection to proxy to. Unfortunately this means we pretty + // much need to make it as soon as a list view gets focus. + createTextFilter(false); + return mTextFilter.onCreateInputConnection(outAttrs); + } + + /** + * For filtering we proxy an input connection to an internal text editor, + * and this allows the proxying to happen. + */ + @Override + public boolean checkInputConnectionProxy(View view) { + return view == mTextFilter; + } + /** * Creates the window for the text filter and populates it with an EditText field; * @@ -2918,6 +2946,11 @@ public abstract class AbsListView extends AdapterView implements Te c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mTextFilter = (EditText) layoutInflater.inflate( com.android.internal.R.layout.typing_filter, null); + // For some reason setting this as the "real" input type changes + // the text view in some way that it doesn't work, and I don't + // want to figure out why this is. + mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_VARIATION_FILTER); mTextFilter.addTextChangedListener(this); p.setFocusable(false); p.setTouchable(false); diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 0a552e8cf2add..3368477950253 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -26,7 +26,6 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.SystemClock; -import android.util.TypedValue; import android.view.MotionEvent; /** @@ -313,12 +312,17 @@ class FastScroller { // Non-existent letter while (section > 0) { section--; - prevIndex = mSectionIndexer.getPositionForSection(section); - if (prevIndex != index) { - prevSection = section; - sectionIndex = section; - break; - } + prevIndex = mSectionIndexer.getPositionForSection(section); + if (prevIndex != index) { + prevSection = section; + sectionIndex = section; + break; + } else if (section == 0) { + // When section reaches 0 here, sectionIndex must follow it. + // Assuming mSectionIndexer.getPositionForSection(0) == 0. + sectionIndex = 0; + break; + } } } // Find the next index, in case the assumed next index is not diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java index 1d0fd5ead8c90..7e55c78a39b30 100644 --- a/core/java/android/widget/Filter.java +++ b/core/java/android/widget/Filter.java @@ -42,10 +42,12 @@ public abstract class Filter { private static final String THREAD_NAME = "Filter"; private static final int FILTER_TOKEN = 0xD0D0F00D; private static final int FINISH_TOKEN = 0xDEADBEEF; - + private Handler mThreadHandler; private Handler mResultHandler; + private final Object mLock = new Object(); + /** *

Creates a new asynchronous filter.

*/ @@ -81,8 +83,7 @@ public abstract class Filter { * @see #publishResults(CharSequence, android.widget.Filter.FilterResults) */ public final void filter(CharSequence constraint, FilterListener listener) { - synchronized (this) { - + synchronized (mLock) { if (mThreadHandler == null) { HandlerThread thread = new HandlerThread(THREAD_NAME); thread.start(); @@ -221,7 +222,7 @@ public abstract class Filter { message.sendToTarget(); } - synchronized (this) { + synchronized (mLock) { if (mThreadHandler != null) { Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN); mThreadHandler.sendMessageDelayed(finishMessage, 3000); @@ -229,7 +230,7 @@ public abstract class Filter { } break; case FINISH_TOKEN: - synchronized (this) { + synchronized (mLock) { if (mThreadHandler != null) { mThreadHandler.getLooper().quit(); mThreadHandler = null; diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index f2cec92e708e4..227fb9571d2f7 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -451,6 +451,7 @@ public class MediaController extends FrameLayout { public void onProgressChanged(SeekBar bar, int progress, boolean fromtouch) { if (fromtouch) { mDragging = true; + duration = mPlayer.getDuration(); long newposition = (duration * progress) / 1000L; mPlayer.seekTo( (int) newposition); if (mCurrentTime != null) diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3f4912f3c9fdf..8271a9a071e06 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -43,6 +43,7 @@ import android.text.GetChars; import android.text.GraphicsOperations; import android.text.ClipboardManager; import android.text.InputFilter; +import android.text.InputType; import android.text.Layout; import android.text.ParcelableSpan; import android.text.Selection; @@ -235,7 +236,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CharWrapper mCharWrapper = null; private boolean mSelectionMoved = false; - private boolean mTouchFocusSelectedAll = false; + private boolean mTouchFocusSelected = false; private Marquee mMarquee; private boolean mRestartMarquee; @@ -243,7 +244,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mMarqueeRepeatLimit = 3; class InputContentType { - int imeOptions = EditorInfo.IME_UNDEFINED; + int imeOptions = EditorInfo.IME_NULL; String privateImeOptions; CharSequence imeActionLabel; int imeActionId; @@ -261,6 +262,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final ExtractedText mTmpExtracted = new ExtractedText(); int mBatchEditNesting; boolean mCursorChanged; + boolean mSelectionModeChanged; boolean mContentChanged; int mChangedStart, mChangedEnd, mChangedDelta; } @@ -287,8 +289,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @param v The view that was clicked. * @param actionId Identifier of the action. This will be either the - * identifier you supplied, or {@link EditorInfo#IME_UNDEFINED - * EditorInfo.IME_UNDEFINED} if being called due to the enter key + * identifier you supplied, or {@link EditorInfo#IME_NULL + * EditorInfo.IME_NULL} if being called due to the enter key * being pressed. * @param event If triggered by an enter key, this is the event; * otherwise, this is null. @@ -1492,10 +1494,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void setPadding(int left, int top, int right, int bottom) { - if (left != getPaddingLeft() || - right != getPaddingRight() || - top != getPaddingTop() || - bottom != getPaddingBottom()) { + if (left != mPaddingLeft || + right != mPaddingRight || + top != mPaddingTop || + bottom != mPaddingBottom) { nullLayouts(); } @@ -2951,7 +2953,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public int getImeOptions() { return mInputContentType != null - ? mInputContentType.imeOptions : EditorInfo.IME_UNDEFINED; + ? mInputContentType.imeOptions : EditorInfo.IME_NULL; } /** @@ -3013,9 +3015,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Called when an attached input method calls * {@link InputConnection#performEditorAction(int) * InputConnection.performEditorAction()} - * for this text view. The default implementation will call your click - * listener supplied to {@link #setOnEditorActionListener}, - * or generate an enter key down/up pair to invoke the action if not. + * for this text view. The default implementation will call your action + * listener supplied to {@link #setOnEditorActionListener}, or perform + * a standard operation for {@link EditorInfo#IME_ACTION_NEXT + * EditorInfo.IME_ACTION_NEXT} or {@link EditorInfo#IME_ACTION_DONE + * EditorInfo.IME_ACTION_DONE}. + * + *

For backwards compatibility, if no IME options have been set and the + * text view would not normally advance focus on enter, then + * the NEXT and DONE actions received here will be turned into an enter + * key down/up pair to go through the normal key handling. * * @param actionCode The code of the action being performed. * @@ -3052,6 +3061,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } + return; } } @@ -3844,7 +3854,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) { if (imm.isActive(this)) { boolean reported = false; - if (ims.mContentChanged) { + if (ims.mContentChanged || ims.mSelectionModeChanged) { // We are in extract mode and the content has changed // in some way... just report complete new text to the // input method. @@ -4197,7 +4207,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && mInputContentType.enterDown) { mInputContentType.enterDown = false; if (mInputContentType.onEditorActionListener.onEditorAction( - this, EditorInfo.IME_UNDEFINED, event)) { + this, EditorInfo.IME_NULL, event)) { return true; } } @@ -4272,17 +4282,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.actionId = mInputContentType.imeActionId; outAttrs.extras = mInputContentType.extras; } else { - outAttrs.imeOptions = EditorInfo.IME_UNDEFINED; + outAttrs.imeOptions = EditorInfo.IME_NULL; } - if (outAttrs.imeOptions == EditorInfo.IME_UNDEFINED) { + if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION) + == EditorInfo.IME_ACTION_UNSPECIFIED) { if (focusSearch(FOCUS_DOWN) != null) { // 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; + outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; } 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; + outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; } if (!shouldAdvanceFocusOnEnter()) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; @@ -4307,51 +4318,64 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { - return extractTextInternal(request, -1, -1, -1, outText); + return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN, + EXTRACT_UNKNOWN, outText); } + static final int EXTRACT_NOTHING = -2; + static final int EXTRACT_UNKNOWN = -1; + boolean extractTextInternal(ExtractedTextRequest request, int partialStartOffset, int partialEndOffset, int delta, ExtractedText outText) { final CharSequence content = mText; if (content != null) { - final int N = content.length(); - if (partialStartOffset < 0) { - outText.partialStartOffset = outText.partialEndOffset = -1; - partialStartOffset = 0; - partialEndOffset = N; - } else { - // Adjust offsets to ensure we contain full spans. - if (content instanceof Spanned) { - Spanned spanned = (Spanned)content; - Object[] spans = spanned.getSpans(partialStartOffset, - partialEndOffset, ParcelableSpan.class); - int i = spans.length; - while (i > 0) { - i--; - int j = spanned.getSpanStart(spans[i]); - if (j < partialStartOffset) partialStartOffset = j; - j = spanned.getSpanEnd(spans[i]); - if (j > partialEndOffset) partialEndOffset = j; + if (partialStartOffset != EXTRACT_NOTHING) { + final int N = content.length(); + if (partialStartOffset < 0) { + outText.partialStartOffset = outText.partialEndOffset = -1; + partialStartOffset = 0; + partialEndOffset = N; + } else { + // Adjust offsets to ensure we contain full spans. + if (content instanceof Spanned) { + Spanned spanned = (Spanned)content; + Object[] spans = spanned.getSpans(partialStartOffset, + partialEndOffset, ParcelableSpan.class); + int i = spans.length; + while (i > 0) { + i--; + int j = spanned.getSpanStart(spans[i]); + if (j < partialStartOffset) partialStartOffset = j; + j = spanned.getSpanEnd(spans[i]); + if (j > partialEndOffset) partialEndOffset = j; + } + } + outText.partialStartOffset = partialStartOffset; + outText.partialEndOffset = partialEndOffset; + // Now use the delta to determine the actual amount of text + // we need. + partialEndOffset += delta; + if (partialEndOffset > N) { + partialEndOffset = N; + } else if (partialEndOffset < 0) { + partialEndOffset = 0; } } - outText.partialStartOffset = partialStartOffset; - outText.partialEndOffset = partialEndOffset; - // Now use the delta to determine the actual amount of text - // we need. - partialEndOffset += delta; - if (partialEndOffset > N) { - partialEndOffset = N; - } else if (partialEndOffset < 0) { - partialEndOffset = 0; + if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { + outText.text = content.subSequence(partialStartOffset, + partialEndOffset); + } else { + outText.text = TextUtils.substring(content, partialStartOffset, + partialEndOffset); } } - if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { - outText.text = content.subSequence(partialStartOffset, - partialEndOffset); - } else { - outText.text = TextUtils.substring(content, partialStartOffset, - partialEndOffset); + outText.flags = 0; + if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { + outText.flags |= ExtractedText.FLAG_SELECTING; + } + if (mSingleLine) { + outText.flags |= ExtractedText.FLAG_SINGLE_LINE; } outText.startOffset = 0; outText.selectionStart = Selection.getSelectionStart(content); @@ -4363,8 +4387,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean reportExtractedText() { final InputMethodState ims = mInputMethodState; - if (ims != null && ims.mContentChanged) { + final boolean contentChanged = ims.mContentChanged; + if (ims != null && (contentChanged || ims.mSelectionModeChanged)) { ims.mContentChanged = false; + ims.mSelectionModeChanged = false; final ExtractedTextRequest req = mInputMethodState.mExtracting; if (req != null) { InputMethodManager imm = InputMethodManager.peekInstance(); @@ -4372,6 +4398,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" + ims.mChangedStart + " end=" + ims.mChangedEnd + " delta=" + ims.mChangedDelta); + if (ims.mChangedStart < 0 && !contentChanged) { + ims.mChangedStart = EXTRACT_NOTHING; + } if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, ims.mChangedDelta, ims.mTmpExtracted)) { if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" @@ -4408,19 +4437,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setExtractedText(ExtractedText text) { Editable content = getEditableText(); - if (content == null) { - setText(text.text, TextView.BufferType.EDITABLE); - } else if (text.partialStartOffset < 0) { - removeParcelableSpans(content, 0, content.length()); - content.replace(0, content.length(), text.text); - } else { - final int N = content.length(); - int start = text.partialStartOffset; - if (start > N) start = N; - int end = text.partialEndOffset; - if (end > N) end = N; - removeParcelableSpans(content, start, end); - content.replace(start, end, text.text); + if (text.text != null) { + if (content == null) { + setText(text.text, TextView.BufferType.EDITABLE); + } else if (text.partialStartOffset < 0) { + removeParcelableSpans(content, 0, content.length()); + content.replace(0, content.length(), text.text); + } else { + final int N = content.length(); + int start = text.partialStartOffset; + if (start > N) start = N; + int end = text.partialEndOffset; + if (end > N) end = N; + removeParcelableSpans(content, start, end); + content.replace(start, end, text.text); + } } // Now set the selection position... make sure it is in range, to @@ -4436,6 +4467,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (end < 0) end = 0; else if (end > N) end = N; Selection.setSelection(sp, start, end); + + // Finally, update the selection mode. + if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) { + MetaKeyKeyListener.startSelecting(this, sp); + } else { + MetaKeyKeyListener.stopSelecting(this, sp); + } } /** @@ -4473,8 +4511,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ims.mChangedStart = 0; ims.mChangedEnd = mText.length(); } else { - ims.mChangedStart = -1; - ims.mChangedEnd = -1; + ims.mChangedStart = EXTRACT_UNKNOWN; + ims.mChangedEnd = EXTRACT_UNKNOWN; ims.mContentChanged = false; } onBeginBatchEdit(); @@ -4503,7 +4541,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener void finishBatchEdit(final InputMethodState ims) { onEndBatchEdit(); - if (ims.mContentChanged) { + if (ims.mContentChanged || ims.mSelectionModeChanged) { updateAfterEdit(); reportExtractedText(); } else if (ims.mCursorChanged) { @@ -5888,6 +5926,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (MetaKeyKeyListener.isMetaTracker(buf, what)) { mHighlightPathBogus = true; + if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { + ims.mSelectionModeChanged = true; + } if (Selection.getSelectionStart(buf) >= 0) { if (ims == null || ims.mBatchEditNesting == 0) { @@ -6026,7 +6067,6 @@ 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) { @@ -6042,6 +6082,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Selection.setSelection((Spannable) mText, selStart, selEnd); } + mTouchFocusSelected = true; } mFrozenWithFocus = false; @@ -6149,7 +6190,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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; + mTouchFocusSelected = false; } final boolean superResult = super.onTouchEvent(event); @@ -6218,10 +6259,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * 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. + * its selection changed. Only valid while processing the touch gesture + * of interest. */ - public boolean didTouchFocusSelectAll() { - return mTouchFocusSelectedAll; + public boolean didTouchFocusSelect() { + return mTouchFocusSelected; } @Override @@ -6351,6 +6393,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.computeVerticalScrollRange(); } + @Override + protected int computeVerticalScrollExtent() { + return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); + } + public enum BufferType { NORMAL, SPANNABLE, EDITABLE, } @@ -6497,6 +6544,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * or null if there is no cursor or no word at the cursor. */ private String getWordForDictionary() { + /* + * Quick return if the input type is one where adding words + * to the dictionary doesn't make any sense. + */ + int klass = mInputType & InputType.TYPE_MASK_CLASS; + if (klass == InputType.TYPE_CLASS_NUMBER || + klass == InputType.TYPE_CLASS_PHONE || + klass == InputType.TYPE_CLASS_DATETIME) { + return null; + } + + int variation = mInputType & InputType.TYPE_MASK_VARIATION; + if (variation == InputType.TYPE_TEXT_VARIATION_URI || + variation == InputType.TYPE_TEXT_VARIATION_PASSWORD || + variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD || + variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || + variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + return null; + } + int end = getSelectionEnd(); if (end < 0) { diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 1227afd9c186c..ec6f88bd5154f 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -46,8 +46,10 @@ import java.io.IOException; * such as scaling and tinting. */ public class VideoView extends SurfaceView implements MediaPlayerControl { + private String TAG = "VideoView"; // settable by the client private Uri mUri; + private int mDuration; // All the stuff we need for playing and showing a video private SurfaceHolder mSurfaceHolder = null; @@ -184,6 +186,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mIsPrepared = false; + Log.v(TAG, "reset duration to -1 in openVideo"); + mDuration = -1; mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); @@ -195,10 +199,10 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mMediaPlayer.prepareAsync(); attachMediaController(); } catch (IOException ex) { - Log.w("VideoView", "Unable to open content: " + mUri, ex); + Log.w(TAG, "Unable to open content: " + mUri, ex); return; } catch (IllegalArgumentException ex) { - Log.w("VideoView", "Unable to open content: " + mUri, ex); + Log.w(TAG, "Unable to open content: " + mUri, ex); return; } } @@ -299,7 +303,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() { public boolean onError(MediaPlayer mp, int a, int b) { - Log.d("VideoView", "Error: " + a + "," + b); + Log.d(TAG, "Error: " + a + "," + b); if (mMediaController != null) { mMediaController.hide(); } @@ -497,9 +501,14 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { public int getDuration() { if (mMediaPlayer != null && mIsPrepared) { - return mMediaPlayer.getDuration(); + if (mDuration > 0) { + return mDuration; + } + mDuration = mMediaPlayer.getDuration(); + return mDuration; } - return -1; + mDuration = -1; + return mDuration; } public int getCurrentPosition() { diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index ec45e23a76208..1ba2dce6d76c6 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -16,14 +16,19 @@ package android.widget; +import android.app.AlertDialog; +import android.app.Dialog; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; import android.provider.Settings; import android.view.Gravity; import android.view.KeyEvent; @@ -31,6 +36,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.WindowManager.LayoutParams; @@ -46,7 +52,7 @@ import android.view.WindowManager.LayoutParams; * * @hide */ -public class ZoomButtonsController implements View.OnTouchListener { +public class ZoomButtonsController implements View.OnTouchListener, View.OnKeyListener { private static final String TAG = "ZoomButtonsController"; @@ -60,13 +66,13 @@ public class ZoomButtonsController implements View.OnTouchListener { private WindowManager mWindowManager; /** - * The view that is being zoomed by this zoom ring. + * The view that is being zoomed by this zoom controller. */ private View mOwnerView; /** * The bounds of the owner view in global coordinates. This is recalculated - * each time the zoom ring is shown. + * each time the zoom controller is shown. */ private Rect mOwnerViewBounds = new Rect(); @@ -89,11 +95,13 @@ public class ZoomButtonsController implements View.OnTouchListener { */ private int[] mTouchTargetLocationInWindow = new int[2]; /** - * If the zoom ring is dismissed but the user is still in a touch + * If the zoom controller is dismissed but the user is still in a touch * interaction, we set this to true. This will ignore all touch events until * up/cancel, and then set the owner's touch listener to null. */ private boolean mReleaseTouchListenerOnUp; + + private boolean mIsSecondTapDown; private boolean mIsVisible; @@ -122,10 +130,16 @@ public class ZoomButtonsController implements View.OnTouchListener { } }; + /** + * The setting name that tracks whether we've shown the zoom tutorial. + */ + private static final String SETTING_NAME_SHOWN_TUTORIAL = "shown_zoom_tutorial"; + private static Dialog sTutorialDialog; + /** 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. */ - private static final int MSG_DISMISS_ZOOM_RING = 3; + /** Used to delay the zoom controller dismissal. */ + private static final int MSG_DISMISS_ZOOM_CONTROLS = 3; /** * If setVisible(true) is called and the owner view's window token is null, * we delay the setVisible(true) call until it is not null. @@ -140,7 +154,7 @@ public class ZoomButtonsController implements View.OnTouchListener { onPostConfigurationChanged(); break; - case MSG_DISMISS_ZOOM_RING: + case MSG_DISMISS_ZOOM_CONTROLS: setVisible(false); break; @@ -148,7 +162,7 @@ public class ZoomButtonsController implements View.OnTouchListener { if (mOwnerView.getWindowToken() == null) { // Doh, it is still null, throw an exception throw new IllegalArgumentException( - "Cannot make the zoom ring visible if the owner view is " + + "Cannot make the zoom controller visible if the owner view is " + "not attached to a window."); } setVisible(true); @@ -165,11 +179,24 @@ public class ZoomButtonsController implements View.OnTouchListener { mContainer = createContainer(); } - + + public void setZoomInEnabled(boolean enabled) { + mControls.setIsZoomInEnabled(enabled); + } + + public void setZoomOutEnabled(boolean enabled) { + mControls.setIsZoomOutEnabled(enabled); + } + + public void setZoomSpeed(long speed) { + mControls.setZoomSpeed(speed); + } + private FrameLayout createContainer() { LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.BOTTOM | Gravity.CENTER; lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE | + LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_NO_LIMITS; lp.height = LayoutParams.WRAP_CONTENT; lp.width = LayoutParams.FILL_PARENT; @@ -182,6 +209,7 @@ public class ZoomButtonsController implements View.OnTouchListener { FrameLayout container = new FrameLayout(mContext); container.setLayoutParams(lp); container.setMeasureAllChildren(true); + container.setOnKeyListener(this); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -326,13 +354,13 @@ public class ZoomButtonsController implements View.OnTouchListener { return mContainer; } - public int getZoomRingId() { + public int getZoomControlsId() { return mControls.getId(); } private void dismissControlsDelayed(int delay) { - mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); - mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay); + mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS); + mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay); } /** @@ -351,8 +379,21 @@ public class ZoomButtonsController implements View.OnTouchListener { int x = (int) event.getX(); int y = (int) event.getY(); + /* + * This class will consume all events in the second tap (down, + * move(s), up). But, the owner already got the second tap's down, + * so cancel that. Do this before setVisible, since that call + * will set us as a touch listener. + */ + MotionEvent cancelEvent = MotionEvent.obtain(event.getDownTime(), + SystemClock.elapsedRealtime(), + MotionEvent.ACTION_CANCEL, 0, 0, 0); + mOwnerView.dispatchTouchEvent(cancelEvent); + cancelEvent.recycle(); + setVisible(true); centerPoint(x, y); + mIsSecondTapDown = true; } return true; @@ -373,11 +414,23 @@ public class ZoomButtonsController implements View.OnTouchListener { } } + public boolean onKey(View v, int keyCode, KeyEvent event) { + dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); + return false; + } + public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); + // Consume all events during the second-tap interaction (down, move, up/cancel) + boolean consumeEvent = mIsSecondTapDown; + if ((action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL)) { + // The second tap can no longer be down + mIsSecondTapDown = false; + } + if (mReleaseTouchListenerOnUp) { - // The ring was dismissed but we need to throw away all events until the up + // The controls were dismissed but we need to throw away all events until the up if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { mOwnerView.setOnTouchListener(null); setTouchTargetView(null); @@ -417,10 +470,10 @@ public class ZoomButtonsController implements View.OnTouchListener { mOwnerViewBounds.top - targetViewRawY); boolean retValue = targetView.dispatchTouchEvent(containerEvent); containerEvent.recycle(); - return retValue; + return retValue || consumeEvent; } else { - return false; + return consumeEvent; } } @@ -465,10 +518,102 @@ public class ZoomButtonsController implements View.OnTouchListener { refreshPositioningVariables(); } - public static boolean useThisZoom(Context context) { - return ZoomRingController.getZoomType(context) == 2; + /* + * This is static so Activities can call this instead of the Views + * (Activities usually do not have a reference to the ZoomButtonsController + * instance.) + */ + /** + * Shows a "tutorial" (some text) to the user teaching her the new zoom + * invocation method. Must call from the main thread. + *

+ * It checks the global system setting to ensure this has not been seen + * before. Furthermore, if the application does not have privilege to write + * to the system settings, it will store this bit locally in a shared + * preference. + * + * @hide This should only be used by our main apps--browser, maps, and + * gallery + */ + public static void showZoomTutorialOnce(Context context) { + ContentResolver cr = context.getContentResolver(); + if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) { + return; + } + + SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); + if (sp.getInt(SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) { + return; + } + + if (sTutorialDialog != null && sTutorialDialog.isShowing()) { + sTutorialDialog.dismiss(); + } + + LayoutInflater layoutInflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + TextView textView = (TextView) layoutInflater.inflate( + com.android.internal.R.layout.alert_dialog_simple_text, null) + .findViewById(android.R.id.text1); + textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short); + + sTutorialDialog = new AlertDialog.Builder(context) + .setView(textView) + .setIcon(0) + .create(); + + Window window = sTutorialDialog.getWindow(); + window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | + WindowManager.LayoutParams.FLAG_BLUR_BEHIND); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); + + sTutorialDialog.show(); } - + + /** @hide Should only be used by Android platform apps */ + public static void finishZoomTutorial(Context context, boolean userNotified) { + if (sTutorialDialog == null) return; + + sTutorialDialog.dismiss(); + sTutorialDialog = null; + + // Record that they have seen the tutorial + if (userNotified) { + try { + Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TUTORIAL, + 1); + } catch (SecurityException e) { + /* + * The app does not have permission to clear this global flag, make + * sure the user does not see the message when he comes back to this + * same app at least. + */ + SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); + sp.edit().putInt(SETTING_NAME_SHOWN_TUTORIAL, 1).commit(); + } + } + } + + /** @hide Should only be used by Android platform apps */ + public void finishZoomTutorial() { + finishZoomTutorial(mContext, true); + } + + // Temporary methods for different zoom types + static int getZoomType(Context context) { + return Settings.System.getInt(context.getContentResolver(), "zoom", 1); + } + + public static boolean useOldZoom(Context context) { + return getZoomType(context) == 0; + } + + public static boolean useThisZoom(Context context) { + return getZoomType(context) == 2; + } + public interface OnZoomListener { void onCenter(int x, int y); void onVisibilityChanged(boolean visible); diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java index fdc05b6e4b20f..e978db8b12c0b 100644 --- a/core/java/android/widget/ZoomControls.java +++ b/core/java/android/widget/ZoomControls.java @@ -81,7 +81,7 @@ public class ZoomControls extends LinearLayout { } public void show() { - if (ZoomRingController.useOldZoom(mContext)) { + if (ZoomButtonsController.useOldZoom(mContext)) { fade(View.VISIBLE, 0.0f, 1.0f); } } diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java deleted file mode 100644 index 83a1225aed456..0000000000000 --- a/core/java/android/widget/ZoomRing.java +++ /dev/null @@ -1,1274 +0,0 @@ -/* - * 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; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; - -/** - * A view that has a draggable thumb on a circle. - * - * @hide - */ -public class ZoomRing extends View { - private static final String TAG = "ZoomRing"; - - // TODO: Temporary until the trail is done - private static final boolean DRAW_TRAIL = false; - - /** - * 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; - 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; - private static final int HALF_PI_INT_MULTIPLIED = PI_INT_MULTIPLIED / 2; - - 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? - *

- * Includes error because we compare this to the result of - * getDelta(getClosestTickeAngle(..), oldAngle) which ends up having some - * rounding error. - */ - private static final int MAX_ABS_JUMP_DELTA_ANGLE = (2 * PI_INT_MULTIPLIED / 3) + - RADIAN_INT_ERROR; - - /** The cached X of the zoom ring's center (in zoom ring coordinates). */ - private int mCenterX; - /** 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 mTrackInnerRadius; - /** Cached square of the inner radius of the track. */ - private int mTrackInnerRadiusSquared; - /** The outer radius of the track. */ - 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 mZoomInArrowDrawable; - /** Shown beneath the thumb if we can still zoom out. */ - 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; - - /** 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 mZoomInArrowHintDrawable; - /** Drawn (without rotation) on top of the arrow. */ - 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 - * slop before going into the #MODE_MOVE_ZOOM_RING. This is a good time to - * 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; - - /** The angle where the user first grabbed the thumb. */ - private int mInitialGrabThumbAngle; - - /** The callback. */ - private OnZoomRingCallback mCallback; - /** 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 mFuzzyTickDelta = Integer.MAX_VALUE; - - /** The angle where the thumb is officially starting to be dragged. */ - private int mThumbDragStartAngle; - - /** 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; - - /** 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_STEP: - onThumbScrollerStep(); - break; - - case MSG_THUMB_ARROWS_FADE_STEP: - onThumbArrowsFadeStep(); - break; - } - } - }; - - public ZoomRing(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - ViewConfiguration viewConfiguration = ViewConfiguration.get(context); - mTouchSlop = viewConfiguration.getScaledTouchSlop(); - - 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(); - if (DRAW_TRAIL) { - // TODO get drawables from style instead - mTrail = res.getDrawable(R.drawable.zoom_ring_trail).mutate(); - } - - mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2; - mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2; - - setTickDelta(PI_INT_MULTIPLIED / 6); - } - - public ZoomRing(Context context, AttributeSet attrs) { - 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; - } - - /** - * 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 vibration) { - mVibration = vibration; - } - - public void setThumbVisible(boolean thumbVisible) { - if (mThumbVisible != thumbVisible) { - mThumbVisible = thumbVisible; - invalidate(); - } - } - - public Drawable getPanningArrowsDrawable() { - return mPanningArrowsDrawable; - } - - public void setTrackRadii(int innerRadius, int outerRadius) { - mTrackInnerRadius = innerRadius; - mTrackOuterRadius = outerRadius; - - mTrackInnerRadiusSquared = innerRadius * innerRadius; - if (mTrackInnerRadiusSquared < innerRadius) { - // Prevent overflow - mTrackInnerRadiusSquared = Integer.MAX_VALUE; - } - - mTrackOuterRadiusSquared = outerRadius * outerRadius; - if (mTrackOuterRadiusSquared < outerRadius) { - // Prevent overflow - 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); - } - updateEnforceMaxAbsJump(); - } - - public void setThumbCounterclockwiseBound(int angle) { - if (angle < 0) { - mThumbCcwBound = Integer.MIN_VALUE; - } else { - mThumbCcwBound = getClosestTickAngle(angle); - } - updateEnforceMaxAbsJump(); - } - - 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; - } - - public int getThumbAngle() { - return mThumbAngle; - } - - public void setThumbAngle(int angle) { - angle = getValidAngle(angle); - mPreviousCallbackTickAngle = getClosestTickAngle(angle); - setThumbAngleAuto(angle, false, false); - } - - /** - * Sets the thumb angle. If already animating, will continue the animation, - * otherwise it will do a direct jump. - * - * @param angle - * @param useDirection Whether to use the ccw parameter - * @param ccw Whether going counterclockwise (only used if useDirection is true) - */ - private void setThumbAngleAuto(int angle, boolean useDirection, boolean ccw) { - if (mThumbScroller == null - || mThumbScroller.isFinished() - || Math.abs(getDelta(angle, getThumbScrollerAngle())) < THUMB_GRAB_SLOP) { - setThumbAngleInt(angle); - } else { - if (useDirection) { - setThumbAngleAnimated(angle, 0, ccw); - } else { - setThumbAngleAnimated(angle, 0); - } - } - } - - private void setThumbAngleInt(int angle) { - mThumbAngle = angle; - int unoffsetAngle = angle + mZeroAngle; - int thumbCenterX = (int) (Math.cos(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * - mThumbDistance) + mCenterX; - int thumbCenterY = (int) (Math.sin(1f * unoffsetAngle / RADIAN_INT_MULTIPLIER) * - mThumbDistance) * -1 + mCenterY; - - mThumbDrawable.setBounds(thumbCenterX - mThumbHalfWidth, - thumbCenterY - mThumbHalfHeight, - thumbCenterX + mThumbHalfWidth, - thumbCenterY + mThumbHalfHeight); - - if (mThumbArrowsToDraw > 0) { - setThumbArrowsAngle(angle); - } - - if (DRAW_TRAIL) { - double degrees; - degrees = Math.min(359.0, Math.abs(mAcculumalatedTrailAngle)); - int level = (int) (10000.0 * degrees / 360.0); - - mTrail.setLevel((int) (10000.0 * - (-Math.toDegrees(angle / (double) RADIAN_INT_MULTIPLIER) - - degrees + 90) / 360.0)); - ((RotateDrawable) mTrail).getDrawable().setLevel(level); - } - - invalidate(); - } - - /** - * - * @param angle - * @param duration The animation duration, or 0 for the default duration. - */ - public void setThumbAngleAnimated(int angle, int duration) { - // The angle when going from the current angle to the new angle - int deltaAngle = getDelta(mThumbAngle, angle); - setThumbAngleAnimated(angle, duration, deltaAngle > 0); - } - - public void setThumbAngleAnimated(int angle, int duration, boolean counterClockwise) { - if (mThumbScroller == null) { - mThumbScroller = new Scroller(mContext); - } - - int startAngle = mThumbAngle; - int endAngle = getValidAngle(angle); - int deltaAngle = getDelta(startAngle, endAngle, counterClockwise); - if (startAngle + deltaAngle < 0) { - // Keep our angles positive - startAngle += TWO_PI_INT_MULTIPLIED; - } - - if (!mThumbScroller.isFinished()) { - duration = mThumbScroller.getDuration() - mThumbScroller.timePassed(); - } else if (duration == 0) { - duration = getAnimationDuration(deltaAngle); - } - mThumbScroller.startScroll(startAngle, 0, deltaAngle, 0, duration); - onThumbScrollerStep(); - } - - private int getAnimationDuration(int deltaAngle) { - if (deltaAngle < 0) deltaAngle *= -1; - return 300 + deltaAngle * 300 / RADIAN_INT_MULTIPLIER; - } - - private void onThumbScrollerStep() { - if (!mThumbScroller.computeScrollOffset()) return; - setThumbAngleInt(getThumbScrollerAngle()); - mHandler.sendEmptyMessage(MSG_THUMB_SCROLLER_STEP); - } - - private int getThumbScrollerAngle() { - return mThumbScroller.getCurrX() % TWO_PI_INT_MULTIPLIED; - } - - public void resetThumbAngle() { - mPreviousCallbackTickAngle = 0; - setThumbAngleInt(0); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), - resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec)); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, - int bottom) { - super.onLayout(changed, left, top, right, bottom); - - // Cache the center point - mCenterX = (right - left) / 2; - mCenterY = (bottom - top) / 2; - - // Done here since we now have center, which is needed to calculate some - // aux info for thumb angle - if (mThumbAngle == Integer.MIN_VALUE) { - resetThumbAngle(); - } - - if (DRAW_TRAIL) { - mTrail.setBounds(0, 0, right - left, bottom - top); - } - - // These drawables are the same size as the track - mZoomInArrowDrawable.setBounds(0, 0, right - left, bottom - top); - mZoomOutArrowDrawable.setBounds(0, 0, right - left, bottom - top); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - return handleTouch(event.getAction(), event.getEventTime(), - (int) event.getX(), (int) event.getY(), (int) event.getRawX(), - (int) event.getRawY()); - } - - private void resetToIdle() { - setMode(MODE_IDLE); - mPreviousWidgetDragX = mPreviousWidgetDragY = Integer.MIN_VALUE; - mAcculumalatedTrailAngle = 0.0; - } - - public void setTapDragMode(boolean tapDragMode, int x, int y) { - resetToIdle(); - if (tapDragMode) { - setMode(MODE_TAP_DRAG); - mCallback.onUserInteractionStarted(); - onThumbDragStarted(getAngle(x - mCenterX, y - mCenterY)); - } else { - onTouchUp(SystemClock.elapsedRealtime(), true); - } - } - - public boolean handleTouch(int action, long time, int x, int y, int rawX, int rawY) { - // local{X,Y} will be where the center of the widget is (0,0) - int localX = x - mCenterX; - int localY = y - mCenterY; - - /* - * If we are not drawing the thumb, there is no way for the user to be - * touching the thumb. Also, if this is the case, assume they are not - * touching the ring (so the user cannot absolute set the thumb, and - * there will be a larger touch region for going into the move-ring - * mode). - */ - boolean isTouchingThumb = mThumbVisible; - boolean isTouchingRing = mThumbVisible; - - int touchAngle = getAngle(localX, localY); - - int radiusSquared = localX * localX + localY * localY; - if (radiusSquared < mTrackInnerRadiusSquared || - radiusSquared > mTrackOuterRadiusSquared) { - // Out-of-bounds - isTouchingThumb = false; - isTouchingRing = false; - } - - if (isTouchingThumb) { - int deltaThumbAndTouch = getDelta(mThumbAngle, touchAngle); - int absoluteDeltaThumbAndTouch = deltaThumbAndTouch >= 0 ? - deltaThumbAndTouch : -deltaThumbAndTouch; - if (absoluteDeltaThumbAndTouch > THUMB_GRAB_SLOP) { - // Didn't grab close enough to the thumb - isTouchingThumb = false; - } - } - - switch (action) { - case MotionEvent.ACTION_DOWN: - 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(); - onTouchUp(time, isTouchingRing); - - // Dismissing, so halt here - return true; - } - - resetToIdle(); - mCallback.onUserInteractionStarted(); - mPreviousDownX = x; - mPreviousDownY = y; - // Fall through to code below switch (since the down is used for - // jumping to the touched tick) - break; - - case MotionEvent.ACTION_MOVE: - // Fall through to code below switch - break; - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - onTouchUp(time, isTouchingRing); - return true; - - default: - return false; - } - - if (mMode == MODE_IDLE) { - if (isTouchingThumb) { - // They grabbed the thumb - setMode(MODE_DRAG_THUMB); - onThumbDragStarted(touchAngle); - - } else if (isTouchingRing) { - // They tapped somewhere else on the ring - int tickAngle = getClosestTickAngle(touchAngle); - int deltaThumbAndTick = getDelta(mThumbAngle, tickAngle); - int boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick); - - if (mEnforceMaxAbsJump) { - // Enforcing the max jump - if (deltaThumbAndTick > MAX_ABS_JUMP_DELTA_ANGLE || - deltaThumbAndTick < -MAX_ABS_JUMP_DELTA_ANGLE) { - // Trying to jump too far, ignore this touch interaction - setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB); - return true; - } - - if (boundAngle != Integer.MIN_VALUE) { - // Cap the user's jump to the bound - tickAngle = boundAngle; - } - } else { - // Not enforcing the max jump, but we have to make sure - // we're getting to the tapped angle by going through the - // in-bounds region - if (boundAngle != Integer.MIN_VALUE) { - // Going this direction hits a bound, let's go the opposite direction - boolean oldDirectionIsCcw = deltaThumbAndTick > 0; - deltaThumbAndTick = getDelta(mThumbAngle, tickAngle, !oldDirectionIsCcw); - boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick); - if (boundAngle != Integer.MIN_VALUE) { - // Cannot get to the tapped location because it is out-of-bounds - setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB); - return true; - } - } - } - - setMode(MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP); - mInitialGrabThumbAngle = touchAngle; - boolean ccw = deltaThumbAndTick > 0; - setThumbAngleAnimated(tickAngle, 0, ccw); - - /* - * Our thumb scrolling animation takes us from mThumbAngle to - * tickAngle, so manifest that as the user dragging the thumb - * there. - */ - onThumbDragStarted(mThumbAngle); - // We know which direction we want to go - onThumbDragged(tickAngle, true, ccw); - - } else { - // They tapped somewhere else on the widget - setMode(MODE_WAITING_FOR_MOVE_ZOOM_RING); - mCallback.onZoomRingSetMovableHintVisible(true); - } - - } else if (mMode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) { - int deltaDownAngle = getDelta(mInitialGrabThumbAngle, touchAngle); - if ((deltaDownAngle < -THUMB_DRAG_SLOP || deltaDownAngle > THUMB_DRAG_SLOP) && - isDeltaInBounds(mInitialGrabThumbAngle, deltaDownAngle)) { - setMode(MODE_DRAG_THUMB); - - // No need to call onThumbDragStarted, since that was done when they tapped-to-jump - } - - } else if (mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { - if (Math.abs(x - mPreviousDownX) > mTouchSlop || - Math.abs(y - mPreviousDownY) > mTouchSlop) { - /* 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) { - // The user is back on the thumb, let's go back to the previous mode - setMode(mPreviousMode); - } - } - - // Purposefully not an "else if" - if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG) { - if (isTouchingRing) { - onThumbDragged(touchAngle, false, false); - } - } else if (mMode == MODE_MOVE_ZOOM_RING) { - onZoomRingMoved(rawX, rawY); - } - - return true; - } - - private void onTouchUp(long time, boolean isTouchingRing) { - int mode = mMode; - if (mode == MODE_IGNORE_UNTIL_TOUCHES_THUMB) { - // For cleaning up, pretend like the user was still in the previous mode - mode = mPreviousMode; - } - - if (mode == MODE_MOVE_ZOOM_RING || mode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { - mCallback.onZoomRingSetMovableHintVisible(false); - if (mode == MODE_MOVE_ZOOM_RING) { - mCallback.onZoomRingMovingStopped(); - } - } else if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG || - mode == MODE_WAITING_FOR_DRAG_THUMB_AFTER_JUMP) { - onThumbDragStopped(); - - if (mode == MODE_DRAG_THUMB || mode == MODE_TAP_DRAG) { - // Animate back to a tick - setThumbAngleAnimated(mPreviousCallbackTickAngle, 0); - } - } - mCallback.onUserInteractionStopped(); - - if (!isTouchingRing) { - mPreviousCenterUpTime = time; - } - } - - private void setMode(int mode) { - if (mode != mMode) { - mPreviousMode = mMode; - mMode = mode; - } - } - - private boolean isDeltaInBounds(int startAngle, int deltaAngle) { - return getBoundIfExceeds(startAngle, deltaAngle) == Integer.MIN_VALUE; - } - - private int getBoundIfExceeds(int startAngle, int deltaAngle) { - if (deltaAngle > 0) { - // Counterclockwise movement - if (mThumbCcwBound != Integer.MIN_VALUE && - getDelta(startAngle, mThumbCcwBound, true) < deltaAngle) { - return mThumbCcwBound; - } - } else if (deltaAngle < 0) { - // Clockwise movement, both of these will be negative - int deltaThumbAndBound = getDelta(startAngle, mThumbCwBound, false); - if (mThumbCwBound != Integer.MIN_VALUE && - deltaThumbAndBound > deltaAngle) { - // Tapped outside of the bound in that direction - return mThumbCwBound; - } - } - - return Integer.MIN_VALUE; - } - - private int getDelta(int startAngle, int endAngle, boolean useDirection, boolean ccw) { - return useDirection ? getDelta(startAngle, endAngle, ccw) : getDelta(startAngle, endAngle); - } - - /** - * Gets the smallest delta between two angles, and infers the direction - * based on the shortest path between the two angles. If going from - * startAngle to endAngle is counterclockwise, the result will be positive. - * If it is clockwise, the result will be negative. - * - * @param startAngle The start angle. - * @param endAngle The end angle. - * @return The difference in angles. - */ - private int getDelta(int startAngle, int endAngle) { - int largerAngle, smallerAngle; - if (endAngle > startAngle) { - largerAngle = endAngle; - smallerAngle = startAngle; - } else { - largerAngle = startAngle; - smallerAngle = endAngle; - } - - int delta = largerAngle - smallerAngle; - if (delta <= PI_INT_MULTIPLIED) { - // If going clockwise, negate the delta - return startAngle == largerAngle ? -delta : delta; - } else { - // The other direction is the delta we want (it includes the - // discontinuous 0-2PI angle) - delta = TWO_PI_INT_MULTIPLIED - delta; - // If going clockwise, negate the delta - return startAngle == smallerAngle ? -delta : delta; - } - } - - /** - * Gets the delta between two angles in the direction specified. - * - * @param startAngle The start angle. - * @param endAngle The end angle. - * @param counterClockwise The direction to take when computing the delta. - * @return The difference in angles in the given direction. - */ - private int getDelta(int startAngle, int endAngle, boolean counterClockwise) { - int delta = endAngle - startAngle; - - if (!counterClockwise && delta > 0) { - // Crossed the discontinuous 0/2PI angle, take the leftover slice of - // the pie and negate it - return -TWO_PI_INT_MULTIPLIED + delta; - } else if (counterClockwise && delta < 0) { - // Crossed the discontinuous 0/2PI angle, take the leftover slice of - // the pie (and ensure it is positive) - return TWO_PI_INT_MULTIPLIED + delta; - } else { - return delta; - } - } - - private void onThumbDragStarted(int startAngle) { - setThumbArrowsVisible(false); - mThumbDragStartAngle = startAngle; - mCallback.onZoomRingThumbDraggingStarted(); - } - - private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) { - boolean animateThumbToNewAngle = false; - - int totalDeltaAngle; - totalDeltaAngle = getDelta(mPreviousCallbackTickAngle, touchAngle, useDirection, ccw); - if (totalDeltaAngle >= mFuzzyTickDelta - || totalDeltaAngle <= -mFuzzyTickDelta) { - - if (!useDirection) { - // Set ccw to match the direction found by getDelta - ccw = totalDeltaAngle > 0; - } - - /* - * When the user slides the thumb through the tick that corresponds - * to a zoom bound, we don't want to abruptly stop there. Instead, - * let the user slide it to the next tick, and then animate it back - * to the original zoom bound tick. Because of this, we make sure - * the delta from the bound is more than halfway to the next tick. - * We make sure the bound is between the touch and the previous - * callback to ensure we just passed the bound. - */ - int oldTouchAngle = touchAngle; - if (ccw && mThumbCcwBound != Integer.MIN_VALUE) { - int deltaCcwBoundAndTouch = - getDelta(mThumbCcwBound, touchAngle, useDirection, true); - if (deltaCcwBoundAndTouch >= mTickDelta / 2) { - // The touch has past a bound - int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackTickAngle, - touchAngle, useDirection, true); - if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) { - // The bound is between the previous callback angle and the touch - touchAngle = mThumbCcwBound; - // We're moving the touch BACK to the bound, so opposite direction - ccw = false; - } - } - } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) { - // See block above for general comments - int deltaCwBoundAndTouch = - getDelta(mThumbCwBound, touchAngle, useDirection, false); - if (deltaCwBoundAndTouch <= -mTickDelta / 2) { - int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackTickAngle, - touchAngle, useDirection, false); - /* - * Both of these will be negative since we got delta in - * clockwise direction, and we want the magnitude of - * deltaPreviousCbAndTouch to be greater than the magnitude - * of deltaCwBoundAndTouch - */ - if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) { - touchAngle = mThumbCwBound; - ccw = true; - } - } - } - if (touchAngle != oldTouchAngle) { - // We bounded the touch angle - totalDeltaAngle = getDelta(mPreviousCallbackTickAngle, touchAngle, useDirection, ccw); - animateThumbToNewAngle = true; - setMode(MODE_IGNORE_UNTIL_TOUCHES_THUMB); - } - - - // Prevent it from jumping too far - if (mEnforceMaxAbsJump) { - if (totalDeltaAngle <= -MAX_ABS_JUMP_DELTA_ANGLE) { - totalDeltaAngle = -MAX_ABS_JUMP_DELTA_ANGLE; - animateThumbToNewAngle = true; - } else if (totalDeltaAngle >= MAX_ABS_JUMP_DELTA_ANGLE) { - totalDeltaAngle = MAX_ABS_JUMP_DELTA_ANGLE; - animateThumbToNewAngle = true; - } - } - - /* - * We need to cover the edge case of a user grabbing the thumb, - * going into the center of the widget, and then coming out from the - * center to an angle that's slightly below the angle he's trying to - * hit. If we do int division, we'll end up with one level lower - * than the one he was going for. - */ - int deltaLevels = Math.round((float) totalDeltaAngle / mTickDelta); - if (deltaLevels != 0) { - boolean canStillZoom = mCallback.onZoomRingThumbDragged( - deltaLevels, mThumbDragStartAngle, touchAngle); - - if (mVibration) { - // TODO: we're trying the haptics to see how it goes with - // users, so we're ignoring the settings (for now) - performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING | - HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); - } - - // Set the callback angle to the actual angle based on how many delta levels we gave - mPreviousCallbackTickAngle = getValidAngle( - mPreviousCallbackTickAngle + (deltaLevels * mTickDelta)); - } - } - - if (DRAW_TRAIL) { - int deltaAngle = getDelta(mThumbAngle, touchAngle, useDirection, ccw); - mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER); - } - - if (animateThumbToNewAngle) { - if (useDirection) { - setThumbAngleAnimated(touchAngle, 0, ccw); - } else { - setThumbAngleAnimated(touchAngle, 0); - } - } else { - setThumbAngleAuto(touchAngle, useDirection, ccw); - } - } -// private void onThumbDragged(int touchAngle, boolean useDirection, boolean ccw) { -// int deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, useDirection, ccw); -// -// if (!useDirection) { -// // Set ccw to match the direction found by getDelta -// ccw = deltaPrevCbAndTouch > 0; -// useDirection = true; -// } -// -// boolean animateThumbToNewAngle = false; -// boolean animationCcw = ccw; -// -// if (deltaPrevCbAndTouch >= mFuzzyCallbackThreshold -// || deltaPrevCbAndTouch <= -mFuzzyCallbackThreshold) { -// -// /* -// * When the user slides the thumb through the tick that corresponds -// * to a zoom bound, we don't want to abruptly stop there. Instead, -// * let the user slide it to the next tick, and then animate it back -// * to the original zoom bound tick. Because of this, we make sure -// * the delta from the bound is more than halfway to the next tick. -// * We make sure the bound is between the touch and the previous -// * callback to ensure we JUST passed the bound. -// */ -// int oldTouchAngle = touchAngle; -// if (ccw && mThumbCcwBound != Integer.MIN_VALUE) { -// int deltaCcwBoundAndTouch = -// getDelta(mThumbCcwBound, touchAngle, true, ccw); -// if (deltaCcwBoundAndTouch >= mCallbackThreshold / 2) { -// // The touch has past far enough from the bound -// int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle, -// touchAngle, true, ccw); -// if (deltaPreviousCbAndTouch >= deltaCcwBoundAndTouch) { -// // The bound is between the previous callback angle and the touch -// // Cap to the bound -// touchAngle = mThumbCcwBound; -// /* -// * We're moving the touch BACK to the bound, so animate -// * back in the opposite direction that passed the bound. -// */ -// animationCcw = false; -// } -// } -// } else if (!ccw && mThumbCwBound != Integer.MIN_VALUE) { -// // See block above for general comments -// int deltaCwBoundAndTouch = -// getDelta(mThumbCwBound, touchAngle, true, ccw); -// if (deltaCwBoundAndTouch <= -mCallbackThreshold / 2) { -// int deltaPreviousCbAndTouch = getDelta(mPreviousCallbackAngle, -// touchAngle, true, ccw); -// /* -// * Both of these will be negative since we got delta in -// * clockwise direction, and we want the magnitude of -// * deltaPreviousCbAndTouch to be greater than the magnitude -// * of deltaCwBoundAndTouch -// */ -// if (deltaPreviousCbAndTouch <= deltaCwBoundAndTouch) { -// touchAngle = mThumbCwBound; -// animationCcw = true; -// } -// } -// } -// if (touchAngle != oldTouchAngle) { -// // We bounded the touch angle -// deltaPrevCbAndTouch = getDelta(mPreviousCallbackAngle, touchAngle, true, ccw); -// // Animate back to the bound -// animateThumbToNewAngle = true; -// // Disallow movement now -// setMode(MODE_IGNORE_UNTIL_UP); -// } -// -// -// /* -// * Prevent it from jumping too far (this could happen if the user -// * goes through the center) -// */ -// -// if (mEnforceMaxAbsJump) { -// if (deltaPrevCbAndTouch <= -MAX_ABS_JUMP_DELTA_ANGLE) { -// deltaPrevCbAndTouch = -MAX_ABS_JUMP_DELTA_ANGLE; -// animateThumbToNewAngle = true; -// } else if (deltaPrevCbAndTouch >= MAX_ABS_JUMP_DELTA_ANGLE) { -// deltaPrevCbAndTouch = MAX_ABS_JUMP_DELTA_ANGLE; -// animateThumbToNewAngle = true; -// } -// } -// -// /* -// * We need to cover the edge case of a user grabbing the thumb, -// * going into the center of the widget, and then coming out from the -// * center to an angle that's slightly below the angle he's trying to -// * hit. If we do int division, we'll end up with one level lower -// * than the one he was going for. -// */ -// int deltaLevels = Math.round((float) deltaPrevCbAndTouch / mCallbackThreshold); -// if (deltaLevels != 0) { -// boolean canStillZoom = mCallback.onZoomRingThumbDragged( -// deltaLevels, mThumbDragStartAngle, touchAngle); -// -// if (mVibration) { -// // TODO: we're trying the haptics to see how it goes with -// // users, so we're ignoring the settings (for now) -// performHapticFeedback(HapticFeedbackConstants.ZOOM_RING_TICK, -// HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING | -// HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); -// -// } -// // Set the callback angle to the actual angle based on how many delta levels we gave -// mPreviousCallbackAngle = getValidAngle( -// mPreviousCallbackAngle + (deltaLevels * mCallbackThreshold)); -// } -// } -// -// if (DRAW_TRAIL) { -// int deltaAngle = getDelta(mThumbAngle, touchAngle, true, ccw); -// mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER); -// } -// -// if (animateThumbToNewAngle) { -// setThumbAngleAnimated(touchAngle, 0, animationCcw); -// } else { -// /* -// * Use regular ccw here because animationCcw will never have been -// * changed if animateThumbToNewAngle is false -// */ -// setThumbAngleAuto(touchAngle, true, ccw); -// } -// } - - private int getValidAngle(int invalidAngle) { - if (invalidAngle < 0) { - return (invalidAngle % TWO_PI_INT_MULTIPLIED) + TWO_PI_INT_MULTIPLIED; - } else if (invalidAngle >= TWO_PI_INT_MULTIPLIED) { - return invalidAngle % TWO_PI_INT_MULTIPLIED; - } else { - return invalidAngle; - } - } - - private int getClosestTickAngle(int angle) { - int smallerAngleDistance = angle % mTickDelta; - int smallerAngle = angle - smallerAngleDistance; - if (smallerAngleDistance < mTickDelta / 2) { - // Closer to the smaller angle - return smallerAngle; - } else { - // Closer to the bigger angle (premodding) - return (smallerAngle + mTickDelta) % TWO_PI_INT_MULTIPLIED; - } - } - - private void onThumbDragStopped() { - mCallback.onZoomRingThumbDraggingStopped(); - } - - private void onZoomRingMoved(int rawX, int rawY) { - if (mPreviousWidgetDragX != Integer.MIN_VALUE) { - int deltaX = rawX - mPreviousWidgetDragX; - int deltaY = rawY - mPreviousWidgetDragY; - - mCallback.onZoomRingMoved(deltaX, deltaY, rawX, rawY); - } - - mPreviousWidgetDragX = rawX; - mPreviousWidgetDragY = rawY; - } - - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - - if (!hasWindowFocus) { - mCallback.onZoomRingDismissed(); - } - } - - private int getAngle(int localX, int localY) { - int radians = (int) (Math.atan2(localY, localX) * RADIAN_INT_MULTIPLIER); - - // Convert from [-pi,pi] to {0,2pi] - if (radians < 0) { - radians = -radians; - } else if (radians > 0) { - radians = 2 * PI_INT_MULTIPLIED - radians; - } else { - radians = 0; - } - - radians = radians - mZeroAngle; - return radians >= 0 ? radians : radians + 2 * PI_INT_MULTIPLIED; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mThumbVisible) { - if (DRAW_TRAIL) { - mTrail.draw(canvas); - } - if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) { - mZoomInArrowDrawable.draw(canvas); - mZoomInArrowHintDrawable.draw(canvas); - } - if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) { - mZoomOutArrowDrawable.draw(canvas); - mZoomOutArrowHintDrawable.draw(canvas); - } - mThumbDrawable.draw(canvas); - } - } - - private void setThumbArrowsAngle(int angle) { - int level = -angle * 10000 / ZoomRing.TWO_PI_INT_MULTIPLIED; - mZoomInArrowDrawable.setLevel(level); - mZoomOutArrowDrawable.setLevel(level); - - // Assume it is a square - int halfSideLength = mZoomInArrowHintDrawable.getIntrinsicHeight() / 2; - int unoffsetAngle = angle + mZeroAngle; - - 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 + 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); - } - - void setThumbArrowsVisible(boolean visible) { - if (visible) { - mThumbArrowsAlpha = 255; - int callbackAngle = mPreviousCallbackTickAngle; - if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR || - callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) { - 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) { - mZoomOutArrowDrawable.setAlpha(255); - mZoomOutArrowHintDrawable.setAlpha(255); - mThumbArrowsToDraw |= THUMB_ARROW_MINUS; - } else { - mThumbArrowsToDraw &= ~THUMB_ARROW_MINUS; - } - invalidate(); - } else if (mThumbArrowsAlpha == 255) { - // Only start fade if we're fully visible (otherwise another fade is happening already) - mThumbArrowsFadeStartTime = SystemClock.elapsedRealtime(); - onThumbArrowsFadeStep(); - } - } - - private void onThumbArrowsFadeStep() { - if (mThumbArrowsAlpha <= 0) { - mThumbArrowsToDraw = 0; - return; - } - - mThumbArrowsAlpha = (int) - (255 - (255 * (SystemClock.elapsedRealtime() - mThumbArrowsFadeStartTime) - / THUMB_ARROWS_FADE_DURATION)); - if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0; - if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) { - mZoomInArrowDrawable.setAlpha(mThumbArrowsAlpha); - mZoomInArrowHintDrawable.setAlpha(mThumbArrowsAlpha); - invalidateDrawable(mZoomInArrowHintDrawable); - invalidateDrawable(mZoomInArrowDrawable); - } - if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) { - mZoomOutArrowDrawable.setAlpha(mThumbArrowsAlpha); - mZoomOutArrowHintDrawable.setAlpha(mThumbArrowsAlpha); - invalidateDrawable(mZoomOutArrowHintDrawable); - invalidateDrawable(mZoomOutArrowDrawable); - } - - if (!mHandler.hasMessages(MSG_THUMB_ARROWS_FADE_STEP)) { - mHandler.sendEmptyMessage(MSG_THUMB_ARROWS_FADE_STEP); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - setThumbArrowsAngle(mThumbAngle); - setThumbArrowsVisible(true); - } - - public interface OnZoomRingCallback { - void onZoomRingSetMovableHintVisible(boolean visible); - - void onZoomRingMovingStarted(); - boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY); - void onZoomRingMovingStopped(); - - void onZoomRingThumbDraggingStarted(); - boolean onZoomRingThumbDragged(int numLevels, int startAngle, int curAngle); - void onZoomRingThumbDraggingStopped(); - - void onZoomRingDismissed(); - - void onUserInteractionStarted(); - void onUserInteractionStopped(); - } - - private static void printAngle(String angleName, int angle) { - Log.d(TAG, angleName + ": " + (long) angle * 180 / PI_INT_MULTIPLIED); - } -} diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java deleted file mode 100644 index 3bf3b227dba04..0000000000000 --- a/core/java/android/widget/ZoomRingController.java +++ /dev/null @@ -1,1383 +0,0 @@ -/* - * 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 android.app.AlertDialog; -import android.app.Dialog; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.provider.Settings; -import android.util.DisplayMetrics; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.Window; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; - -/** - * A controller to simplify the use of the zoom ring widget. - *

- * If you are using this with a custom View, please call - * {@link #setVisible(boolean) setVisible(false)} from the - * {@link View#onDetachedFromWindow}. - * - * @hide - */ -public class ZoomRingController implements ZoomRing.OnZoomRingCallback, - View.OnTouchListener, View.OnKeyListener { - - // Temporary methods for different zoom types - static int getZoomType(Context context) { - return Settings.System.getInt(context.getContentResolver(), "zoom", 1); - } - public static boolean useOldZoom(Context context) { - return getZoomType(context) == 0; - } - private static boolean useThisZoom(Context context) { - return getZoomType(context) == 1; - } - - /** The duration for the animation to re-center the zoom ring. */ - private static final int RECENTERING_DURATION = 500; - - /** 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; - private WindowManager mWindowManager; - - /** - * The view that is being zoomed by this zoom ring. - */ - private View mOwnerView; - - /** - * The bounds of the owner view in global coordinates. This is recalculated - * each time the zoom ring is shown. - */ - private Rect mOwnerViewBounds = new Rect(); - - /** - * The container that is added as a window. - */ - private FrameLayout mContainer; - private LayoutParams mContainerLayoutParams; - - /** - * The view (or null) that should receive touch events. This will get set if - * the touch down hits the container. It will be reset on the touch up. - */ - private View mTouchTargetView; - /** - * The {@link #mTouchTargetView}'s location in window, set on touch down. - */ - private int[] mTouchTargetLocationInWindow = new int[2]; - /** - * If the zoom ring is dismissed but the user is still in a touch - * interaction, we set this to true. This will ignore all touch events until - * up/cancel, and then set the owner's touch listener to null. - */ - private boolean mReleaseTouchListenerOnUp; - - - /* - * Tap-drag is an interaction where the user first taps and then (quickly) - * does the clockwise or counter-clockwise drag. In reality, this is: (down, - * up, down, move in circles, up). This differs from the usual events of: - * (down, up, down, up, down, move in circles, up). While the only - * difference is the omission of an (up, down), for power-users this is a - * pretty big improvement as it now only requires them to focus on the - * 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; - /** - * 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; - /** 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 - * accumulator for how much the finger has moved off of its original touch - * point on the zoom ring (OOB = out-of-bounds). If < 0, the finger has - * moved that many px to the left of its original touch point on the ring. - */ - private int mMovingZoomRingOobX; - /** 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; - - /** - * When the zoom ring is centered on screen, this will be the x value used - * for the container's layout params. - */ - private int mCenteredContainerX = Integer.MIN_VALUE; - - /** - * When the zoom ring is centered on screen, this will be the y value used - * for the container's layout params. - */ - private int mCenteredContainerY = Integer.MIN_VALUE; - - /** - * Scroller used to re-center the zoom ring if the user had dragged it to a - * corner and then double-taps any point on the owner view (the owner view - * will center the double-tapped point, but we should re-center the zoom - * ring). - *

- * The (x,y) of the scroller is the (x,y) of the container's layout params. - */ - private Scroller mScroller; - - /** - * When showing the zoom ring, we add the view as a new window. However, - * there is logic that needs to know the size of the zoom ring which is - * determined after it's laid out. Therefore, we must post this logic onto - * the UI thread so it will be exceuted AFTER the layout. This is the logic. - */ - private Runnable mPostedVisibleInitializer; - - /** - * Only touch from the main thread. - */ - private static Dialog sTutorialDialog; - private static long sTutorialShowTime; - private static final int TUTORIAL_MIN_DISPLAY_TIME = 2000; - - private IntentFilter mConfigurationChangedFilter = - new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); - - /** 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) { - if (!mIsZoomRingVisible) return; - - mHandler.removeMessages(MSG_POST_CONFIGURATION_CHANGED); - mHandler.sendEmptyMessage(MSG_POST_CONFIGURATION_CHANGED); - } - }; - - /** Keeps the scroller going (or starts it). */ - 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. */ - private static final int MSG_DISMISS_ZOOM_RING = 3; - - /** - * If setVisible(true) is called and the owner view's window token is null, - * we delay the setVisible(true) call until it is not null. - */ - private static final int MSG_POST_SET_VISIBLE = 4; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_SCROLLER_STEP: - onScrollerStep(); - break; - - case MSG_POST_CONFIGURATION_CHANGED: - onPostConfigurationChanged(); - break; - - case MSG_DISMISS_ZOOM_RING: - setVisible(false); - break; - - case MSG_POST_SET_VISIBLE: - if (mOwnerView.getWindowToken() == null) { - // Doh, it is still null, throw an exception - throw new IllegalArgumentException( - "Cannot make the zoom ring visible if the owner view is " + - "not attached to a window."); - } - setVisible(true); - break; - } - - } - }; - - public ZoomRingController(Context context, View ownerView) { - mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - mPanner = new Panner(); - mOwnerView = ownerView; - - mZoomRing = new ZoomRing(context); - mZoomRing.setId(com.android.internal.R.id.zoomControls); - mZoomRing.setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.CENTER)); - mZoomRing.setCallback(this); - - createPanningArrows(); - - mContainerLayoutParams = new LayoutParams(); - mContainerLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - mContainerLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCHABLE | - LayoutParams.FLAG_NOT_FOCUSABLE | - LayoutParams.FLAG_LAYOUT_NO_LIMITS; - mContainerLayoutParams.height = LayoutParams.WRAP_CONTENT; - mContainerLayoutParams.width = LayoutParams.WRAP_CONTENT; - mContainerLayoutParams.type = LayoutParams.TYPE_APPLICATION_PANEL; - mContainerLayoutParams.format = PixelFormat.TRANSPARENT; - mContainerLayoutParams.windowAnimations = com.android.internal.R.style.Animation_ZoomRing; - - mContainer = new FrameLayout(context); - mContainer.setLayoutParams(mContainerLayoutParams); - mContainer.setMeasureAllChildren(true); - - mContainer.addView(mZoomRing); - mContainer.addView(mPanningArrows); - - mScroller = new Scroller(context, new DecelerateInterpolator()); - - 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() { - mPanningArrows = new ImageView(mContext); - mPanningArrows.setImageDrawable(mZoomRing.getPanningArrowsDrawable()); - mPanningArrows.setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.CENTER)); - mPanningArrows.setVisibility(View.INVISIBLE); - - mPanningArrowsEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.fade_in); - mPanningArrowsExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.fade_out); - } - - /** - * 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 setTickDelta(float angle) { - mZoomRing.setTickDelta((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER)); - } - - /** - * Sets a drawable for the zoom ring track. - * - * @param drawable The drawable to use for the track. - * @hide Need a better way of doing this, but this one-off for browser so it - * can have its final look for the usability study - */ - public void 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)); - } - - public void setThumbAngleAnimated(float angle) { - mZoomRing.setThumbAngleAnimated((int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER), 0); - } - - public void setThumbVisible(boolean thumbVisible) { - mZoomRing.setThumbVisible(thumbVisible); - } - - public void setThumbClockwiseBound(float angle) { - mZoomRing.setThumbClockwiseBound(angle >= 0 ? - (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) : - Integer.MIN_VALUE); - } - - public void setThumbCounterclockwiseBound(float angle) { - mZoomRing.setThumbCounterclockwiseBound(angle >= 0 ? - (int) (angle * ZoomRing.RADIAN_INT_MULTIPLIER) : - Integer.MIN_VALUE); - } - - public boolean isVisible() { - return mIsZoomRingVisible; - } - - public void setVisible(boolean visible) { - - if (!useThisZoom(mContext)) return; - - if (visible) { - if (mOwnerView.getWindowToken() == null) { - /* - * We need a window token to show ourselves, maybe the owner's - * window hasn't been created yet but it will have been by the - * time the looper is idle, so post the setVisible(true) call. - */ - if (!mHandler.hasMessages(MSG_POST_SET_VISIBLE)) { - mHandler.sendEmptyMessage(MSG_POST_SET_VISIBLE); - } - return; - } - - dismissZoomRingDelayed(INACTIVITY_TIMEOUT); - } else { - mPanner.stop(); - } - - if (mIsZoomRingVisible == visible) { - return; - } - mIsZoomRingVisible = visible; - - if (visible) { - if (mContainerLayoutParams.token == null) { - mContainerLayoutParams.token = mOwnerView.getWindowToken(); - } - - mWindowManager.addView(mContainer, mContainerLayoutParams); - - if (mPostedVisibleInitializer == null) { - mPostedVisibleInitializer = new Runnable() { - public void run() { - refreshPositioningVariables(); - resetZoomRing(); - refreshContainerLayout(); - - if (mCallback != null) { - mCallback.onVisibilityChanged(true); - } - } - }; - } - - mPanningArrows.setAnimation(null); - - mHandler.post(mPostedVisibleInitializer); - - // Handle configuration changes when visible - mContext.registerReceiver(mConfigurationChangedReceiver, mConfigurationChangedFilter); - - // Steal key/touches events from the owner - mOwnerView.setOnKeyListener(this); - mOwnerView.setOnTouchListener(this); - mReleaseTouchListenerOnUp = false; - - } else { - // Don't want to steal any more keys/touches - mOwnerView.setOnKeyListener(null); - if (mTouchTargetView != null) { - // We are still stealing the touch events for this touch - // sequence, so release the touch listener later - mReleaseTouchListenerOnUp = true; - } else { - mOwnerView.setOnTouchListener(null); - } - - // No longer care about configuration changes - mContext.unregisterReceiver(mConfigurationChangedReceiver); - - mWindowManager.removeView(mContainer); - mHandler.removeCallbacks(mPostedVisibleInitializer); - - if (mCallback != null) { - mCallback.onVisibilityChanged(false); - } - } - - } - - private void refreshContainerLayout() { - if (mIsZoomRingVisible) { - mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); - } - } - - /** - * Returns the container of the zoom ring widget. The client can add views - * here to be shown alongside the zoom ring. See {@link #getZoomRingId()}. - *

- * Notes: - *

    - *
  • The controller dispatches touch events differently than the regular view - * framework. - *
  • Please ensure you set your view to INVISIBLE not GONE when hiding it. - *
- * - * @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(); - } - - private void dismissZoomRingDelayed(int delay) { - mHandler.removeMessages(MSG_DISMISS_ZOOM_RING); - mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_RING, delay); - } - - private void resetZoomRing() { - mScroller.abortAnimation(); - - mContainerLayoutParams.x = mCenteredContainerX; - mContainerLayoutParams.y = mCenteredContainerY; - - // Reset the thumb - mZoomRing.resetThumbAngle(); - } - - /** - * Should be called by the client for each event belonging to the second tap - * (the down, move, up, and cancel events). - *

- * 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. - */ - public boolean handleDoubleTapEvent(MotionEvent event) { - if (!useThisZoom(mContext)) return false; - - int action = event.getAction(); - - // TODO: make sure this works well with the - // ownerView.setOnTouchListener(this) instead of window receiving - // touches - if (action == MotionEvent.ACTION_DOWN) { - mTouchMode = TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT; - int x = (int) event.getX(); - int y = (int) event.getY(); - - refreshPositioningVariables(); - setVisible(true); - centerPoint(x, y); - ensureZoomRingIsCentered(); - - // Tap drag mode stuff - mTapDragStartX = x; - mTapDragStartY = y; - - } else if (action == MotionEvent.ACTION_CANCEL) { - mTouchMode = TOUCH_MODE_IDLE; - - } else { // action is move or up - switch (mTouchMode) { - case TOUCH_MODE_WAITING_FOR_TAP_DRAG_MOVEMENT: { - switch (action) { - case MotionEvent.ACTION_MOVE: - int x = (int) event.getX(); - int y = (int) event.getY(); - if (Math.abs(x - mTapDragStartX) > mScaledTouchSlop || - Math.abs(y - mTapDragStartY) > - mScaledTouchSlop) { - mZoomRing.setTapDragMode(true, x, y); - mTouchMode = TOUCH_MODE_FORWARDING_FOR_TAP_DRAG; - setTouchTargetView(mZoomRing); - } - return true; - - case MotionEvent.ACTION_UP: - mTouchMode = TOUCH_MODE_IDLE; - break; - } - break; - } - - case TOUCH_MODE_FORWARDING_FOR_TAP_DRAG: { - switch (action) { - case MotionEvent.ACTION_MOVE: - giveTouchToZoomRing(event); - return true; - - case MotionEvent.ACTION_UP: - mTouchMode = TOUCH_MODE_IDLE; - - /* - * This is a power-user feature that only shows the - * zoom while the user is performing the tap-drag. - * That means once it is released, the zoom ring - * should disappear. - */ - mZoomRing.setTapDragMode(false, (int) event.getX(), (int) event.getY()); - dismissZoomRingDelayed(0); - break; - } - break; - } - } - } - - return true; - } - - private void ensureZoomRingIsCentered() { - LayoutParams lp = mContainerLayoutParams; - - if (lp.x != mCenteredContainerX || lp.y != mCenteredContainerY) { - int width = mContainer.getWidth(); - int height = mContainer.getHeight(); - mScroller.startScroll(lp.x, lp.y, mCenteredContainerX - lp.x, - mCenteredContainerY - lp.y, RECENTERING_DURATION); - mHandler.sendEmptyMessage(MSG_SCROLLER_STEP); - } - } - - private void refreshPositioningVariables() { - mZoomRingWidth = mZoomRing.getWidth(); - mZoomRingHeight = mZoomRing.getHeight(); - - // Calculate the owner view's bounds - mOwnerView.getGlobalVisibleRect(mOwnerViewBounds); - - // Get the center - Gravity.apply(Gravity.CENTER, mContainer.getWidth(), mContainer.getHeight(), - mOwnerViewBounds, mTempRect); - mCenteredContainerX = mTempRect.left; - mCenteredContainerY = mTempRect.top; - } - - /** - * Centers the point (in owner view's coordinates). - */ - private void centerPoint(int x, int y) { - if (mCallback != null) { - mCallback.onCenter(x, y); - } - } - - private void giveTouchToZoomRing(MotionEvent event) { - int rawX = (int) event.getRawX(); - int rawY = (int) event.getRawY(); - int x = rawX - mContainerLayoutParams.x - mZoomRing.getLeft(); - int y = rawY - mContainerLayoutParams.y - mZoomRing.getTop(); - 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(INACTIVITY_TIMEOUT); - } - - /** @hide */ - public void onZoomRingMovingStarted() { - mScroller.abortAnimation(); - mTouchingEdgeStartTime = 0; - mMovingZoomRingOobX = 0; - mMovingZoomRingOobY = 0; - if (mCallback != null) { - mCallback.onBeginPan(); - } - } - - private void setPanningArrowsVisible(boolean visible) { - mPanningArrows.startAnimation(visible ? mPanningArrowsEnterAnimation - : mPanningArrowsExitAnimation); - mPanningArrows.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - } - - /** @hide */ - public boolean onZoomRingMoved(int deltaX, int deltaY, int rawX, int rawY) { - - if (mMovingZoomRingOobX != 0) { - /* - * The finger has moved off the point where it originally touched - * the zidget. - */ - boolean wasOobLeft = mMovingZoomRingOobX < 0; - mMovingZoomRingOobX += deltaX; - if ((wasOobLeft && mMovingZoomRingOobX > 0) || - (!wasOobLeft && mMovingZoomRingOobX < 0)) { - /* - * Woot, the finger is back on the original point. Infact, it - * went PAST its original point, so take the amount it passed - * and use that as the delta to move the zoom ring. - */ - deltaX = mMovingZoomRingOobX; - // No longer out-of-bounds, reset - mMovingZoomRingOobX = 0; - } else { - // The finger is still not back, eat this movement - deltaX = 0; - } - } - - if (mMovingZoomRingOobY != 0) { - // See above for comments - boolean wasOobUp = mMovingZoomRingOobY < 0; - mMovingZoomRingOobY += deltaY; - if ((wasOobUp && mMovingZoomRingOobY > 0) || (!wasOobUp && mMovingZoomRingOobY < 0)) { - deltaY = mMovingZoomRingOobY; - mMovingZoomRingOobY = 0; - } else { - deltaY = 0; - } - } - - WindowManager.LayoutParams lp = mContainerLayoutParams; - Rect ownerBounds = mOwnerViewBounds; - - int zoomRingLeft = mZoomRing.getLeft(); - int zoomRingTop = mZoomRing.getTop(); - - int newX = lp.x + deltaX; - int newZoomRingX = newX + zoomRingLeft; - newZoomRingX = (newZoomRingX <= ownerBounds.left) ? ownerBounds.left : - (newZoomRingX + mZoomRingWidth > ownerBounds.right) ? - ownerBounds.right - mZoomRingWidth : newZoomRingX; - lp.x = newZoomRingX - zoomRingLeft; - - int newY = lp.y + deltaY; - int newZoomRingY = newY + zoomRingTop; - newZoomRingY = (newZoomRingY <= ownerBounds.top) ? ownerBounds.top : - (newZoomRingY + mZoomRingHeight > ownerBounds.bottom) ? - ownerBounds.bottom - mZoomRingHeight : newZoomRingY; - lp.y = newZoomRingY - zoomRingTop; - - refreshContainerLayout(); - - // Check for pan - boolean horizontalPanning = true; - int leftGap = newZoomRingX - ownerBounds.left; - 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); - } - if (shouldPan(leftGap)) { - mPanner.setHorizontalStrength(-getStrengthFromGap(leftGap)); - } - } else { - int rightGap = ownerBounds.right - (lp.x + mZoomRingWidth + zoomRingLeft); - if (rightGap < mPanGap) { - if (rightGap == 0 && deltaX != 0 && mMovingZoomRingOobX == 0) { - mMovingZoomRingOobX = deltaX / Math.abs(deltaX); - } - if (shouldPan(rightGap)) { - mPanner.setHorizontalStrength(getStrengthFromGap(rightGap)); - } - } else { - mPanner.setHorizontalStrength(0); - horizontalPanning = false; - } - } - - int topGap = newZoomRingY - ownerBounds.top; - if (topGap < mPanGap) { - if (topGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) { - mMovingZoomRingOobY = deltaY / Math.abs(deltaY); - } - if (shouldPan(topGap)) { - mPanner.setVerticalStrength(-getStrengthFromGap(topGap)); - } - } else { - int bottomGap = ownerBounds.bottom - (lp.y + mZoomRingHeight + zoomRingTop); - if (bottomGap < mPanGap) { - if (bottomGap == 0 && deltaY != 0 && mMovingZoomRingOobY == 0) { - mMovingZoomRingOobY = deltaY / Math.abs(deltaY); - } - if (shouldPan(bottomGap)) { - mPanner.setVerticalStrength(getStrengthFromGap(bottomGap)); - } - } else { - mPanner.setVerticalStrength(0); - if (!horizontalPanning) { - // Neither are panning, reset any timer to start pan mode - mTouchingEdgeStartTime = 0; - mPanningInitiated = false; - mPanner.stop(); - } - } - } - - return true; - } - - private boolean shouldPan(int gap) { - if (mPanningInitiated) return true; - - if (gap < mInitiatePanGap) { - long time = SystemClock.elapsedRealtime(); - if (mTouchingEdgeStartTime != 0 && - mTouchingEdgeStartTime + INITIATE_PAN_DELAY < time) { - mPanningInitiated = true; - return true; - } else if (mTouchingEdgeStartTime == 0) { - mTouchingEdgeStartTime = time; - } else { - } - } else { - // Moved away from the initiate pan gap, so reset the timer - mTouchingEdgeStartTime = 0; - } - return false; - } - - /** @hide */ - public void onZoomRingMovingStopped() { - mPanner.stop(); - setPanningArrowsVisible(false); - if (mCallback != null) { - mCallback.onEndPan(); - } - } - - private int getStrengthFromGap(int 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; - - return mCallback.onDragZoom(deltaZoomLevel, - getZoomRingCenterXInOwnerCoordinates(), - getZoomRingCenterYInOwnerCoordinates(), - (float) startAngle / ZoomRing.RADIAN_INT_MULTIPLIER, - (float) curAngle / ZoomRing.RADIAN_INT_MULTIPLIER); - } - - 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(); - } - } - - /** @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) { - finishZoomTutorial(); - } - - int action = event.getAction(); - - if (mReleaseTouchListenerOnUp) { - // The ring was dismissed but we need to throw away all events until the up - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - mOwnerView.setOnTouchListener(null); - setTouchTargetView(null); - mReleaseTouchListenerOnUp = false; - } - - // Eat this event - return true; - } - - View targetView = mTouchTargetView; - - switch (action) { - case MotionEvent.ACTION_DOWN: - targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY()); - setTouchTargetView(targetView); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - setTouchTargetView(null); - break; - } - - if (targetView != null) { - // The upperleft corner of the target view in raw coordinates - int targetViewRawX = mContainerLayoutParams.x + mTouchTargetLocationInWindow[0]; - int targetViewRawY = mContainerLayoutParams.y + mTouchTargetLocationInWindow[1]; - - MotionEvent containerEvent = MotionEvent.obtain(event); - // Convert the motion event into the target view's coordinates (from - // owner view's coordinates) - containerEvent.offsetLocation(mOwnerViewBounds.left - targetViewRawX, - mOwnerViewBounds.top - targetViewRawY); - boolean retValue = targetView.dispatchTouchEvent(containerEvent); - containerEvent.recycle(); - return retValue; - - } else { -// dismissZoomRingDelayed(ZOOM_CONTROLS_TIMEOUT); - if (action == MotionEvent.ACTION_DOWN) { - dismissZoomRingDelayed(OUTSIDE_TAP_DISMISS_DELAY); - } - - return false; - } - } - - private void setTouchTargetView(View view) { - mTouchTargetView = view; - if (view != null) { - view.getLocationInWindow(mTouchTargetLocationInWindow); - } - } - - /** - * Returns the View that should receive a touch at the given coordinates. - * - * @param rawX The raw X. - * @param rawY The raw Y. - * @return The view that should receive the touches, or null if there is not one. - */ - private View getViewForTouch(int rawX, int rawY) { - // Check to see if it is touching the ring - int containerCenterX = mContainerLayoutParams.x + mContainer.getWidth() / 2; - int containerCenterY = mContainerLayoutParams.y + mContainer.getHeight() / 2; - int distanceFromCenterX = rawX - containerCenterX; - int distanceFromCenterY = rawY - containerCenterY; - int zoomRingRadius = mZoomRing.getTrackOuterRadius(); - if (distanceFromCenterX * distanceFromCenterX + - distanceFromCenterY * distanceFromCenterY <= - zoomRingRadius * zoomRingRadius) { - return mZoomRing; - } - - // Check to see if it is touching any other clickable View. - // Reverse order so the child drawn on top gets first dibs. - int containerCoordsX = rawX - mContainerLayoutParams.x; - int containerCoordsY = rawY - mContainerLayoutParams.y; - Rect frame = mTempRect; - for (int i = mContainer.getChildCount() - 1; i >= 0; i--) { - View child = mContainer.getChildAt(i); - if (child == mZoomRing || child.getVisibility() != View.VISIBLE || - !child.isClickable()) { - continue; - } - - child.getHitRect(frame); - if (frame.contains(containerCoordsX, containerCoordsY)) { - return child; - } - } - - return null; - } - - /** - * Steals key events from the owner view. - * - * @hide - */ - public boolean onKey(View v, int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - // Eat these - return true; - - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - // Keep the zoom alive a little longer - 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, - getZoomRingCenterXInOwnerCoordinates(), - getZoomRingCenterYInOwnerCoordinates()); - } - - return true; - } - - return false; - } - - private void onScrollerStep() { - if (!mScroller.computeScrollOffset() || !mIsZoomRingVisible) return; - - mContainerLayoutParams.x = mScroller.getCurrX(); - mContainerLayoutParams.y = mScroller.getCurrY(); - refreshContainerLayout(); - - mHandler.sendEmptyMessage(MSG_SCROLLER_STEP); - } - - private void onPostConfigurationChanged() { - dismissZoomRingDelayed(INACTIVITY_TIMEOUT); - refreshPositioningVariables(); - ensureZoomRingIsCentered(); - } - - /* - * This is static so Activities can call this instead of the Views - * (Activities usually do not have a reference to the ZoomRingController - * instance.) - */ - /** - * Shows a "tutorial" (some text) to the user teaching her the new zoom - * invocation method. Must call from the main thread. - *

- * It checks the global system setting to ensure this has not been seen - * before. Furthermore, if the application does not have privilege to write - * to the system settings, it will store this bit locally in a shared - * preference. - * - * @hide This should only be used by our main apps--browser, maps, and - * gallery - */ - public static void showZoomTutorialOnce(Context context) { - ContentResolver cr = context.getContentResolver(); - if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TOAST, 0) == 1) { - return; - } - - SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); - if (sp.getInt(SETTING_NAME_SHOWN_TOAST, 0) == 1) { - return; - } - - if (sTutorialDialog != null && sTutorialDialog.isShowing()) { - sTutorialDialog.dismiss(); - } - - LayoutInflater layoutInflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - TextView textView = (TextView) layoutInflater.inflate( - com.android.internal.R.layout.alert_dialog_simple_text, null) - .findViewById(android.R.id.text1); - textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short); - - sTutorialDialog = new AlertDialog.Builder(context) - .setView(textView) - .setIcon(0) - .create(); - - Window window = sTutorialDialog.getWindow(); - window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | - WindowManager.LayoutParams.FLAG_BLUR_BEHIND); - window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); - - sTutorialDialog.show(); - sTutorialShowTime = SystemClock.elapsedRealtime(); - } - - /** @hide Should only be used by Android platform apps */ - public static void finishZoomTutorial(Context context, boolean userNotified) { - if (sTutorialDialog == null) return; - - sTutorialDialog.dismiss(); - sTutorialDialog = null; - - // Record that they have seen the tutorial - if (userNotified) { - try { - Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TOAST, 1); - } catch (SecurityException e) { - /* - * The app does not have permission to clear this global flag, make - * sure the user does not see the message when he comes back to this - * same app at least. - */ - SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); - sp.edit().putInt(SETTING_NAME_SHOWN_TOAST, 1).commit(); - } - } - } - - /** @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; - } - - private class Panner implements Runnable { - private static final int RUN_DELAY = 15; - private static final float STOP_SLOWDOWN = 0.8f; - - private final Handler mUiHandler = new Handler(); - - private int mVerticalStrength; - private int mHorizontalStrength; - - private boolean mStopping; - - /** The time this current pan started. */ - private long mStartTime; - - /** The time of the last callback to pan the map/browser/etc. */ - private long mPreviousCallbackTime; - - // TODO Adjust to be DPI safe - private float mStartVelocity = 135; - private float mAcceleration = 160; - private float mMaxVelocity = 1000; - private int mStartAcceleratingDuration = 700; - private float mVelocity; - - /** -100 (full left) to 0 (none) to 100 (full right) */ - public void setHorizontalStrength(int horizontalStrength) { - if (mHorizontalStrength == 0 && mVerticalStrength == 0 && horizontalStrength != 0) { - start(); - } else if (mVerticalStrength == 0 && horizontalStrength == 0) { - stop(); - } - - mHorizontalStrength = horizontalStrength; - mStopping = false; - } - - /** -100 (full up) to 0 (none) to 100 (full down) */ - public void setVerticalStrength(int verticalStrength) { - if (mHorizontalStrength == 0 && mVerticalStrength == 0 && verticalStrength != 0) { - start(); - } else if (mHorizontalStrength == 0 && verticalStrength == 0) { - stop(); - } - - mVerticalStrength = verticalStrength; - mStopping = false; - } - - private void start() { - mUiHandler.post(this); - mPreviousCallbackTime = 0; - mStartTime = 0; - } - - public void stop() { - mStopping = true; - } - - public void run() { - if (mStopping) { - mHorizontalStrength *= STOP_SLOWDOWN; - mVerticalStrength *= STOP_SLOWDOWN; - } - - if (mHorizontalStrength == 0 && mVerticalStrength == 0) { - return; - } - - boolean firstRun = mPreviousCallbackTime == 0; - long curTime = SystemClock.elapsedRealtime(); - int panAmount = getPanAmount(mPreviousCallbackTime, curTime); - mPreviousCallbackTime = curTime; - - if (firstRun) { - mStartTime = curTime; - mVelocity = mStartVelocity; - } else { - int panX = panAmount * mHorizontalStrength / 100; - int panY = panAmount * mVerticalStrength / 100; - - if (mCallback != null) { - mCallback.onPan(panX, panY); - } - } - - mUiHandler.postDelayed(this, RUN_DELAY); - } - - private int getPanAmount(long previousTime, long currentTime) { - if (mVelocity > mMaxVelocity) { - mVelocity = mMaxVelocity; - } else if (mVelocity < mMaxVelocity) { - // See if it's time to add in some acceleration - if (currentTime - mStartTime > mStartAcceleratingDuration) { - mVelocity += (currentTime - previousTime) * mAcceleration / 1000; - } - } - - return (int) ((currentTime - previousTime) * mVelocity) / 1000; - } - - } - - /** - * 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(); - - /** - * 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/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 9030a3e377c26..adec0a750ebc5 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -43,7 +43,7 @@ interface IInputMethodManager { in ResultReceiver resultReceiver); boolean hideSoftInput(in IInputMethodClient client, int flags, in ResultReceiver resultReceiver); - void windowGainedFocus(in IInputMethodClient client, + void windowGainedFocus(in IInputMethodClient client, in IBinder windowToken, boolean viewHasFocus, boolean isTextEditor, int softInputMode, boolean first, int windowFlags); diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 6e5c4e07e2fe7..18f2878ad5ad1 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -27,6 +27,7 @@ LOCAL_SRC_FILES:= \ android_database_SQLiteProgram.cpp \ android_database_SQLiteQuery.cpp \ android_database_SQLiteStatement.cpp \ + android_emoji_EmojiFactory.cpp \ android_view_Display.cpp \ android_view_Surface.cpp \ android_view_ViewRoot.cpp \ @@ -132,6 +133,7 @@ LOCAL_C_INCLUDES += \ external/tremor/Tremor \ external/icu4c/i18n \ external/icu4c/common \ + frameworks/opt/emoji LOCAL_SHARED_LIBRARIES := \ libexpat \ @@ -156,12 +158,13 @@ LOCAL_SHARED_LIBRARIES := \ libicui18n \ libicudata \ libmedia \ - libwpa_client + libwpa_client \ + libemoji ifeq ($(BOARD_HAVE_BLUETOOTH),true) LOCAL_C_INCLUDES += \ external/dbus \ - external/bluez/libs/include + system/bluetooth/bluez-clean-headers LOCAL_CFLAGS += -DHAVE_BLUETOOTH LOCAL_SHARED_LIBRARIES += libbluedroid libdbus endif @@ -190,5 +193,4 @@ LOCAL_MODULE:= libandroid_runtime include $(BUILD_SHARED_LIBRARY) - include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 40dc2a1dc4d13..28c602b8dad34 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -92,6 +92,7 @@ extern int register_android_util_EventLog(JNIEnv* env); extern int register_android_util_Log(JNIEnv* env); extern int register_android_content_StringBlock(JNIEnv* env); extern int register_android_content_XmlBlock(JNIEnv* env); +extern int register_android_emoji_EmojiFactory(JNIEnv* env); extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); @@ -1026,6 +1027,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_AssetManager), REG_JNI(register_android_content_StringBlock), REG_JNI(register_android_content_XmlBlock), + REG_JNI(register_android_emoji_EmojiFactory), REG_JNI(register_android_security_Md5MessageDigest), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_KeyCharacterMap), diff --git a/core/jni/android_bluetooth_BluetoothAudioGateway.cpp b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp index 7f87d8028b5eb..bf2365094917f 100755 --- a/core/jni/android_bluetooth_BluetoothAudioGateway.cpp +++ b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp @@ -49,8 +49,6 @@ #ifdef HAVE_BLUETOOTH #include -#include -#include #include #include #endif diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp new file mode 100644 index 0000000000000..59f63a83cac54 --- /dev/null +++ b/core/jni/android_emoji_EmojiFactory.cpp @@ -0,0 +1,292 @@ +#include "SkTypes.h" +#include "SkImageDecoder.h" + +#define LOG_TAG "DoCoMoEmojiFactory_jni" +#include +#include + +#include "EmojiFactory.h" +#include + +#include +// #include + +namespace android { + +// Note: This class is originally developed so that libandroid_runtime does +// not have to depend on libemoji which is optional library. However, we +// cannot use this class, since current (2009-02-16) bionic libc does not allow +// dlopen()-ing inside dlopen(), while not only this class but also libemoji +// uses dlopen(). +class EmojiFactoryCaller { + public: + EmojiFactoryCaller(); + virtual ~EmojiFactoryCaller(); + EmojiFactory *TryCallGetImplementation(const char* name); + EmojiFactory *TryCallGetAvailableImplementation(); + private: + void *m_handle; + EmojiFactory *(*m_get_implementation)(const char*); + EmojiFactory *(*m_get_available_implementation)(); +}; + +EmojiFactoryCaller::EmojiFactoryCaller() { + m_handle = dlopen("libemoji.so", RTLD_LAZY | RTLD_LOCAL); + const char* error_str = dlerror(); + if (error_str) { + LOGI("Failed to load libemoji.so: %s", error_str); + return; + } + + m_get_implementation = + reinterpret_cast( + dlsym(m_handle, "GetImplementation")); + error_str = dlerror(); + if (error_str) { + LOGE("Failed to get symbol of GetImplementation: %s", error_str); + dlclose(m_handle); + m_handle = NULL; + return; + } + + m_get_available_implementation = + reinterpret_cast( + dlsym(m_handle,"GetAvailableImplementation")); + error_str = dlerror(); + if (error_str) { + LOGE("Failed to get symbol of GetAvailableImplementation: %s", error_str); + dlclose(m_handle); + m_handle = NULL; + return; + } +} + +EmojiFactoryCaller::~EmojiFactoryCaller() { + if (m_handle) { + dlclose(m_handle); + } +} + +EmojiFactory *EmojiFactoryCaller::TryCallGetImplementation( + const char* name) { + if (NULL == m_handle) { + return NULL; + } + return m_get_implementation(name); +} + +EmojiFactory *EmojiFactoryCaller::TryCallGetAvailableImplementation() { + if (NULL == m_handle) { + return NULL; + } + return m_get_available_implementation(); +} + +// Note: bionic libc's dlopen() does not allow recursive dlopen(). So currently +// we cannot use EmojiFactoryCaller here. +// static EmojiFactoryCaller* gCaller; +// static pthread_once_t g_once = PTHREAD_ONCE_INIT; + +static jclass gString_class; + +static jclass gBitmap_class; +static jmethodID gBitmap_constructorMethodID; + +static jclass gEmojiFactory_class; +static jmethodID gEmojiFactory_constructorMethodID; + +// static void InitializeCaller() { +// gCaller = new EmojiFactoryCaller(); +// } + +static jobject create_java_EmojiFactory( + JNIEnv* env, EmojiFactory* factory, jstring name) { + jobject obj = env->AllocObject(gEmojiFactory_class); + if (obj) { + env->CallVoidMethod(obj, gEmojiFactory_constructorMethodID, + (jint)factory, name); + if (env->ExceptionCheck() != 0) { + LOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + obj = NULL; + } + } + return obj; +} + +static jobject android_emoji_EmojiFactory_newInstance( + JNIEnv* env, jobject clazz, jstring name) { + // pthread_once(&g_once, InitializeCaller); + + if (NULL == name) { + return NULL; + } + + const jchar* jchars = env->GetStringChars(name, NULL); + jsize len = env->GetStringLength(name); + String8 str(String16(jchars, len)); + + // EmojiFactory *factory = gCaller->TryCallGetImplementation(str.string()); + EmojiFactory *factory = EmojiFactory::GetImplementation(str.string()); + + env->ReleaseStringChars(name, jchars); + + return create_java_EmojiFactory(env, factory, name); +} + +static jobject android_emoji_EmojiFactory_newAvailableInstance( + JNIEnv* env, jobject clazz) { + // pthread_once(&g_once, InitializeCaller); + + // EmojiFactory *factory = gCaller->TryCallGetAvailableImplementation(); + EmojiFactory *factory = EmojiFactory::GetAvailableImplementation(); + if (NULL == factory) { + return NULL; + } + String16 name_16(String8(factory->Name())); + jstring jname = env->NewString(name_16.string(), name_16.size()); + if (NULL == jname) { + return NULL; + } + + return create_java_EmojiFactory(env, factory, jname); +} + +static jobject android_emoji_EmojiFactory_getBitmapFromAndroidPua( + JNIEnv* env, jobject clazz, jint nativeEmojiFactory, jint pua) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + + int size; + const char *bytes = factory->GetImageBinaryFromAndroidPua(pua, &size); + if (bytes == NULL) { + return NULL; + } + + SkBitmap *bitmap = new SkBitmap; + if (!SkImageDecoder::DecodeMemory(bytes, size, bitmap)) { + LOGE("SkImageDecoder::DecodeMemory() failed."); + return NULL; + } + + jobject obj = env->AllocObject(gBitmap_class); + if (obj) { + env->CallVoidMethod(obj, gBitmap_constructorMethodID, + reinterpret_cast(bitmap), false, NULL); + if (env->ExceptionCheck() != 0) { + LOGE("*** Uncaught exception returned from Java call!\n"); + env->ExceptionDescribe(); + return NULL; + } + } + + return obj; +} + +static void android_emoji_EmojiFactory_destructor( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + delete factory; +} + +static jint android_emoji_EmojiFactory_getAndroidPuaFromVendorSpecificSjis( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jchar sjis) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetAndroidPuaFromVendorSpecificSjis(sjis); +} + +static jint android_emoji_EmojiFactory_getVendorSpecificSjisFromAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jint pua) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetVendorSpecificSjisFromAndroidPua(pua); +} + +static jint android_emoji_EmojiFactory_getAndroidPuaFromVendorSpecificPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jint vsu) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetAndroidPuaFromVendorSpecificPua(vsu); +} + +static jint android_emoji_EmojiFactory_getVendorSpecificPuaFromAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jint pua) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetVendorSpecificPuaFromAndroidPua(pua); +} + +static jint android_emoji_EmojiFactory_getMaximumVendorSpecificPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetMaximumVendorSpecificPua(); +} + +static jint android_emoji_EmojiFactory_getMinimumVendorSpecificPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetMinimumVendorSpecificPua(); +} + +static jint android_emoji_EmojiFactory_getMaximumAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetMaximumAndroidPua(); +} + +static jint android_emoji_EmojiFactory_getMinimumAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast(nativeEmojiFactory); + return factory->GetMinimumAndroidPua(); +} + +static JNINativeMethod gMethods[] = { + { "newInstance", "(Ljava/lang/String;)Landroid/emoji/EmojiFactory;", + (void*)android_emoji_EmojiFactory_newInstance}, + { "newAvailableInstance", "()Landroid/emoji/EmojiFactory;", + (void*)android_emoji_EmojiFactory_newAvailableInstance}, + { "nativeDestructor", "(I)V", + (void*)android_emoji_EmojiFactory_destructor}, + { "nativeGetBitmapFromAndroidPua", "(II)Landroid/graphics/Bitmap;", + (void*)android_emoji_EmojiFactory_getBitmapFromAndroidPua}, + { "nativeGetAndroidPuaFromVendorSpecificSjis", "(IC)I", + (void*)android_emoji_EmojiFactory_getAndroidPuaFromVendorSpecificSjis}, + { "nativeGetVendorSpecificSjisFromAndroidPua", "(II)I", + (void*)android_emoji_EmojiFactory_getVendorSpecificSjisFromAndroidPua}, + { "nativeGetAndroidPuaFromVendorSpecificPua", "(II)I", + (void*)android_emoji_EmojiFactory_getAndroidPuaFromVendorSpecificPua}, + { "nativeGetVendorSpecificPuaFromAndroidPua", "(II)I", + (void*)android_emoji_EmojiFactory_getVendorSpecificPuaFromAndroidPua}, + { "nativeGetMaximumVendorSpecificPua", "(I)I", + (void*)android_emoji_EmojiFactory_getMaximumVendorSpecificPua}, + { "nativeGetMinimumVendorSpecificPua", "(I)I", + (void*)android_emoji_EmojiFactory_getMinimumVendorSpecificPua}, + { "nativeGetMaximumAndroidPua", "(I)I", + (void*)android_emoji_EmojiFactory_getMaximumAndroidPua}, + { "nativeGetMinimumAndroidPua", "(I)I", + (void*)android_emoji_EmojiFactory_getMinimumAndroidPua} +}; + +static jclass make_globalref(JNIEnv* env, const char classname[]) +{ + jclass c = env->FindClass(classname); + SkASSERT(c); + return (jclass)env->NewGlobalRef(c); +} + +static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz, + const char fieldname[], const char type[]) +{ + jfieldID id = env->GetFieldID(clazz, fieldname, type); + SkASSERT(id); + return id; +} + +int register_android_emoji_EmojiFactory(JNIEnv* env) { + gBitmap_class = make_globalref(env, "android/graphics/Bitmap"); + gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "", + "(IZ[B)V"); + gEmojiFactory_class = make_globalref(env, "android/emoji/EmojiFactory"); + gEmojiFactory_constructorMethodID = env->GetMethodID( + gEmojiFactory_class, "", "(ILjava/lang/String;)V"); + return jniRegisterNativeMethods(env, "android/emoji/EmojiFactory", + gMethods, NELEM(gMethods)); +} + +} // namespace android diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp index c98207a58b57f..fcab8130d4b3d 100644 --- a/core/jni/android_net_wifi_Wifi.cpp +++ b/core/jni/android_net_wifi_Wifi.cpp @@ -397,6 +397,16 @@ static jboolean android_net_wifi_setBluetoothCoexistenceModeCommand(JNIEnv* env, return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK"); } +static jboolean android_net_wifi_setBluetoothCoexistenceScanModeCommand(JNIEnv* env, jobject clazz, jboolean setCoexScanMode) +{ + char cmdstr[256]; + + int numWritten = snprintf(cmdstr, sizeof(cmdstr), "DRIVER BTCOEXSCAN-%s", setCoexScanMode ? "START" : "STOP"); + int cmdTooLong = numWritten >= (int)sizeof(cmdstr); + + return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK"); +} + static jboolean android_net_wifi_saveConfigCommand(JNIEnv* env, jobject clazz) { // Make sure we never write out a value for AP_SCAN other than 1 @@ -503,6 +513,8 @@ static JNINativeMethod gWifiMethods[] = { { "getNumAllowedChannelsCommand", "()I", (void*) android_net_wifi_getNumAllowedChannelsCommand }, { "setBluetoothCoexistenceModeCommand", "(I)Z", (void*) android_net_wifi_setBluetoothCoexistenceModeCommand }, + { "setBluetoothCoexistenceScanModeCommand", "(Z)Z", + (void*) android_net_wifi_setBluetoothCoexistenceScanModeCommand }, { "getRssiCommand", "()I", (void*) android_net_wifi_getRssiCommand }, { "getLinkSpeedCommand", "()I", (void*) android_net_wifi_getLinkSpeedCommand }, { "getMacAddressCommand", "()Ljava/lang/String;", (void*) android_net_wifi_getMacAddressCommand }, diff --git a/core/res/Android.mk b/core/res/Android.mk index 5fca5d011fc1b..cb5524a6dc30b 100644 --- a/core/res/Android.mk +++ b/core/res/Android.mk @@ -24,7 +24,7 @@ LOCAL_CERTIFICATE := platform # since these resources will be used by many apps. LOCAL_AAPT_FLAGS := -x -LOCAL_MODULE_TAGS := user development +LOCAL_MODULE_TAGS := user # Install this alongside the libraries. LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES) diff --git a/core/res/res/anim/zoom_ring_enter.xml b/core/res/res/anim/zoom_ring_enter.xml deleted file mode 100644 index 13d89b2f229b2..0000000000000 --- a/core/res/res/anim/zoom_ring_enter.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/core/res/res/anim/zoom_ring_exit.xml b/core/res/res/anim/zoom_ring_exit.xml deleted file mode 100644 index 177a4c31697e3..0000000000000 --- a/core/res/res/anim/zoom_ring_exit.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - diff --git a/core/res/res/drawable-land/statusbar_background.png b/core/res/res/drawable-land/statusbar_background.png index 8ecc24c59add0..bafa5c5a5196b 100644 Binary files a/core/res/res/drawable-land/statusbar_background.png and b/core/res/res/drawable-land/statusbar_background.png differ diff --git a/core/res/res/drawable/btn_circle.xml b/core/res/res/drawable/btn_circle.xml new file mode 100644 index 0000000000000..920801033c1fc --- /dev/null +++ b/core/res/res/drawable/btn_circle.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/core/res/res/drawable/btn_circle_disable.png b/core/res/res/drawable/btn_circle_disable.png new file mode 100644 index 0000000000000..33b74a66c07f3 Binary files /dev/null and b/core/res/res/drawable/btn_circle_disable.png differ diff --git a/core/res/res/drawable/btn_circle_disable_focused.png b/core/res/res/drawable/btn_circle_disable_focused.png new file mode 100644 index 0000000000000..005ad8dcaeaeb Binary files /dev/null and b/core/res/res/drawable/btn_circle_disable_focused.png differ diff --git a/core/res/res/drawable/btn_circle_longpress.png b/core/res/res/drawable/btn_circle_longpress.png new file mode 100644 index 0000000000000..f27d4119b42fd Binary files /dev/null and b/core/res/res/drawable/btn_circle_longpress.png differ diff --git a/core/res/res/drawable/btn_circle_normal.png b/core/res/res/drawable/btn_circle_normal.png new file mode 100644 index 0000000000000..fc5af1c9fc508 Binary files /dev/null and b/core/res/res/drawable/btn_circle_normal.png differ diff --git a/core/res/res/drawable/btn_circle_pressed.png b/core/res/res/drawable/btn_circle_pressed.png new file mode 100644 index 0000000000000..8f40afdfcb30c Binary files /dev/null and b/core/res/res/drawable/btn_circle_pressed.png differ diff --git a/core/res/res/drawable/btn_circle_selected.png b/core/res/res/drawable/btn_circle_selected.png new file mode 100644 index 0000000000000..c74fac227a238 Binary files /dev/null and b/core/res/res/drawable/btn_circle_selected.png differ diff --git a/core/res/res/drawable/extract_edit_text.xml b/core/res/res/drawable/extract_edit_text.xml index 9c6c4ba860cdf..c7f66f6b199fe 100644 --- a/core/res/res/drawable/extract_edit_text.xml +++ b/core/res/res/drawable/extract_edit_text.xml @@ -15,7 +15,6 @@ --> - diff --git a/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml b/core/res/res/drawable/ic_btn_round_more.xml similarity index 71% rename from core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml rename to core/res/res/drawable/ic_btn_round_more.xml index f2d495b8d4482..82b759337e8f8 100644 --- a/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml +++ b/core/res/res/drawable/ic_btn_round_more.xml @@ -14,9 +14,9 @@ limitations under the License. --> - + + + + diff --git a/core/res/res/drawable/ic_btn_round_more_disabled.png b/core/res/res/drawable/ic_btn_round_more_disabled.png new file mode 100644 index 0000000000000..1ab98c985e7ed Binary files /dev/null and b/core/res/res/drawable/ic_btn_round_more_disabled.png differ diff --git a/core/res/res/drawable/ic_btn_round_more_normal.png b/core/res/res/drawable/ic_btn_round_more_normal.png new file mode 100644 index 0000000000000..ebdc55c272de3 Binary files /dev/null and b/core/res/res/drawable/ic_btn_round_more_normal.png differ diff --git a/core/res/res/drawable/keyboard_textfield_pressed.9.png b/core/res/res/drawable/keyboard_textfield_pressed.9.png deleted file mode 100644 index f4e3f10fa8f84..0000000000000 Binary files a/core/res/res/drawable/keyboard_textfield_pressed.9.png and /dev/null differ diff --git a/core/res/res/drawable/stat_sys_phone_call.png b/core/res/res/drawable/stat_sys_phone_call.png index ad5369399221d..c44d0620f7f40 100644 Binary files a/core/res/res/drawable/stat_sys_phone_call.png and b/core/res/res/drawable/stat_sys_phone_call.png differ diff --git a/core/res/res/drawable/stat_sys_phone_call_bluetooth.png b/core/res/res/drawable/stat_sys_phone_call_bluetooth.png new file mode 100644 index 0000000000000..7abfd194fb800 Binary files /dev/null and b/core/res/res/drawable/stat_sys_phone_call_bluetooth.png differ diff --git a/core/res/res/drawable/statusbar_background.png b/core/res/res/drawable/statusbar_background.png index 945ad92ad209b..25a2344cd65da 100644 Binary files a/core/res/res/drawable/statusbar_background.png and b/core/res/res/drawable/statusbar_background.png differ diff --git a/core/res/res/drawable/zoom_ring_arrows.png b/core/res/res/drawable/zoom_ring_arrows.png deleted file mode 100644 index e443de7bfe0b5..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_arrows.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_overview_tab.9.png b/core/res/res/drawable/zoom_ring_overview_tab.9.png deleted file mode 100644 index d2658d89cb286..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_overview_tab.9.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_thumb.png b/core/res/res/drawable/zoom_ring_thumb.png deleted file mode 100644 index 1002724238c2b..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_thumb.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_thumb_minus.png b/core/res/res/drawable/zoom_ring_thumb_minus.png deleted file mode 100644 index faed674385486..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_thumb_minus.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png b/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png deleted file mode 100644 index abe1b8a5de5a6..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_thumb_plus.png b/core/res/res/drawable/zoom_ring_thumb_plus.png deleted file mode 100644 index ba7aff2318347..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_thumb_plus.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png b/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png deleted file mode 100644 index d01ccb4342ab3..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml b/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml deleted file mode 100644 index b77eaa4090c18..0000000000000 --- a/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - diff --git a/core/res/res/drawable/zoom_ring_track.png b/core/res/res/drawable/zoom_ring_track.png deleted file mode 100644 index 1d5a44cd8bcc6..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_track.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_track_absolute.png b/core/res/res/drawable/zoom_ring_track_absolute.png deleted file mode 100644 index 2b876995655e4..0000000000000 Binary files a/core/res/res/drawable/zoom_ring_track_absolute.png and /dev/null differ diff --git a/core/res/res/drawable/zoom_ring_trail.xml b/core/res/res/drawable/zoom_ring_trail.xml deleted file mode 100644 index 08931ac5aafc1..0000000000000 --- a/core/res/res/drawable/zoom_ring_trail.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - diff --git a/core/res/res/layout/preferences.xml b/core/res/res/layout/preferences.xml index e6876ff862ef2..f0c253543c175 100644 --- a/core/res/res/layout/preferences.xml +++ b/core/res/res/layout/preferences.xml @@ -19,7 +19,8 @@ + android:background="@drawable/btn_circle" + android:src="@drawable/ic_btn_round_more" /> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 18a13554dc74d..81fd87d0d4713 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -65,13 +65,6 @@ @drawable/spinner_dropdown_background @drawable/title_bar @drawable/title_bar_shadow - @drawable/zoom_ring_arrows - @drawable/zoom_ring_overview_tab - @drawable/zoom_ring_thumb - @drawable/zoom_ring_thumb_minus_arrow_rotatable - @drawable/zoom_ring_thumb_plus_arrow_rotatable - @drawable/zoom_ring_track - @drawable/zoom_ring_track_absolute @drawable/indicator_code_lock_drag_direction_green_up @drawable/indicator_code_lock_drag_direction_red_up diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 30b26fa09c3a4..5fa5f8d8b4095 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -371,8 +371,6 @@ - - @@ -585,29 +583,34 @@ - + + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 9f4d82e22372c..54eba629a58a5 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -144,11 +144,6 @@ @anim/search_bar_exit - - - - diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index bde6b2acde55c..01c46de1e5dc2 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -162,7 +162,6 @@ @android:style/Widget.TextView.SpinnerItem @android:style/TextAppearance.Widget.DropDownHint @android:style/Widget.KeyboardView - @android:style/Widget.ZoomRing @android:style/Preference.PreferenceScreen diff --git a/data/etc/Android.mk b/data/etc/Android.mk index 4b5464f5e26dc..a32d8ea8db36b 100644 --- a/data/etc/Android.mk +++ b/data/etc/Android.mk @@ -21,7 +21,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := platform.xml -LOCAL_MODULE_TAGS := user development +LOCAL_MODULE_TAGS := user LOCAL_MODULE_CLASS := ETC diff --git a/data/sounds/Android.mk b/data/sounds/Android.mk deleted file mode 100644 index 9e3697f258483..0000000000000 --- a/data/sounds/Android.mk +++ /dev/null @@ -1,240 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# don't understand what's wrong here... bs -# -#copy_from := $(wildcard $(LOCAL_PATH)/*.mp3) -#copy_to := $(addprefix $(TARGET_OUT)/sounds/,$(patsubst $(LOCAL_PATH)/%,%,$(copy_from))) -# -#$(copy_to) : PRIVATE_MODULE := sounds -#$(copy_to) : $(TARGET_OUT)/sounds/% : $(LOCAL_PATH)/% | $(ACP) -# $(transform-prebuilt-to-target) -# -#ALL_PREBUILT += $(copy_to) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/F1_MissedCall.ogg -$(TARGET_OUT)/media/audio/notifications/F1_MissedCall.ogg : $(LOCAL_PATH)/F1_MissedCall.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/F1_New_MMS.ogg -$(TARGET_OUT)/media/audio/notifications/F1_New_MMS.ogg : $(LOCAL_PATH)/F1_New_MMS.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/F1_New_SMS.ogg -$(TARGET_OUT)/media/audio/notifications/F1_New_SMS.ogg : $(LOCAL_PATH)/F1_New_SMS.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/alarms/Alarm_Buzzer.ogg -$(TARGET_OUT)/media/audio/alarms/Alarm_Buzzer.ogg : $(LOCAL_PATH)/Alarm_Buzzer.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/alarms/Alarm_Beep_01.ogg -$(TARGET_OUT)/media/audio/alarms/Alarm_Beep_01.ogg : $(LOCAL_PATH)/Alarm_Beep_01.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/alarms/Alarm_Beep_02.ogg -$(TARGET_OUT)/media/audio/alarms/Alarm_Beep_02.ogg : $(LOCAL_PATH)/Alarm_Beep_02.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/alarms/Alarm_Classic.ogg -$(TARGET_OUT)/media/audio/alarms/Alarm_Classic.ogg : $(LOCAL_PATH)/Alarm_Classic.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/alarms/Alarm_Beep_03.ogg -$(TARGET_OUT)/media/audio/alarms/Alarm_Beep_03.ogg : $(LOCAL_PATH)/Alarm_Beep_03.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/alarms/Alarm_Rooster_02.ogg -$(TARGET_OUT)/media/audio/alarms/Alarm_Rooster_02.ogg : $(LOCAL_PATH)/Alarm_Rooster_02.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Ring_Classic_02.ogg -$(TARGET_OUT)/media/audio/ringtones/Ring_Classic_02.ogg : $(LOCAL_PATH)/Ring_Classic_02.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Ring_Digital_02.ogg -$(TARGET_OUT)/media/audio/ringtones/Ring_Digital_02.ogg : $(LOCAL_PATH)/Ring_Digital_02.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Ring_Synth_04.ogg -$(TARGET_OUT)/media/audio/ringtones/Ring_Synth_04.ogg : $(LOCAL_PATH)/Ring_Synth_04.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Ring_Synth_02.ogg -$(TARGET_OUT)/media/audio/ringtones/Ring_Synth_02.ogg : $(LOCAL_PATH)/Ring_Synth_02.ogg | $(ACP) - $(transform-prebuilt-to-target) - -# -# --- New Wave Labs ringtones -# -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/BeatPlucker.ogg -$(TARGET_OUT)/media/audio/ringtones/BeatPlucker.ogg : $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/BentleyDubs.ogg -$(TARGET_OUT)/media/audio/ringtones/BentleyDubs.ogg : $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/BirdLoop.ogg -$(TARGET_OUT)/media/audio/ringtones/BirdLoop.ogg : $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/CaribbeanIce.ogg -$(TARGET_OUT)/media/audio/ringtones/CaribbeanIce.ogg : $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/CurveBall.ogg -$(TARGET_OUT)/media/audio/ringtones/CurveBall.ogg : $(LOCAL_PATH)/newwavelabs/CurveBall.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/EtherShake.ogg -$(TARGET_OUT)/media/audio/ringtones/EtherShake.ogg : $(LOCAL_PATH)/newwavelabs/EtherShake.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/FriendlyGhost.ogg -$(TARGET_OUT)/media/audio/ringtones/FriendlyGhost.ogg : $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/GameOverGuitar.ogg -$(TARGET_OUT)/media/audio/ringtones/GameOverGuitar.ogg : $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Growl.ogg -$(TARGET_OUT)/media/audio/ringtones/Growl.ogg : $(LOCAL_PATH)/newwavelabs/Growl.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/InsertCoin.ogg -$(TARGET_OUT)/media/audio/ringtones/InsertCoin.ogg : $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/LoopyLounge.ogg -$(TARGET_OUT)/media/audio/ringtones/LoopyLounge.ogg : $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/LoveFlute.ogg -$(TARGET_OUT)/media/audio/ringtones/LoveFlute.ogg : $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/MidEvilJaunt.ogg -$(TARGET_OUT)/media/audio/ringtones/MidEvilJaunt.ogg : $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/MildlyAlarming.ogg -$(TARGET_OUT)/media/audio/ringtones/MildlyAlarming.ogg : $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/NewPlayer.ogg -$(TARGET_OUT)/media/audio/ringtones/NewPlayer.ogg : $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Noises1.ogg -$(TARGET_OUT)/media/audio/ringtones/Noises1.ogg : $(LOCAL_PATH)/newwavelabs/Noises1.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Noises2.ogg -$(TARGET_OUT)/media/audio/ringtones/Noises2.ogg : $(LOCAL_PATH)/newwavelabs/Noises2.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Noises3.ogg -$(TARGET_OUT)/media/audio/ringtones/Noises3.ogg : $(LOCAL_PATH)/newwavelabs/Noises3.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/OrganDub.ogg -$(TARGET_OUT)/media/audio/ringtones/OrganDub.ogg : $(LOCAL_PATH)/newwavelabs/OrganDub.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/RomancingTheTone.ogg -$(TARGET_OUT)/media/audio/ringtones/RomancingTheTone.ogg : $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/SitarVsSitar.ogg -$(TARGET_OUT)/media/audio/ringtones/SitarVsSitar.ogg : $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/SpringyJalopy.ogg -$(TARGET_OUT)/media/audio/ringtones/SpringyJalopy.ogg : $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/Terminated.ogg -$(TARGET_OUT)/media/audio/ringtones/Terminated.ogg : $(LOCAL_PATH)/newwavelabs/Terminated.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/TwirlAway.ogg -$(TARGET_OUT)/media/audio/ringtones/TwirlAway.ogg : $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/VeryAlarmed.ogg -$(TARGET_OUT)/media/audio/ringtones/VeryAlarmed.ogg : $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ringtones/World.ogg -$(TARGET_OUT)/media/audio/ringtones/World.ogg : $(LOCAL_PATH)/newwavelabs/World.ogg | $(ACP) - $(transform-prebuilt-to-target) -# -# --- New Wave Labs notifications -# -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/CaffeineSnake.ogg -$(TARGET_OUT)/media/audio/notifications/CaffeineSnake.ogg : $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/DearDeer.ogg -$(TARGET_OUT)/media/audio/notifications/DearDeer.ogg : $(LOCAL_PATH)/newwavelabs/DearDeer.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/DontPanic.ogg -$(TARGET_OUT)/media/audio/notifications/DontPanic.ogg : $(LOCAL_PATH)/newwavelabs/DontPanic.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/Highwire.ogg -$(TARGET_OUT)/media/audio/notifications/Highwire.ogg : $(LOCAL_PATH)/newwavelabs/Highwire.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/KzurbSonar.ogg -$(TARGET_OUT)/media/audio/notifications/KzurbSonar.ogg : $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/OnTheHunt.ogg -$(TARGET_OUT)/media/audio/notifications/OnTheHunt.ogg : $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/Voila.ogg -$(TARGET_OUT)/media/audio/notifications/Voila.ogg : $(LOCAL_PATH)/newwavelabs/Voila.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/Beat_Box_Android.ogg -$(TARGET_OUT)/media/audio/notifications/Beat_Box_Android.ogg : $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/Heaven.ogg -$(TARGET_OUT)/media/audio/notifications/Heaven.ogg : $(LOCAL_PATH)/notifications/Heaven.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/TaDa.ogg -$(TARGET_OUT)/media/audio/notifications/TaDa.ogg : $(LOCAL_PATH)/notifications/TaDa.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/notifications/Tinkerbell.ogg -$(TARGET_OUT)/media/audio/notifications/Tinkerbell.ogg : $(LOCAL_PATH)/notifications/Tinkerbell.ogg | $(ACP) - $(transform-prebuilt-to-target) - -# UI effects -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ui/Effect_Tick.ogg -$(TARGET_OUT)/media/audio/ui/Effect_Tick.ogg : $(LOCAL_PATH)/effects/Effect_Tick.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ui/KeypressStandard.ogg -$(TARGET_OUT)/media/audio/ui/KeypressStandard.ogg : $(LOCAL_PATH)/effects/KeypressStandard.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ui/KeypressSpacebar.ogg -$(TARGET_OUT)/media/audio/ui/KeypressSpacebar.ogg : $(LOCAL_PATH)/effects/KeypressSpacebar.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ui/KeypressDelete.ogg -$(TARGET_OUT)/media/audio/ui/KeypressDelete.ogg : $(LOCAL_PATH)/effects/KeypressDelete.ogg | $(ACP) - $(transform-prebuilt-to-target) - -ALL_PREBUILT += $(TARGET_OUT)/media/audio/ui/KeypressReturn.ogg -$(TARGET_OUT)/media/audio/ui/KeypressReturn.ogg : $(LOCAL_PATH)/effects/KeypressReturn.ogg | $(ACP) - $(transform-prebuilt-to-target) - diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk new file mode 100644 index 0000000000000..87229838253a6 --- /dev/null +++ b/data/sounds/OriginalAudio.mk @@ -0,0 +1,68 @@ +# +# Original audio package that shipped on G1 +# +# This file is included from core.mk so that all devices will have these sounds +# +# TODO: Clean up for future releases +# + +LOCAL_PATH:= frameworks/base/data/sounds + +PRODUCT_COPY_FILES += \ + $(LOCAL_PATH)/F1_MissedCall.ogg:system/media/audio/notifications/F1_MissedCall.ogg \ + $(LOCAL_PATH)/F1_New_MMS.ogg:system/media/audio/notifications/F1_New_MMS.ogg \ + $(LOCAL_PATH)/F1_New_SMS.ogg:system/media/audio/notifications/F1_New_SMS.ogg \ + $(LOCAL_PATH)/Alarm_Buzzer.ogg:system/media/audio/alarms/Alarm_Buzzer.ogg \ + $(LOCAL_PATH)/Alarm_Beep_01.ogg:system/media/audio/alarms/Alarm_Beep_01.ogg \ + $(LOCAL_PATH)/Alarm_Beep_02.ogg:system/media/audio/alarms/Alarm_Beep_02.ogg \ + $(LOCAL_PATH)/Alarm_Classic.ogg:system/media/audio/alarms/Alarm_Classic.ogg \ + $(LOCAL_PATH)/Alarm_Beep_03.ogg:system/media/audio/alarms/Alarm_Beep_03.ogg \ + $(LOCAL_PATH)/Alarm_Rooster_02.ogg:system/media/audio/alarms/Alarm_Rooster_02.ogg \ + $(LOCAL_PATH)/Ring_Classic_02.ogg:system/media/audio/ringtones/Ring_Classic_02.ogg \ + $(LOCAL_PATH)/Ring_Digital_02.ogg:system/media/audio/ringtones/Ring_Digital_02.ogg \ + $(LOCAL_PATH)/Ring_Synth_04.ogg:system/media/audio/ringtones/Ring_Synth_04.ogg \ + $(LOCAL_PATH)/Ring_Synth_02.ogg:system/media/audio/ringtones/Ring_Synth_02.ogg \ + $(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \ + $(LOCAL_PATH)/newwavelabs/BentleyDubs.ogg:system/media/audio/ringtones/BentleyDubs.ogg \ + $(LOCAL_PATH)/newwavelabs/BirdLoop.ogg:system/media/audio/ringtones/BirdLoop.ogg \ + $(LOCAL_PATH)/newwavelabs/CaribbeanIce.ogg:system/media/audio/ringtones/CaribbeanIce.ogg \ + $(LOCAL_PATH)/newwavelabs/CurveBall.ogg:system/media/audio/ringtones/CurveBall.ogg \ + $(LOCAL_PATH)/newwavelabs/EtherShake.ogg:system/media/audio/ringtones/EtherShake.ogg \ + $(LOCAL_PATH)/newwavelabs/FriendlyGhost.ogg:system/media/audio/ringtones/FriendlyGhost.ogg \ + $(LOCAL_PATH)/newwavelabs/GameOverGuitar.ogg:system/media/audio/ringtones/GameOverGuitar.ogg \ + $(LOCAL_PATH)/newwavelabs/Growl.ogg:system/media/audio/ringtones/Growl.ogg \ + $(LOCAL_PATH)/newwavelabs/InsertCoin.ogg:system/media/audio/ringtones/InsertCoin.ogg \ + $(LOCAL_PATH)/newwavelabs/LoopyLounge.ogg:system/media/audio/ringtones/LoopyLounge.ogg \ + $(LOCAL_PATH)/newwavelabs/LoveFlute.ogg:system/media/audio/ringtones/LoveFlute.ogg \ + $(LOCAL_PATH)/newwavelabs/MidEvilJaunt.ogg:system/media/audio/ringtones/MidEvilJaunt.ogg \ + $(LOCAL_PATH)/newwavelabs/MildlyAlarming.ogg:system/media/audio/ringtones/MildlyAlarming.ogg \ + $(LOCAL_PATH)/newwavelabs/NewPlayer.ogg:system/media/audio/ringtones/NewPlayer.ogg \ + $(LOCAL_PATH)/newwavelabs/Noises1.ogg:system/media/audio/ringtones/Noises1.ogg \ + $(LOCAL_PATH)/newwavelabs/Noises2.ogg:system/media/audio/ringtones/Noises2.ogg \ + $(LOCAL_PATH)/newwavelabs/Noises3.ogg:system/media/audio/ringtones/Noises3.ogg \ + $(LOCAL_PATH)/newwavelabs/OrganDub.ogg:system/media/audio/ringtones/OrganDub.ogg \ + $(LOCAL_PATH)/newwavelabs/RomancingTheTone.ogg:system/media/audio/ringtones/RomancingTheTone.ogg \ + $(LOCAL_PATH)/newwavelabs/SitarVsSitar.ogg:system/media/audio/ringtones/SitarVsSitar.ogg \ + $(LOCAL_PATH)/newwavelabs/SpringyJalopy.ogg:system/media/audio/ringtones/SpringyJalopy.ogg \ + $(LOCAL_PATH)/newwavelabs/Terminated.ogg:system/media/audio/ringtones/Terminated.ogg \ + $(LOCAL_PATH)/newwavelabs/TwirlAway.ogg:system/media/audio/ringtones/TwirlAway.ogg \ + $(LOCAL_PATH)/newwavelabs/VeryAlarmed.ogg:system/media/audio/ringtones/VeryAlarmed.ogg \ + $(LOCAL_PATH)/newwavelabs/World.ogg:system/media/audio/ringtones/World.ogg \ + $(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg \ + $(LOCAL_PATH)/newwavelabs/DearDeer.ogg:system/media/audio/notifications/DearDeer.ogg \ + $(LOCAL_PATH)/newwavelabs/DontPanic.ogg:system/media/audio/notifications/DontPanic.ogg \ + $(LOCAL_PATH)/newwavelabs/Highwire.ogg:system/media/audio/notifications/Highwire.ogg \ + $(LOCAL_PATH)/newwavelabs/KzurbSonar.ogg:system/media/audio/notifications/KzurbSonar.ogg \ + $(LOCAL_PATH)/newwavelabs/OnTheHunt.ogg:system/media/audio/notifications/OnTheHunt.ogg \ + $(LOCAL_PATH)/newwavelabs/Voila.ogg:system/media/audio/notifications/Voila.ogg \ + $(LOCAL_PATH)/notifications/Beat_Box_Android.ogg:system/media/audio/notifications/Beat_Box_Android.ogg \ + $(LOCAL_PATH)/notifications/Heaven.ogg:system/media/audio/notifications/Heaven.ogg \ + $(LOCAL_PATH)/notifications/TaDa.ogg:system/media/audio/notifications/TaDa.ogg \ + $(LOCAL_PATH)/notifications/Tinkerbell.ogg:system/media/audio/notifications/Tinkerbell.ogg \ + $(LOCAL_PATH)/effects/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \ + $(LOCAL_PATH)/effects/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/newwavelabs/CrazyDream.ogg:system/media/audio/ringtones/CrazyDream.ogg \ + $(LOCAL_PATH)/newwavelabs/DreamTheme.ogg:system/media/audio/ringtones/DreamTheme.ogg diff --git a/data/sounds/effects/KeypressDelete.ogg b/data/sounds/effects/KeypressDelete.ogg index b35ea65b17a7b..738f6aea72f71 100644 Binary files a/data/sounds/effects/KeypressDelete.ogg and b/data/sounds/effects/KeypressDelete.ogg differ diff --git a/data/sounds/effects/KeypressDelete.wav b/data/sounds/effects/KeypressDelete.wav index 5903f7f165d31..0a2f3cc5ec0bb 100644 Binary files a/data/sounds/effects/KeypressDelete.wav and b/data/sounds/effects/KeypressDelete.wav differ diff --git a/data/sounds/effects/KeypressReturn.ogg b/data/sounds/effects/KeypressReturn.ogg index 1b0fd566cce00..6c807b0114ffe 100644 Binary files a/data/sounds/effects/KeypressReturn.ogg and b/data/sounds/effects/KeypressReturn.ogg differ diff --git a/data/sounds/effects/KeypressReturn.wav b/data/sounds/effects/KeypressReturn.wav index 9558fcf0c4ac3..59ff13af4df4e 100644 Binary files a/data/sounds/effects/KeypressReturn.wav and b/data/sounds/effects/KeypressReturn.wav differ diff --git a/data/sounds/effects/KeypressSpacebar.ogg b/data/sounds/effects/KeypressSpacebar.ogg index 5299337f4d91c..b59c8ce4904da 100644 Binary files a/data/sounds/effects/KeypressSpacebar.ogg and b/data/sounds/effects/KeypressSpacebar.ogg differ diff --git a/data/sounds/effects/KeypressSpacebar.wav b/data/sounds/effects/KeypressSpacebar.wav index 1226a217e0d2b..1f819c9dbbdb7 100644 Binary files a/data/sounds/effects/KeypressSpacebar.wav and b/data/sounds/effects/KeypressSpacebar.wav differ diff --git a/data/sounds/effects/KeypressStandard.ogg b/data/sounds/effects/KeypressStandard.ogg index 8a152d11fc3de..a465cf8f9aa73 100644 Binary files a/data/sounds/effects/KeypressStandard.ogg and b/data/sounds/effects/KeypressStandard.ogg differ diff --git a/data/sounds/effects/KeypressStandard.wav b/data/sounds/effects/KeypressStandard.wav index 0c9aa2a383977..56a791155a396 100644 Binary files a/data/sounds/effects/KeypressStandard.wav and b/data/sounds/effects/KeypressStandard.wav differ diff --git a/docs/html/guide/topics/graphics/2d-graphics.jd b/docs/html/guide/topics/graphics/2d-graphics.jd index a72962e8c4327..befb01863d799 100644 --- a/docs/html/guide/topics/graphics/2d-graphics.jd +++ b/docs/html/guide/topics/graphics/2d-graphics.jd @@ -447,7 +447,7 @@ Here's an example XML file for a frame-by-frame animation:

This animation runs for just three frames. By setting the android:oneshot attribute of the list to true, it will cycle just once then stop and hold on the last frame. If it is set false then -the animation will loop. With this XML saved as rocket_thrust.xml

in the res/anim/ directory +the animation will loop. With this XML saved as rocket_thrust.xml in the res/anim/ directory of the project, it can be added as the background image to a View and then called to play. Here's an example Activity, in which the animation is added to an {@link android.widget.ImageView} and then animated when the screen is touched:

diff --git a/libs/audioflinger/A2dpAudioInterface.cpp b/libs/audioflinger/A2dpAudioInterface.cpp
index eb00f8cc9a6c9..2974e32ee179e 100644
--- a/libs/audioflinger/A2dpAudioInterface.cpp
+++ b/libs/audioflinger/A2dpAudioInterface.cpp
@@ -48,7 +48,6 @@ AudioStreamOut* A2dpAudioInterface::openOutputStream(
         int format, int channelCount, uint32_t sampleRate, status_t *status)
 {
     LOGD("A2dpAudioInterface::openOutputStream %d, %d, %d\n", format, channelCount, sampleRate);
-    Mutex::Autolock lock(mLock);
     status_t err = 0;
 
     // only one output stream allowed
@@ -134,7 +133,8 @@ A2dpAudioInterface::A2dpAudioStreamOut::A2dpAudioStreamOut() :
     mFd(-1), mStandby(true), mStartCount(0), mRetryCount(0), mData(NULL)
 {
     // use any address by default
-    strncpy(mA2dpAddress, "00:00:00:00:00:00", sizeof(mA2dpAddress));
+    strcpy(mA2dpAddress, "00:00:00:00:00:00");
+    init();
 }
 
 status_t A2dpAudioInterface::A2dpAudioStreamOut::set(
@@ -163,18 +163,12 @@ A2dpAudioInterface::A2dpAudioStreamOut::~A2dpAudioStreamOut()
 
 ssize_t A2dpAudioInterface::A2dpAudioStreamOut::write(const void* buffer, size_t bytes)
 {    
-    status_t status = NO_INIT;
-    size_t remaining = bytes;
+    Mutex::Autolock lock(mLock);
 
-    if (!mData) {
-        status = a2dp_init(44100, 2, &mData);
-        if (status < 0) {
-            LOGE("a2dp_init failed err: %d\n", status);
-            mData = NULL;
-            goto Error;
-        }
-        a2dp_set_sink(mData, mA2dpAddress);
-    }
+    size_t remaining = bytes;
+    status_t status = init();
+    if (status < 0)
+        goto Error;
     
     while (remaining > 0) {
         status = a2dp_write(mData, buffer, remaining);
@@ -197,10 +191,27 @@ Error:
     return status;
 }
 
+status_t A2dpAudioInterface::A2dpAudioStreamOut::init()
+{
+    if (!mData) {
+        status_t status = a2dp_init(44100, 2, &mData);
+        if (status < 0) {
+            LOGE("a2dp_init failed err: %d\n", status);
+            mData = NULL;
+            return status;
+        }
+        a2dp_set_sink(mData, mA2dpAddress);
+    }
+
+    return 0;
+}
+
 status_t A2dpAudioInterface::A2dpAudioStreamOut::standby()
 {
     int result = 0;
 
+    Mutex::Autolock lock(mLock);
+
     if (!mStandby) {
         result = a2dp_stop(mData);
         if (result == 0)
@@ -212,15 +223,15 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::standby()
 
 status_t A2dpAudioInterface::A2dpAudioStreamOut::setAddress(const char* address)
 {
-    if (strlen(address) < sizeof(mA2dpAddress))
+    Mutex::Autolock lock(mLock);
+
+    if (strlen(address) != strlen("00:00:00:00:00:00"))
         return -EINVAL;
 
-    if (strcmp(address, mA2dpAddress)) {
-        strcpy(mA2dpAddress, address);
-        if (mData)
-            a2dp_set_sink(mData, mA2dpAddress);
-    }
-    
+    strcpy(mA2dpAddress, address);
+    if (mData)
+        a2dp_set_sink(mData, mA2dpAddress);
+
     return NO_ERROR;
 }
 
diff --git a/libs/audioflinger/A2dpAudioInterface.h b/libs/audioflinger/A2dpAudioInterface.h
index a56e8a0cdd32b..99614dc108739 100644
--- a/libs/audioflinger/A2dpAudioInterface.h
+++ b/libs/audioflinger/A2dpAudioInterface.h
@@ -82,11 +82,12 @@ private:
         virtual status_t    setVolume(float volume) { return INVALID_OPERATION; }
         virtual ssize_t     write(const void* buffer, size_t bytes);
                 status_t    standby();
-                status_t    close();
         virtual status_t    dump(int fd, const Vector& args);
 
     private:
         friend class A2dpAudioInterface;
+                status_t    init();
+                status_t    close();
         status_t            setAddress(const char* address);
 
     private:
@@ -96,9 +97,9 @@ private:
                 int         mRetryCount;
                 char        mA2dpAddress[20];
                 void*       mData;
+                Mutex       mLock;
     };
 
-    Mutex                   mLock;
     A2dpAudioStreamOut*     mOutput;
 };
 
diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp
index 92c40e90dcd50..440778ddc27ba 100644
--- a/libs/audioflinger/AudioFlinger.cpp
+++ b/libs/audioflinger/AudioFlinger.cpp
@@ -71,6 +71,9 @@ static const int8_t kMaxTrackStartupRetries = 50;
 static const int kStartSleepTime = 30000;
 static const int kStopSleepTime = 30000;
 
+static const int kDumpLockRetries = 50;
+static const int kDumpLockSleep = 20000;
+
 // Maximum number of pending buffers allocated by OutputTrack::write()
 static const uint8_t kMaxOutputTrackBuffers = 5;
 
@@ -115,8 +118,7 @@ static bool settingsAllowed() {
 
 AudioFlinger::AudioFlinger()
     : BnAudioFlinger(),
-        mAudioHardware(0), mA2dpAudioInterface(0),
-        mA2dpEnabled(false), mA2dpEnabledReq(false),
+        mAudioHardware(0), mA2dpAudioInterface(0), mA2dpEnabled(false), mNotifyA2dpChange(false),
         mForcedSpeakerCount(0), mForcedRoute(0), mRouteRestoreTime(0), mMusicMuteSaved(false)
 {
     mHardwareStatus = AUDIO_HW_IDLE;
@@ -190,13 +192,44 @@ AudioFlinger::~AudioFlinger()
 
 
 #ifdef WITH_A2DP
-void AudioFlinger::setA2dpEnabled(bool enable)
+// setA2dpEnabled_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::setA2dpEnabled_l(bool enable)
 {
+    SortedVector < sp > tracks;
+    SortedVector < wp > activeTracks;
+    
     LOGV_IF(enable, "set output to A2DP\n");
     LOGV_IF(!enable, "set output to hardware audio\n");
 
-    mA2dpEnabledReq = enable;
-    mA2dpMixerThread->wakeUp();
+    // Transfer tracks playing on MUSIC stream from one mixer to the other
+    if (enable) {
+        mHardwareMixerThread->getTracks_l(tracks, activeTracks);
+        mA2dpMixerThread->putTracks_l(tracks, activeTracks);
+    } else {
+        mA2dpMixerThread->getTracks_l(tracks, activeTracks);
+        mHardwareMixerThread->putTracks_l(tracks, activeTracks);
+    }
+    mA2dpEnabled = enable;
+    mNotifyA2dpChange = true;
+    mWaitWorkCV.broadcast();
+}
+
+// checkA2dpEnabledChange_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::checkA2dpEnabledChange_l()
+{
+    if (mNotifyA2dpChange) {
+        // Notify AudioSystem of the A2DP activation/deactivation
+        size_t size = mNotificationClients.size();
+        for (size_t i = 0; i < size; i++) {
+            sp binder = mNotificationClients.itemAt(i).promote();
+            if (binder != NULL) {
+                LOGV("Notifying output change to client %p", binder.get());
+                sp client = interface_cast (binder);
+                client->a2dpEnabledChanged(mA2dpEnabled);
+            }
+        }
+        mNotifyA2dpChange = false;
+    }
 }
 #endif // WITH_A2DP
 
@@ -236,8 +269,12 @@ status_t AudioFlinger::dumpInternals(int fd, const Vector& args)
     const size_t SIZE = 256;
     char buffer[SIZE];
     String8 result;
-
-    snprintf(buffer, SIZE, "Hardware status: %d\n", mHardwareStatus);
+    int hardwareStatus = mHardwareStatus;
+    
+    if (hardwareStatus == AUDIO_HW_IDLE && mHardwareMixerThread->mStandby) {
+        hardwareStatus = AUDIO_HW_STANDBY;
+    }
+    snprintf(buffer, SIZE, "Hardware status: %d\n", hardwareStatus);
     result.append(buffer);
     write(fd, result.string(), result.size());
     return NO_ERROR;
@@ -262,7 +299,14 @@ status_t AudioFlinger::dump(int fd, const Vector& args)
     if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
         dumpPermissionDenial(fd, args);
     } else {
-        AutoMutex lock(&mLock);
+        bool locked = false;
+        for (int i = 0; i < kDumpLockRetries; ++i) {
+            if (mLock.tryLock() == NO_ERROR) {
+                locked = true;
+                break;
+            }
+            usleep(kDumpLockSleep);
+        }
 
         dumpClients(fd, args);
         dumpInternals(fd, args);
@@ -277,6 +321,7 @@ status_t AudioFlinger::dump(int fd, const Vector& args)
         if (mAudioHardware) {
             mAudioHardware->dumpState(fd, args);
         }
+        if (locked) mLock.unlock();
     }
     return NO_ERROR;
 }
@@ -320,18 +365,19 @@ sp AudioFlinger::createTrack(
         }
 #ifdef WITH_A2DP
         if (isA2dpEnabled() && AudioSystem::routedToA2dpOutput(streamType)) {
-            track = mA2dpMixerThread->createTrack(client, streamType, sampleRate, format,
+            track = mA2dpMixerThread->createTrack_l(client, streamType, sampleRate, format,
                     channelCount, frameCount, sharedBuffer, &lStatus);            
         } else 
 #endif
         {
-            track = mHardwareMixerThread->createTrack(client, streamType, sampleRate, format,
+            track = mHardwareMixerThread->createTrack_l(client, streamType, sampleRate, format,
                     channelCount, frameCount, sharedBuffer, &lStatus);            
         }
-        if (track != NULL) {
-            trackHandle = new TrackHandle(track);
-            lStatus = NO_ERROR;            
-        }
+    }
+    if (lStatus == NO_ERROR) {
+        trackHandle = new TrackHandle(track);
+    } else {
+        track.clear();
     }
 
 Exit:
@@ -409,7 +455,7 @@ status_t AudioFlinger::setMasterVolume(float value)
 #ifdef WITH_A2DP
     mA2dpMixerThread->setMasterVolume(value);
 #endif
-    
+
     return NO_ERROR;
 }
 
@@ -436,7 +482,7 @@ status_t AudioFlinger::setRouting(int mode, uint32_t routes, uint32_t mask)
         if (routes & AudioSystem::ROUTE_BLUETOOTH_A2DP) {
             enableA2dp = true;
         }
-        setA2dpEnabled(enableA2dp);
+        setA2dpEnabled_l(enableA2dp);
         LOGV("setOutput done\n");
     }
 #endif
@@ -704,46 +750,6 @@ void AudioFlinger::binderDied(const wp& who) {
     }
 }
 
-void AudioFlinger::handleOutputSwitch()
-{
-    if (mA2dpEnabled != mA2dpEnabledReq)
-    {
-        Mutex::Autolock _l(mLock);
-
-        if (mA2dpEnabled != mA2dpEnabledReq)
-        {
-            mA2dpEnabled = mA2dpEnabledReq;
-            SortedVector < sp > tracks;
-            SortedVector < wp > activeTracks;
-            
-            // We hold mA2dpMixerThread mLock already 
-            Mutex::Autolock _l(mHardwareMixerThread->mLock);
-            
-            // Transfer tracks playing on MUSIC stream from one mixer to the other
-            if (mA2dpEnabled) {
-                mHardwareMixerThread->getTracks(tracks, activeTracks);
-                mA2dpMixerThread->putTracks(tracks, activeTracks);
-            } else {
-                mA2dpMixerThread->getTracks(tracks, activeTracks);
-                mHardwareMixerThread->putTracks(tracks, activeTracks);
-            }            
-            
-            // Notify AudioSystem of the A2DP activation/deactivation
-            size_t size = mNotificationClients.size();
-            for (size_t i = 0; i < size; i++) {
-                sp binder = mNotificationClients.itemAt(i).promote();
-                if (binder != NULL) {
-                    LOGV("Notifying output change to client %p", binder.get());
-                    sp client = interface_cast (binder);
-                    client->a2dpEnabledChanged(mA2dpEnabled);
-                }
-            }
-
-            mHardwareMixerThread->wakeUp();
-        }
-    }
-}
-
 void AudioFlinger::removeClient(pid_t pid)
 {
     LOGV("removeClient() pid %d, tid %d, calling tid %d", pid, gettid(), IPCThreadState::self()->getCallingPid());
@@ -751,17 +757,9 @@ void AudioFlinger::removeClient(pid_t pid)
     mClients.removeItem(pid);
 }
 
-void AudioFlinger::wakeUp()
-{
-    mHardwareMixerThread->wakeUp();
-#ifdef WITH_A2DP
-    mA2dpMixerThread->wakeUp();
-#endif // WITH_A2DP
-}
-
 bool AudioFlinger::isA2dpEnabled() const
 {
-    return mA2dpEnabledReq;
+    return mA2dpEnabled;
 }
 
 void AudioFlinger::handleForcedSpeakerRoute(int command)
@@ -803,7 +801,7 @@ void AudioFlinger::handleForcedSpeakerRoute(int command)
                 LOGV("mForcedSpeakerCount decremented to %d", mForcedSpeakerCount);            
             } else {
                 LOGE("mForcedSpeakerCount is already zero");            
-            }            
+            }
         }
         break;
     case CHECK_ROUTE_RESTORE_TIME:
@@ -946,20 +944,20 @@ bool AudioFlinger::MixerThread::threadLoop()
 
     do {
         enabledTracks = 0;
-        { // scope for the mLock
+        { // scope for the AudioFlinger::mLock
         
-            Mutex::Autolock _l(mLock);
+            Mutex::Autolock _l(mAudioFlinger->mLock);
 
 #ifdef WITH_A2DP
-            if (mOutputType == AudioSystem::AUDIO_OUTPUT_A2DP) {
-                mAudioFlinger->handleOutputSwitch();
-            }
             if (mOutputTrack != NULL && !mAudioFlinger->isA2dpEnabled()) {
                 if (outputTrackActive) {
+                    mAudioFlinger->mLock.unlock();
                     mOutputTrack->stop();
+                    mAudioFlinger->mLock.lock();
                     outputTrackActive = false;
                 }
             }
+            mAudioFlinger->checkA2dpEnabledChange_l();
 #endif
 
             const SortedVector< wp >& activeTracks = mActiveTracks;
@@ -968,7 +966,6 @@ bool AudioFlinger::MixerThread::threadLoop()
             if UNLIKELY(!activeTracks.size() && systemTime() > standbyTime) {
                 // wait until we have something to do...
                 LOGV("Audio hardware entering standby, output %d\n", mOutputType);
-//                mAudioFlinger->mHardwareStatus = AUDIO_HW_STANDBY;
                 if (!mStandby) {
                     mOutput->standby();
                     mStandby = true;
@@ -976,17 +973,18 @@ bool AudioFlinger::MixerThread::threadLoop()
                 
 #ifdef WITH_A2DP
                 if (outputTrackActive) {
+                    mAudioFlinger->mLock.unlock();
                     mOutputTrack->stop();
+                    mAudioFlinger->mLock.lock();
                     outputTrackActive = false;
                 }
 #endif
                 if (mOutputType == AudioSystem::AUDIO_OUTPUT_HARDWARE) {
                     mAudioFlinger->handleForcedSpeakerRoute(FORCE_ROUTE_RESTORE);
                 }                
-//                mHardwareStatus = AUDIO_HW_IDLE;
                 // we're about to wait, flush the binder command buffer
                 IPCThreadState::self()->flushCommands();
-                mWaitWorkCV.wait(mLock);
+                mAudioFlinger->mWaitWorkCV.wait(mAudioFlinger->mLock);
                 LOGV("Audio hardware exiting standby, output %d\n", mOutputType);
                 
                 if (mMasterMute == false) {
@@ -1104,13 +1102,13 @@ bool AudioFlinger::MixerThread::threadLoop()
             if (UNLIKELY(count)) {
                 for (size_t i=0 ; i& track = tracksToRemove[i];
-                    removeActiveTrack(track);
+                    removeActiveTrack_l(track);
                     if (track->isTerminated()) {
                         mTracks.remove(track);
-                        deleteTrackName(track->mName);
+                        deleteTrackName_l(track->mName);
                     }
                 }
-            }  
+            }
        }
         
         if (LIKELY(enabledTracks)) {
@@ -1197,8 +1195,8 @@ void AudioFlinger::MixerThread::onFirstRef()
     run(buffer, ANDROID_PRIORITY_URGENT_AUDIO);
 }
 
-
-sp  AudioFlinger::MixerThread::createTrack(
+// MixerThread::createTrack_l() must be called with AudioFlinger::mLock held
+sp  AudioFlinger::MixerThread::createTrack_l(
         const sp& client,
         int streamType,
         uint32_t sampleRate,
@@ -1218,26 +1216,22 @@ sp  AudioFlinger::MixerThread::createTrack(
         goto Exit;
     }
 
-    {
-        Mutex::Autolock _l(mLock);
 
-        if (mSampleRate == 0) {
-            LOGE("Audio driver not initialized.");
-            lStatus = NO_INIT;
-            goto Exit;
-        }
-
-        track = new Track(this, client, streamType, sampleRate, format,
-                channelCount, frameCount, sharedBuffer);
-        if (track->getCblk() == NULL) {
-            track.clear();
-            lStatus = NO_MEMORY;
-            goto Exit;
-        }
-        mTracks.add(track);
-        lStatus = NO_ERROR;
+    if (mSampleRate == 0) {
+        LOGE("Audio driver not initialized.");
+        lStatus = NO_INIT;
+        goto Exit;
     }
 
+    track = new Track(this, client, streamType, sampleRate, format,
+            channelCount, frameCount, sharedBuffer);
+    if (track->getCblk() == NULL) {
+        lStatus = NO_MEMORY;
+        goto Exit;
+    }
+    mTracks.add(track);
+    lStatus = NO_ERROR;
+
 Exit:
     if(status) {
         *status = lStatus;
@@ -1245,12 +1239,13 @@ Exit:
     return track;
 }
 
-void AudioFlinger::MixerThread::getTracks(
+// getTracks_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::getTracks_l(
         SortedVector < sp >& tracks,
         SortedVector < wp >& activeTracks)
 {
     size_t size = mTracks.size();
-    LOGV ("MixerThread::getTracks() for output %d, mTracks.size %d, mActiveTracks.size %d", mOutputType,  mTracks.size(), mActiveTracks.size());
+    LOGV ("MixerThread::getTracks_l() for output %d, mTracks.size %d, mActiveTracks.size %d", mOutputType,  mTracks.size(), mActiveTracks.size());
     for (size_t i = 0; i < size; i++) {
         sp t = mTracks[i];
         if (AudioSystem::routedToA2dpOutput(t->mStreamType)) {
@@ -1267,28 +1262,29 @@ void AudioFlinger::MixerThread::getTracks(
 
     size = activeTracks.size();
     for (size_t i = 0; i < size; i++) {
-        removeActiveTrack(activeTracks[i]);
+        removeActiveTrack_l(activeTracks[i]);
     }
     
     size = tracks.size();
     for (size_t i = 0; i < size; i++) {
         sp t = tracks[i];
         mTracks.remove(t);
-        deleteTrackName(t->name());
+        deleteTrackName_l(t->name());
     }
 }
 
-void AudioFlinger::MixerThread::putTracks(
+// putTracks_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::putTracks_l(
         SortedVector < sp >& tracks,
         SortedVector < wp >& activeTracks)
 {
 
-    LOGV ("MixerThread::putTracks() for output %d, tracks.size %d, activeTracks.size %d", mOutputType,  tracks.size(), activeTracks.size());
+    LOGV ("MixerThread::putTracks_l() for output %d, tracks.size %d, activeTracks.size %d", mOutputType,  tracks.size(), activeTracks.size());
 
     size_t size = tracks.size();
     for (size_t i = 0; i < size ; i++) {
         sp t = tracks[i];
-        int name = getTrackName();
+        int name = getTrackName_l();
 
         if (name < 0) return;
         
@@ -1298,7 +1294,7 @@ void AudioFlinger::MixerThread::putTracks(
 
         int j = activeTracks.indexOf(t);
         if (j >= 0) {
-            addActiveTrack(t);
+            addActiveTrack_l(t);
         }            
     }
 }
@@ -1390,10 +1386,10 @@ bool AudioFlinger::MixerThread::isMusicActive() const
     return false;
 }
 
-status_t AudioFlinger::MixerThread::addTrack(const sp& track)
+// addTrack_l() must be called with AudioFlinger::mLock held
+status_t AudioFlinger::MixerThread::addTrack_l(const sp& track)
 {
     status_t status = ALREADY_EXISTS;
-    Mutex::Autolock _l(mLock);
 
     // here the track could be either new, or restarted
     // in both cases "unstop" the track
@@ -1412,55 +1408,41 @@ status_t AudioFlinger::MixerThread::addTrack(const sp& track)
         // effectively get the latency it requested.
         track->mFillingUpStatus = Track::FS_FILLING;
         track->mResetDone = false;
-        addActiveTrack(track);
+        addActiveTrack_l(track);
         status = NO_ERROR;
     }
     
     LOGV("mWaitWorkCV.broadcast");
-    mWaitWorkCV.broadcast();
+    mAudioFlinger->mWaitWorkCV.broadcast();
 
     return status;
 }
 
-void AudioFlinger::MixerThread::removeTrack(wp track, int name)
+// removeTrack_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::removeTrack_l(wp track, int name)
 {
-    Mutex::Autolock _l(mLock);
     sp t = track.promote();
     if (t!=NULL && (t->mState <= TrackBase::STOPPED)) {
-        remove_track_l(track, name);
-    }
-}
-
-void AudioFlinger::MixerThread::remove_track_l(wp track, int name)
-{
-    sp t = track.promote();
-    if (t!=NULL) {
         t->reset();
+        deleteTrackName_l(name);
+        removeActiveTrack_l(track);
+        mAudioFlinger->mWaitWorkCV.broadcast();
     }
-    deleteTrackName(name);
-    removeActiveTrack(track);
-    mWaitWorkCV.broadcast();
 }
 
-void AudioFlinger::MixerThread::destroyTrack(const sp& track)
+// destroyTrack_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::destroyTrack_l(const sp& track)
 {
-    // NOTE: We're acquiring a strong reference on the track before
-    // acquiring the lock, this is to make sure removing it from
-    // mTracks won't cause the destructor to be called while the lock is
-    // held (note that technically, 'track' could be a reference to an item
-    // in mTracks, which is why we need to do this).
-    sp keep(track);
-    Mutex::Autolock _l(mLock);
     track->mState = TrackBase::TERMINATED;
     if (mActiveTracks.indexOf(track) < 0) {
         LOGV("remove track (%d) and delete from mixer", track->name());
         mTracks.remove(track);
-        deleteTrackName(keep->name());
+        deleteTrackName_l(track->name());
     }
 }
 
-
-void AudioFlinger::MixerThread::addActiveTrack(const wp& t)
+// addActiveTrack_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::addActiveTrack_l(const wp& t)
 {
     mActiveTracks.add(t);
 
@@ -1476,7 +1458,8 @@ void AudioFlinger::MixerThread::addActiveTrack(const wp& t)
     }
 }
 
-void AudioFlinger::MixerThread::removeActiveTrack(const wp& t)
+// removeActiveTrack_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::removeActiveTrack_l(const wp& t)
 {
     mActiveTracks.remove(t);
 
@@ -1492,12 +1475,14 @@ void AudioFlinger::MixerThread::removeActiveTrack(const wp& t)
     }
 }
 
-int AudioFlinger::MixerThread::getTrackName()
+// getTrackName_l() must be called with AudioFlinger::mLock held
+int AudioFlinger::MixerThread::getTrackName_l()
 {
     return mAudioMixer->getTrackName();
 }
 
-void AudioFlinger::MixerThread::deleteTrackName(int name)
+// deleteTrackName_l() must be called with AudioFlinger::mLock held
+void AudioFlinger::MixerThread::deleteTrackName_l(int name)
 {
     mAudioMixer->deleteTrackName(name);
 }
@@ -1509,6 +1494,7 @@ size_t AudioFlinger::MixerThread::getOutputFrameCount()
 
 // ----------------------------------------------------------------------------
 
+// TrackBase constructor must be called with AudioFlinger::mLock held
 AudioFlinger::MixerThread::TrackBase::TrackBase(
             const sp& mixerThread,
             const sp& client,
@@ -1529,7 +1515,7 @@ AudioFlinger::MixerThread::TrackBase::TrackBase(
         mFormat(format),
         mFlags(flags & ~SYSTEM_FLAGS_MASK)
 {
-    mName = mixerThread->getTrackName();
+    mName = mixerThread->getTrackName_l();
     LOGV("TrackBase contructor name %d, calling thread %d", mName, IPCThreadState::self()->getCallingPid());
     if (mName < 0) {
         LOGE("no more track names availlable");
@@ -1661,6 +1647,7 @@ void* AudioFlinger::MixerThread::TrackBase::getBuffer(uint32_t offset, uint32_t
 
 // ----------------------------------------------------------------------------
 
+// Track constructor must be called with AudioFlinger::mLock held
 AudioFlinger::MixerThread::Track::Track(
             const sp& mixerThread,
             const sp& client,
@@ -1681,13 +1668,26 @@ AudioFlinger::MixerThread::Track::Track(
 AudioFlinger::MixerThread::Track::~Track()
 {
     wp weak(this); // never create a strong ref from the dtor
+    Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
     mState = TERMINATED;
-    mMixerThread->removeTrack(weak, mName);
+    mMixerThread->removeTrack_l(weak, mName);
 }
 
 void AudioFlinger::MixerThread::Track::destroy()
 {
-    mMixerThread->destroyTrack(this);
+    // NOTE: destroyTrack_l() can remove a strong reference to this Track 
+    // by removing it from mTracks vector, so there is a risk that this Tracks's
+    // desctructor is called. As the destructor needs to lock AudioFlinger::mLock,
+    // we must acquire a strong reference on this Track before locking AudioFlinger::mLock
+    // here so that the destructor is called only when exiting this function.
+    // On the other hand, as long as Track::destroy() is only called by 
+    // TrackHandle destructor, the TrackHandle still holds a strong ref on 
+    // this Track with its member mTrack.
+    sp keep(this);
+    { // scope for AudioFlinger::mLock
+        Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
+        mMixerThread->destroyTrack_l(this);
+    }
 }
 
 void AudioFlinger::MixerThread::Track::dump(char* buffer, size_t size)
@@ -1765,14 +1765,15 @@ bool AudioFlinger::MixerThread::Track::isReady() const {
 status_t AudioFlinger::MixerThread::Track::start()
 {
     LOGV("start(%d), calling thread %d for output %d", mName, IPCThreadState::self()->getCallingPid(), mMixerThread->mOutputType);
-    mMixerThread->addTrack(this);
+    Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
+    mMixerThread->addTrack_l(this);
     return NO_ERROR;
 }
 
 void AudioFlinger::MixerThread::Track::stop()
 {
     LOGV("stop(%d), calling thread %d for output %d", mName, IPCThreadState::self()->getCallingPid(), mMixerThread->mOutputType);
-    Mutex::Autolock _l(mMixerThread->mLock);
+    Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
     if (mState > STOPPED) {
         mState = STOPPED;
         // If the track is not active (PAUSED and buffers full), flush buffers
@@ -1786,7 +1787,7 @@ void AudioFlinger::MixerThread::Track::stop()
 void AudioFlinger::MixerThread::Track::pause()
 {
     LOGV("pause(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid());
-    Mutex::Autolock _l(mMixerThread->mLock);
+    Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
     if (mState == ACTIVE || mState == RESUMING) {
         mState = PAUSING;
         LOGV("ACTIVE/RESUMING => PAUSING (%d)", mName);
@@ -1796,7 +1797,7 @@ void AudioFlinger::MixerThread::Track::pause()
 void AudioFlinger::MixerThread::Track::flush()
 {
     LOGV("flush(%d)", mName);
-    Mutex::Autolock _l(mMixerThread->mLock);
+    Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
     if (mState != STOPPED && mState != PAUSED && mState != PAUSING) {
         return;
     }
@@ -1840,6 +1841,7 @@ void AudioFlinger::MixerThread::Track::setVolume(float left, float right)
 
 // ----------------------------------------------------------------------------
 
+// RecordTrack constructor must be called with AudioFlinger::mLock held
 AudioFlinger::MixerThread::RecordTrack::RecordTrack(
             const sp& mixerThread,
             const sp& client,
@@ -1857,7 +1859,8 @@ AudioFlinger::MixerThread::RecordTrack::RecordTrack(
 
 AudioFlinger::MixerThread::RecordTrack::~RecordTrack()
 {
-    mMixerThread->deleteTrackName(mName);
+    Mutex::Autolock _l(mMixerThread->mAudioFlinger->mLock);
+    mMixerThread->deleteTrackName_l(mName);
 }
 
 status_t AudioFlinger::MixerThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
@@ -2183,7 +2186,6 @@ sp AudioFlinger::openRecord(
         uint32_t flags,
         status_t *status)
 {
-    sp thread;
     sp recordTrack;
     sp recordHandle;
     sp client;
@@ -2227,7 +2229,7 @@ sp AudioFlinger::openRecord(
     }
 
     // add client to list
-    {
+    { // scope for mLock
         Mutex::Autolock _l(mLock);
         wclient = mClients.valueFor(pid);
         if (wclient != NULL) {
@@ -2236,15 +2238,15 @@ sp AudioFlinger::openRecord(
             client = new Client(this, pid);
             mClients.add(pid, client);
         }
+
+        // frameCount must be a multiple of input buffer size
+        inFrameCount = inputBufferSize/channelCount/sizeof(short);
+        frameCount = ((frameCount - 1)/inFrameCount + 1) * inFrameCount;
+    
+        // create new record track. The record track uses one track in mHardwareMixerThread by convention.
+        recordTrack = new MixerThread::RecordTrack(mHardwareMixerThread, client, streamType, sampleRate,
+                                                   format, channelCount, frameCount, flags);
     }
-
-    // frameCount must be a multiple of input buffer size
-    inFrameCount = inputBufferSize/channelCount/sizeof(short);
-    frameCount = ((frameCount - 1)/inFrameCount + 1) * inFrameCount;
-
-    // create new record track and pass to record thread
-    recordTrack = new MixerThread::RecordTrack(mHardwareMixerThread, client, streamType, sampleRate,
-                                               format, channelCount, frameCount, flags);
     if (recordTrack->getCblk() == NULL) {
         recordTrack.clear();
         lStatus = NO_MEMORY;
@@ -2369,7 +2371,8 @@ bool AudioFlinger::AudioRecordThread::threadLoop()
         } else if (mRecordTrack != 0) {
 
             buffer.frameCount = inFrameCount;
-            if (LIKELY(mRecordTrack->getNextBuffer(&buffer) == NO_ERROR)) {
+            if (LIKELY(mRecordTrack->getNextBuffer(&buffer) == NO_ERROR &&
+                       (int)buffer.frameCount == inFrameCount)) {
                 LOGV("AudioRecordThread read: %d frames", buffer.frameCount);
                 ssize_t bytesRead = input->read(buffer.raw, inBufferSize);
                 if (bytesRead < 0) {
diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h
index 77f064b237416..c505336c0ebb9 100644
--- a/libs/audioflinger/AudioFlinger.h
+++ b/libs/audioflinger/AudioFlinger.h
@@ -112,7 +112,7 @@ public:
     
     virtual     size_t      getInputBufferSize(uint32_t sampleRate, int format, int channelCount);
     
-    virtual     void        wakeUp();
+    virtual     void        wakeUp()    { mWaitWorkCV.broadcast(); }
     
     // IBinder::DeathRecipient
     virtual     void        binderDied(const wp& who);
@@ -161,7 +161,8 @@ private:
     void                    doSetOutput(int outputType);
 
 #ifdef WITH_A2DP
-    void                    setA2dpEnabled(bool enable);
+    void                    setA2dpEnabled_l(bool enable);
+    void                    checkA2dpEnabledChange_l();
 #endif
     static bool             streamForcedToSpeaker(int streamType);
     
@@ -459,7 +460,7 @@ private:
                     bool        isMusicActive() const;
         
                     
-        sp createTrack(
+                    sp   createTrack_l(
                                     const sp& client,
                                     int streamType,
                                     uint32_t sampleRate,
@@ -468,12 +469,10 @@ private:
                                     int frameCount,
                                     const sp& sharedBuffer,
                                     status_t *status);
-
-                    void        wakeUp() { mWaitWorkCV.broadcast(); }
                     
-                    void        getTracks(SortedVector < sp >& tracks,
+                    void        getTracks_l(SortedVector < sp >& tracks,
                                           SortedVector < wp >& activeTracks);
-                    void        putTracks(SortedVector < sp >& tracks,
+                    void        putTracks_l(SortedVector < sp >& tracks,
                                           SortedVector < wp >& activeTracks);
                     void        setOuputTrack(OutputTrack *track) { mOutputTrack = track; }
                     
@@ -498,22 +497,19 @@ private:
         MixerThread(const Client&);
         MixerThread& operator = (const MixerThread&);
   
-        status_t    addTrack(const sp& track);
-        void        removeTrack(wp track, int name);
-        void        remove_track_l(wp track, int name);
-        void        destroyTrack(const sp& track);
-        int         getTrackName();
-        void        deleteTrackName(int name);
-        void        addActiveTrack(const wp& t);
-        void        removeActiveTrack(const wp& t);
+        status_t    addTrack_l(const sp& track);
+        void        removeTrack_l(wp track, int name);
+        void        destroyTrack_l(const sp& track);
+        int         getTrackName_l();
+        void        deleteTrackName_l(int name);
+        void        addActiveTrack_l(const wp& t);
+        void        removeActiveTrack_l(const wp& t);
         size_t      getOutputFrameCount();
 
         status_t    dumpInternals(int fd, const Vector& args);
         status_t    dumpTracks(int fd, const Vector& args);
         
         sp                mAudioFlinger;       
-        mutable     Mutex               mLock;
-        mutable     Condition           mWaitWorkCV;
         SortedVector< wp >       mActiveTracks;
         SortedVector< sp >       mTracks;
         stream_type_t                   mStreamTypes[AudioSystem::NUM_STREAM_TYPES];
@@ -607,11 +603,11 @@ private:
 
                 status_t    startRecord(MixerThread::RecordTrack* recordTrack);
                 void        stopRecord(MixerThread::RecordTrack* recordTrack);
-                
-                void        handleOutputSwitch();
 
-    mutable     Mutex                                       mHardwareLock;
-    mutable     Mutex                                       mLock;
+    mutable     Mutex                               mHardwareLock;
+    mutable     Mutex                               mLock;
+    mutable     Condition                           mWaitWorkCV;
+
                 DefaultKeyedVector< pid_t, wp >     mClients;
 
                 sp                     mA2dpMixerThread;
@@ -620,7 +616,7 @@ private:
                 AudioHardwareInterface*             mA2dpAudioInterface;
                 sp               mAudioRecordThread;
                 bool                                mA2dpEnabled;
-                bool                                mA2dpEnabledReq;
+                bool                                mNotifyA2dpChange;
     mutable     int                                 mHardwareStatus;
                 SortedVector< wp >         mNotificationClients;
                 int                                 mForcedSpeakerCount;
diff --git a/location/data/Android.mk b/location/data/Android.mk
index befd79299a472..794e6c7bf0d8d 100644
--- a/location/data/Android.mk
+++ b/location/data/Android.mk
@@ -13,7 +13,7 @@ include $(CLEAR_VARS)
 
 LOCAL_MODULE := nmea
 
-LOCAL_MODULE_TAGS := development
+LOCAL_MODULE_TAGS := tests
 
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(local_target_dir)/gps
@@ -27,7 +27,7 @@ include $(CLEAR_VARS)
 
 LOCAL_MODULE := location
 
-LOCAL_MODULE_TAGS := development
+LOCAL_MODULE_TAGS := tests
 
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(local_target_dir)/gps
@@ -41,7 +41,7 @@ include $(CLEAR_VARS)
 
 LOCAL_MODULE := properties
 
-LOCAL_MODULE_TAGS := development
+LOCAL_MODULE_TAGS := tests
 
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(local_target_dir)/gps
diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp
index 5416629b8e10a..856059328e4b5 100644
--- a/media/libmedia/ToneGenerator.cpp
+++ b/media/libmedia/ToneGenerator.cpp
@@ -182,8 +182,9 @@ bool ToneGenerator::startTone(int toneType) {
             mLock.lock();
             if (mState == TONE_STARTING) {
                 LOGV("Wait for start callback");
-                if (mWaitCbkCond.waitRelative(mLock, seconds(1)) != NO_ERROR) {
-                    LOGE("--- Immediate start timed out");
+                status_t lStatus = mWaitCbkCond.waitRelative(mLock, seconds(1));
+                if (lStatus != NO_ERROR) {
+                    LOGE("--- Immediate start timed out, status %d", lStatus);
                     mState = TONE_IDLE;
                     lResult = false;
                 }
@@ -195,13 +196,14 @@ bool ToneGenerator::startTone(int toneType) {
         LOGV("Delayed start\n");
 
         mState = TONE_RESTARTING;
-        if (mWaitCbkCond.waitRelative(mLock, seconds(1)) == NO_ERROR) {
+        status_t lStatus = mWaitCbkCond.waitRelative(mLock, seconds(1));
+        if (lStatus == NO_ERROR) {
             if (mState != TONE_IDLE) {
                 lResult = true;
             }
             LOGV("cond received");
         } else {
-            LOGE("--- Delayed start timed out");
+            LOGE("--- Delayed start timed out, status %d", lStatus);
             mState = TONE_IDLE;
         }
     }
@@ -368,6 +370,8 @@ void ToneGenerator::audioCallback(int event, void* user, void *info) {
             break;
         default:
             LOGV("Extra Cbk");
+            // Force loop exit
+            lNumSmp = 0;
             goto audioCallback_EndLoop;
         }
         
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 5e9c4882fb104..07b43bb34f107 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -494,4 +494,18 @@ public class MediaNames {
     "/sdcard/media_api/video_stress/h263/mpeg4_QVGA.3gp",
     "/sdcard/media_api/video_stress/h263/mpeg4_SQVGA.mp4"
   };
+  
+  //Streaming test files
+  public static final String STREAM_H264_480_360_1411k = 
+      "http://sridharg.googlejunta.com/yslau/stress_media/h264_regular.mp4"; 
+  public static final String STREAM_WMV = 
+      "http://sridharg.googlejunta.com/yslau/stress_media/bugs.wmv"; 
+  public static final String STREAM_H263_176x144_325k = 
+      "http://sridharg.googlejunta.com/yslau/stress_media/h263_regular.3gp";
+  public static final String STREAM_H264_352x288_1536k = 
+      "http://sridharg.googlejunta.com/yslau/stress_media/h264_highBitRate.mp4";
+  public static final String STREAM_MP3= 
+      "http://sridharg.googlejunta.com/yslau/stress_media/mp3_regular.mp3";
+  public static final String STREAM_MPEG4_QVGA_128k = 
+      "http://sridharg.googlejunta.com/yslau/stress_media/mpeg4_qvga_24fps.3gp";
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
index 0e88719de6bf3..caba47c58c388 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/CodecTest.java
@@ -28,6 +28,7 @@ import android.graphics.BitmapFactory;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -39,6 +40,16 @@ import java.io.InputStream;
  */  
 public class CodecTest {    
     private static String TAG = "MediaPlayerApiTest";
+    private static MediaPlayer mMediaPlayer;
+    private MediaPlayer.OnPreparedListener mOnPreparedListener;
+    
+    private static int WAIT_FOR_COMMAND_TO_COMPLETE = 10000;  //10 seconds max.
+    private static boolean mInitialized = false;
+    private static Looper mLooper = null;
+    private static final Object lock = new Object();
+    private static final Object prepareDone = new Object();
+    private static boolean onPrepareSuccess = false;
+    
 
     public static String printCpuInfo(){      
         String cm = "dumpsys cpuinfo";
@@ -573,5 +584,89 @@ public class CodecTest {
 
         return true;
     }
+    
+    /*
+     * Initializes the message looper so that the mediaPlayer object can 
+     * receive the callback messages.
+     */
+    private static void initializeMessageLooper() {
+        Log.v(TAG, "start looper");
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by camera.
+                Looper.prepare();
+                Log.v(TAG, "start loopRun");
+                // Save the looper so that we can terminate this thread 
+                // after we are done with it.
+                mLooper = Looper.myLooper();                
+                mMediaPlayer = new MediaPlayer();                                
+                synchronized (lock) {
+                    mInitialized = true;
+                    lock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+                Log.v(TAG, "initializeMessageLooper: quit.");
+            }
+        }.start();
+    }
+    
+    /*
+     * Terminates the message looper thread.
+     */
+    private static void terminateMessageLooper() {
+        mLooper.quit();
+        mMediaPlayer.release();
+    }
+    
+    static MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
+        public void onPrepared(MediaPlayer mp) {
+            synchronized (prepareDone) {
+                Log.v(TAG, "notify the prepare callback");
+                prepareDone.notify();
+                onPrepareSuccess = true;
+            }
+        }
+    };
+   
+    public static boolean prepareAsyncCallback(String filePath) throws Exception {
+        int videoWidth = 0;
+        int videoHeight = 0;
+        boolean checkVideoDimension = false;
+        
+        initializeMessageLooper();
+        synchronized (lock) {
+            try {
+                lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
+            } catch(Exception e) {
+                Log.v(TAG, "looper was interrupted.");
+                return false;
+            }
+        }
+        try{
+            mMediaPlayer.setOnPreparedListener(mPreparedListener);
+            mMediaPlayer.setDataSource(filePath);
+            mMediaPlayer.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
+            mMediaPlayer.prepareAsync(); 
+            synchronized (prepareDone) {
+                try {
+                    prepareDone.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
+                    Log.v(TAG, "setPreview done");
+                } catch (Exception e) {
+                    Log.v(TAG, "wait was interrupted.");
+                }
+            }
+            videoWidth = mMediaPlayer.getVideoWidth();
+            videoHeight = mMediaPlayer.getVideoHeight();
+            
+            terminateMessageLooper();
+        }catch (Exception e){
+            Log.v(TAG,e.getMessage());
+        }      
+       return onPrepareSuccess;
+    }
+    
+    
+    
 }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
index dd94164d95ce4..ee6a727dadb73 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java
@@ -52,7 +52,8 @@ public class MediaPlayerApiTest extends ActivityInstrumentationTestCase connected = mBluetoothA2dp.listConnectedSinks();
+
+        for (String address : connected) {
+            if (mBluetoothA2dp.getSinkState(address) == BluetoothA2dp.STATE_PLAYING) {
+                isBluetoothPlaying = true;
+                break;
+            }
+        }
+        setBluetoothScanMode(isBluetoothPlaying);
+    }
+
     @Override
     public void releaseWakeLock() {
         if (mReleaseWakeLockCallback != null) {
@@ -682,9 +711,13 @@ public class WifiStateTracker extends NetworkStateTracker {
                  * are going to end up being thrown away. Obviously, if we
                  * ever want to support multicast, this will have to change.
                  */
+                if (mBluetoothA2dp == null) {
+                    mBluetoothA2dp = new BluetoothA2dp(mContext);
+                }
                 synchronized (this) {
                     WifiNative.startPacketFiltering();
                 }
+                checkIsBluetoothPlaying();
                 break;
 
             case EVENT_SUPPLICANT_DISCONNECT: