diff options
111 files changed, 2185 insertions, 3837 deletions
diff --git a/api/current.xml b/api/current.xml index 55c7756..96998ed 100644 --- a/api/current.xml +++ b/api/current.xml @@ -27761,6 +27761,17 @@ visibility="public" > </field> +<field name="ACTION_USER_PRESENT" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.USER_PRESENT"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_VIEW" type="java.lang.String" transient="false" @@ -108917,6 +108928,21 @@ <parameter name="what" type="java.lang.Object"> </parameter> </method> +<method name="isSelectingMetaTracker" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="text" type="java.lang.CharSequence"> +</parameter> +<parameter name="what" type="java.lang.Object"> +</parameter> +</method> <method name="onKeyDown" return="boolean" abstract="false" @@ -123194,6 +123220,19 @@ visibility="public" > </method> +<method name="checkInputConnectionProxy" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="view" type="android.view.View"> +</parameter> +</method> <method name="clearAnimation" return="void" abstract="false" @@ -135200,7 +135239,7 @@ type="int" transient="false" volatile="false" - value="5" + value="6" static="true" final="true" deprecated="not deprecated" @@ -135211,7 +135250,7 @@ type="int" transient="false" volatile="false" - value="1" + value="2" static="true" final="true" deprecated="not deprecated" @@ -135222,7 +135261,7 @@ type="int" transient="false" volatile="false" - value="4" + value="5" static="true" final="true" deprecated="not deprecated" @@ -135233,7 +135272,7 @@ type="int" transient="false" volatile="false" - value="0" + value="1" static="true" final="true" deprecated="not deprecated" @@ -135244,7 +135283,7 @@ type="int" transient="false" volatile="false" - value="2" + value="3" static="true" final="true" deprecated="not deprecated" @@ -135255,51 +135294,51 @@ type="int" transient="false" volatile="false" - value="3" + value="4" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="IME_FLAG_NO_ENTER_ACTION" +<field name="IME_ACTION_UNSPECIFIED" type="int" transient="false" volatile="false" - value="1073741824" + value="0" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="IME_MASK_ACTION" +<field name="IME_FLAG_NO_ENTER_ACTION" type="int" transient="false" volatile="false" - value="255" + value="1073741824" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="IME_NORMAL" +<field name="IME_MASK_ACTION" type="int" transient="false" volatile="false" - value="0" + value="255" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="IME_UNDEFINED" +<field name="IME_NULL" type="int" transient="false" volatile="false" - value="-2147483648" + value="0" static="true" final="true" deprecated="not deprecated" @@ -135501,6 +135540,17 @@ visibility="public" > </field> +<field name="FLAG_SELECTING" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_SINGLE_LINE" type="int" transient="false" @@ -136071,6 +136121,271 @@ > </field> </interface> +<class name="InputConnectionWrapper" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.view.inputmethod.InputConnection"> +</implements> +<constructor name="InputConnectionWrapper" + type="android.view.inputmethod.InputConnectionWrapper" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="target" type="android.view.inputmethod.InputConnection"> +</parameter> +</constructor> +<method name="beginBatchEdit" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="clearMetaKeyStates" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="states" type="int"> +</parameter> +</method> +<method name="commitCompletion" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="text" type="android.view.inputmethod.CompletionInfo"> +</parameter> +</method> +<method name="commitText" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="text" type="java.lang.CharSequence"> +</parameter> +<parameter name="newCursorPosition" type="int"> +</parameter> +</method> +<method name="deleteSurroundingText" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="leftLength" type="int"> +</parameter> +<parameter name="rightLength" type="int"> +</parameter> +</method> +<method name="endBatchEdit" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="finishComposingText" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getCursorCapsMode" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="reqModes" type="int"> +</parameter> +</method> +<method name="getExtractedText" + return="android.view.inputmethod.ExtractedText" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="request" type="android.view.inputmethod.ExtractedTextRequest"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<method name="getTextAfterCursor" + return="java.lang.CharSequence" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="n" type="int"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<method name="getTextBeforeCursor" + return="java.lang.CharSequence" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="n" type="int"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<method name="performContextMenuAction" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="id" type="int"> +</parameter> +</method> +<method name="performEditorAction" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="editorAction" type="int"> +</parameter> +</method> +<method name="performPrivateCommand" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="action" type="java.lang.String"> +</parameter> +<parameter name="data" type="android.os.Bundle"> +</parameter> +</method> +<method name="reportFullscreenMode" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="enabled" type="boolean"> +</parameter> +</method> +<method name="sendKeyEvent" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="event" type="android.view.KeyEvent"> +</parameter> +</method> +<method name="setComposingText" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="text" type="java.lang.CharSequence"> +</parameter> +<parameter name="newCursorPosition" type="int"> +</parameter> +</method> +<method name="setSelection" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="start" type="int"> +</parameter> +<parameter name="end" type="int"> +</parameter> +</method> +</class> <interface name="InputMethod" abstract="true" static="false" @@ -155853,7 +156168,7 @@ <parameter name="depth" type="int"> </parameter> </method> -<method name="didTouchFocusSelectAll" +<method name="didTouchFocusSelect" return="boolean" abstract="false" native="false" diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e1c1f64..824fd9b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1042,6 +1042,14 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; + + /** + * Broadcast Action: Sent when the user is present after device wakes up (e.g when the + * keyguard is gone). + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USER_PRESENT= "android.intent.action.USER_PRESENT"; + /** * Broadcast Action: The current time has changed. Sent every * minute. You can <em>not</em> receive this through components declared diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4ae8b08..2dcb483 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 fadcb35..1c91736 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 0000000..389bd07 --- /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<K, V> extends LinkedHashMap<K, V> { + 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<K, V> eldest) { + return size() > sCacheSize; + } + } + + // A pointer to native EmojiFactory object. + private int mNativeEmojiFactory; + private String mName; + // Cache. + private Map<Integer, WeakReference<Bitmap>> 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<Integer, WeakReference<Bitmap>>(); + } + + @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<Bitmap> 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<Bitmap>(ret)); + } + return ret; + } else { + Bitmap tmp = cache.get(); + if (tmp == null) { + Bitmap ret = nativeGetBitmapFromAndroidPua(mNativeEmojiFactory, pua); + mCache.put(pua, new WeakReference<Bitmap>(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 f1e613e..34d5695 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 1454089..d11a9c5 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 87153cf..6f5513a 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 8b09583..e4ebcca 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<String, Integer> mPasskeyAgentRequestData; private HashMap<String, IBluetoothDeviceCallback> 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 8aa49af..17c7a6c 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 39ad976..61ec67f 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 88ff3c5..f7ac522 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 cc3563c..841066c 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 3e762f5..d78320a 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. + * + * <p>The default implementation always returns false. This does + * <em>not</em> 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. */ @@ -3689,6 +3696,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } /** + * 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 52b4107..deca910 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 1c0d42a..b00e565 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}. - */ - 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. + * Generic unspecified type for {@link #imeOptions}. */ - 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 e5d3cae..c2851d6 100644 --- a/core/java/android/view/inputmethod/ExtractedText.java +++ b/core/java/android/view/inputmethod/ExtractedText.java @@ -55,6 +55,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. */ public int flags; @@ -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 32cce35..8b6831e 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 0000000..e3d5e62 --- /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; + +/** + * <p>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 916ffea..7f2142e 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 39806dc..efc131f 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 91795a3..dc39b90 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 @@ -4432,14 +4217,6 @@ public class WebView extends AbsoluteLayout } /** - * @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 d72570a..f517dc9 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<ListAdapter> 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<ListAdapter> 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<ListAdapter> 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<ListAdapter> 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<ListAdapter> implements Te break; } - if (okToSend && acceptFilter()) { + if (okToSend) { createTextFilter(true); KeyEvent forwardEvent = event; @@ -2906,6 +2913,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * 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; * * @param animateEntrance true if the window should appear with an animation @@ -2918,6 +2946,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> 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 0a552e8..3368477 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 1d0fd5e..7e55c78 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(); + /** * <p>Creates a new asynchronous filter.</p> */ @@ -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 f2cec92..227fb95 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 3f4912f..8271a9a 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}. + * + * <p>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 1227afd..ec6f88b 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 ec45e23..1ba2dce 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(); } + /* + * 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. + * <p> + * It checks the global system setting to ensure this has not been seen + * before. Furthermore, if the application does not have privilege to write + * 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 ZoomRingController.getZoomType(context) == 2; + 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 fdc05b6..e978db8 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 83a1225..0000000 --- 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? - * <p> - * 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 3bf3b22..0000000 --- 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. - * <p> - * 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). - * <p> - * 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()}. - * <p> - * Notes: - * <ul> - * <li> The controller dispatches touch events differently than the regular view - * framework. - * <li> Please ensure you set your view to INVISIBLE not GONE when hiding it. - * </ul> - * - * @return The layout used by the container. - */ - public FrameLayout getContainer() { - return mContainer; - } - - /** - * Returns the id of the zoom ring widget. - * - * @return The id of the zoom ring widget. - */ - public int getZoomRingId() { - return mZoomRing.getId(); - } - - 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). - * <p> - * In most cases, the client can use a {@link GestureDetector} and forward events from - * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)}. - * - * @param event The event belonging to the second tap. - * @return Whether the event was consumed. - */ - 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. - * <p> - * It checks the global system setting to ensure this has not been seen - * before. Furthermore, if the application does not have privilege to write - * 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 9030a3e..adec0a7 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 6e5c4e0..18f2878 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 40dc2a1..28c602b 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 7f87d80..bf23650 100755 --- a/core/jni/android_bluetooth_BluetoothAudioGateway.cpp +++ b/core/jni/android_bluetooth_BluetoothAudioGateway.cpp @@ -49,8 +49,6 @@ #ifdef HAVE_BLUETOOTH #include <bluetooth/bluetooth.h> -#include <bluetooth/hci.h> -#include <bluetooth/hci_lib.h> #include <bluetooth/rfcomm.h> #include <bluetooth/sco.h> #endif diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp new file mode 100644 index 0000000..59f63a8 --- /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 <utils/Log.h> +#include <utils/String8.h> + +#include "EmojiFactory.h" +#include <nativehelper/JNIHelp.h> + +#include <dlfcn.h> +// #include <pthread.h> + +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<EmojiFactory *(*)(const char*)>( + 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<EmojiFactory *(*)()>( + 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<EmojiFactory *>(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<jint>(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<EmojiFactory *>(nativeEmojiFactory); + delete factory; +} + +static jint android_emoji_EmojiFactory_getAndroidPuaFromVendorSpecificSjis( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jchar sjis) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetAndroidPuaFromVendorSpecificSjis(sjis); +} + +static jint android_emoji_EmojiFactory_getVendorSpecificSjisFromAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jint pua) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetVendorSpecificSjisFromAndroidPua(pua); +} + +static jint android_emoji_EmojiFactory_getAndroidPuaFromVendorSpecificPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jint vsu) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetAndroidPuaFromVendorSpecificPua(vsu); +} + +static jint android_emoji_EmojiFactory_getVendorSpecificPuaFromAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory, jint pua) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetVendorSpecificPuaFromAndroidPua(pua); +} + +static jint android_emoji_EmojiFactory_getMaximumVendorSpecificPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetMaximumVendorSpecificPua(); +} + +static jint android_emoji_EmojiFactory_getMinimumVendorSpecificPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetMinimumVendorSpecificPua(); +} + +static jint android_emoji_EmojiFactory_getMaximumAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(nativeEmojiFactory); + return factory->GetMaximumAndroidPua(); +} + +static jint android_emoji_EmojiFactory_getMinimumAndroidPua( + JNIEnv* env, jobject obj, jint nativeEmojiFactory) { + EmojiFactory *factory = reinterpret_cast<EmojiFactory *>(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, "<init>", + "(IZ[B)V"); + gEmojiFactory_class = make_globalref(env, "android/emoji/EmojiFactory"); + gEmojiFactory_constructorMethodID = env->GetMethodID( + gEmojiFactory_class, "<init>", "(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 c98207a..fcab813 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 5fca5d0..cb5524a 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 13d89b2..0000000 --- a/core/res/res/anim/zoom_ring_enter.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/anim/fade_in.xml -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/decelerate_interpolator"> - <scale android:fromXScale="0.75" android:toXScale="1.0" - android:fromYScale="0.75" android:toYScale="1.0" - android:pivotX="50%" android:pivotY="50%" - android:duration="75" /> - <alpha android:fromAlpha="0.0" android:toAlpha="1.0" - android:duration="75" /> -</set> diff --git a/core/res/res/anim/zoom_ring_exit.xml b/core/res/res/anim/zoom_ring_exit.xml deleted file mode 100644 index 177a4c3..0000000 --- a/core/res/res/anim/zoom_ring_exit.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/anim/fade_out.xml -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:interpolator="@anim/accelerate_interpolator"> - <scale android:fromXScale="1.0" android:toXScale="0.75" - android:fromYScale="1.0" android:toYScale="0.75" - android:pivotX="50%" android:pivotY="50%" - android:duration="75" /> - <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:duration="75"/> -</set> diff --git a/core/res/res/drawable-land/statusbar_background.png b/core/res/res/drawable-land/statusbar_background.png Binary files differindex 8ecc24c..bafa5c5 100644 --- a/core/res/res/drawable-land/statusbar_background.png +++ b/core/res/res/drawable-land/statusbar_background.png diff --git a/core/res/res/drawable/btn_circle.xml b/core/res/res/drawable/btn_circle.xml new file mode 100644 index 0000000..9208010 --- /dev/null +++ b/core/res/res/drawable/btn_circle.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_circle_normal" /> + <item android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_circle_disable" /> + <item android:state_pressed="true" + android:drawable="@drawable/btn_circle_pressed" /> + <item android:state_focused="true" android:state_enabled="true" + android:drawable="@drawable/btn_circle_selected" /> + <item android:state_enabled="true" + android:drawable="@drawable/btn_circle_normal" /> + <item android:state_focused="true" + android:drawable="@drawable/btn_circle_disable_focused" /> + <item + android:drawable="@drawable/btn_circle_disable" /> +</selector> diff --git a/core/res/res/drawable/btn_circle_disable.png b/core/res/res/drawable/btn_circle_disable.png Binary files differnew file mode 100644 index 0000000..33b74a6 --- /dev/null +++ b/core/res/res/drawable/btn_circle_disable.png diff --git a/core/res/res/drawable/btn_circle_disable_focused.png b/core/res/res/drawable/btn_circle_disable_focused.png Binary files differnew file mode 100644 index 0000000..005ad8d --- /dev/null +++ b/core/res/res/drawable/btn_circle_disable_focused.png diff --git a/core/res/res/drawable/btn_circle_longpress.png b/core/res/res/drawable/btn_circle_longpress.png Binary files differnew file mode 100644 index 0000000..f27d411 --- /dev/null +++ b/core/res/res/drawable/btn_circle_longpress.png diff --git a/core/res/res/drawable/btn_circle_normal.png b/core/res/res/drawable/btn_circle_normal.png Binary files differnew file mode 100644 index 0000000..fc5af1c --- /dev/null +++ b/core/res/res/drawable/btn_circle_normal.png diff --git a/core/res/res/drawable/btn_circle_pressed.png b/core/res/res/drawable/btn_circle_pressed.png Binary files differnew file mode 100644 index 0000000..8f40afd --- /dev/null +++ b/core/res/res/drawable/btn_circle_pressed.png diff --git a/core/res/res/drawable/btn_circle_selected.png b/core/res/res/drawable/btn_circle_selected.png Binary files differnew file mode 100644 index 0000000..c74fac2 --- /dev/null +++ b/core/res/res/drawable/btn_circle_selected.png diff --git a/core/res/res/drawable/extract_edit_text.xml b/core/res/res/drawable/extract_edit_text.xml index 9c6c4ba..c7f66f6 100644 --- a/core/res/res/drawable/extract_edit_text.xml +++ b/core/res/res/drawable/extract_edit_text.xml @@ -15,7 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:drawable="@drawable/keyboard_textfield_pressed" /> <item android:state_focused="true" android:drawable="@drawable/keyboard_textfield_selected" /> <item android:drawable="@drawable/textfield_disabled" /> </selector> diff --git a/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml b/core/res/res/drawable/ic_btn_round_more.xml index b77eaa4..82b7593 100644 --- a/core/res/res/drawable/zoom_ring_thumb_plus_arrow_rotatable.xml +++ b/core/res/res/drawable/ic_btn_round_more.xml @@ -14,9 +14,9 @@ limitations under the License. --> -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" - android:pivotY="50%" - android:fromDegrees="0" - android:toDegrees="360" - android:drawable="@drawable/zoom_ring_thumb_plus_arrow" /> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:drawable="@drawable/ic_btn_round_more_disabled" /> + <item + android:drawable="@drawable/ic_btn_round_more_normal" /> +</selector> diff --git a/core/res/res/drawable/ic_btn_round_more_disabled.png b/core/res/res/drawable/ic_btn_round_more_disabled.png Binary files differnew file mode 100644 index 0000000..1ab98c9 --- /dev/null +++ b/core/res/res/drawable/ic_btn_round_more_disabled.png diff --git a/core/res/res/drawable/ic_btn_round_more_normal.png b/core/res/res/drawable/ic_btn_round_more_normal.png Binary files differnew file mode 100644 index 0000000..ebdc55c --- /dev/null +++ b/core/res/res/drawable/ic_btn_round_more_normal.png diff --git a/core/res/res/drawable/keyboard_textfield_pressed.9.png b/core/res/res/drawable/keyboard_textfield_pressed.9.png Binary files differdeleted file mode 100644 index f4e3f10..0000000 --- a/core/res/res/drawable/keyboard_textfield_pressed.9.png +++ /dev/null diff --git a/core/res/res/drawable/stat_sys_phone_call.png b/core/res/res/drawable/stat_sys_phone_call.png Binary files differindex ad53693..c44d062 100644 --- a/core/res/res/drawable/stat_sys_phone_call.png +++ b/core/res/res/drawable/stat_sys_phone_call.png diff --git a/core/res/res/drawable/stat_sys_phone_call_bluetooth.png b/core/res/res/drawable/stat_sys_phone_call_bluetooth.png Binary files differnew file mode 100644 index 0000000..7abfd19 --- /dev/null +++ b/core/res/res/drawable/stat_sys_phone_call_bluetooth.png diff --git a/core/res/res/drawable/statusbar_background.png b/core/res/res/drawable/statusbar_background.png Binary files differindex 945ad92..25a2344 100644 --- a/core/res/res/drawable/statusbar_background.png +++ b/core/res/res/drawable/statusbar_background.png diff --git a/core/res/res/drawable/zoom_ring_arrows.png b/core/res/res/drawable/zoom_ring_arrows.png Binary files differdeleted file mode 100644 index e443de7..0000000 --- a/core/res/res/drawable/zoom_ring_arrows.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_overview_tab.9.png b/core/res/res/drawable/zoom_ring_overview_tab.9.png Binary files differdeleted file mode 100644 index d2658d8..0000000 --- a/core/res/res/drawable/zoom_ring_overview_tab.9.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_thumb.png b/core/res/res/drawable/zoom_ring_thumb.png Binary files differdeleted file mode 100644 index 1002724..0000000 --- a/core/res/res/drawable/zoom_ring_thumb.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_thumb_minus.png b/core/res/res/drawable/zoom_ring_thumb_minus.png Binary files differdeleted file mode 100644 index faed674..0000000 --- a/core/res/res/drawable/zoom_ring_thumb_minus.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png b/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png Binary files differdeleted file mode 100644 index abe1b8a..0000000 --- a/core/res/res/drawable/zoom_ring_thumb_minus_arrow.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml b/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml deleted file mode 100644 index f2d495b..0000000 --- a/core/res/res/drawable/zoom_ring_thumb_minus_arrow_rotatable.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" - android:pivotY="50%" - android:fromDegrees="0" - android:toDegrees="360" - android:drawable="@drawable/zoom_ring_thumb_minus_arrow" /> diff --git a/core/res/res/drawable/zoom_ring_thumb_plus.png b/core/res/res/drawable/zoom_ring_thumb_plus.png Binary files differdeleted file mode 100644 index ba7aff2..0000000 --- a/core/res/res/drawable/zoom_ring_thumb_plus.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png b/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png Binary files differdeleted file mode 100644 index d01ccb4..0000000 --- a/core/res/res/drawable/zoom_ring_thumb_plus_arrow.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_track.png b/core/res/res/drawable/zoom_ring_track.png Binary files differdeleted file mode 100644 index 1d5a44c..0000000 --- a/core/res/res/drawable/zoom_ring_track.png +++ /dev/null diff --git a/core/res/res/drawable/zoom_ring_track_absolute.png b/core/res/res/drawable/zoom_ring_track_absolute.png Binary files differdeleted file mode 100644 index 2b87699..0000000 --- a/core/res/res/drawable/zoom_ring_track_absolute.png +++ /dev/null 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 08931ac..0000000 --- a/core/res/res/drawable/zoom_ring_trail.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<rotate xmlns:android="http://schemas.android.com/apk/res/android" - android:pivotX="50%" - android:pivotY="50%" - android:fromDegrees="0" - android:toDegrees="360"> - - <shape - android:shape="ring" - android:innerRadius="60dip" - android:thickness="14dip" - android:useLevel="true"> - - <gradient - android:type="sweep" - android:useLevel="true" - android:startColor="#1000ceff" - android:endColor="#ffadd252" /> - - </shape> - - </rotate> diff --git a/core/res/res/layout/preferences.xml b/core/res/res/layout/preferences.xml index e6876ff..f0c2535 100644 --- a/core/res/res/layout/preferences.xml +++ b/core/res/res/layout/preferences.xml @@ -19,7 +19,8 @@ <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginRight="7dip" + android:layout_marginRight="4dip" android:layout_gravity="center_vertical" - android:src="@drawable/ic_settings_indicator_next_page" /> + 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 18a1355..81fd87d 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -65,13 +65,6 @@ <item>@drawable/spinner_dropdown_background</item> <item>@drawable/title_bar</item> <item>@drawable/title_bar_shadow</item> - <item>@drawable/zoom_ring_arrows</item> - <item>@drawable/zoom_ring_overview_tab</item> - <item>@drawable/zoom_ring_thumb</item> - <item>@drawable/zoom_ring_thumb_minus_arrow_rotatable</item> - <item>@drawable/zoom_ring_thumb_plus_arrow_rotatable</item> - <item>@drawable/zoom_ring_track</item> - <item>@drawable/zoom_ring_track_absolute</item> <!-- Visual lock screen --> <item>@drawable/indicator_code_lock_drag_direction_green_up</item> <item>@drawable/indicator_code_lock_drag_direction_red_up</item> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 30b26fa..5fa5f8d 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -371,8 +371,6 @@ <attr name="spinnerItemStyle" format="reference" /> <!-- Default MapView style. --> <attr name="mapViewStyle" format="reference" /> - <!-- Default ZoomRing style. --> - <attr name="zoomRingStyle" format="reference" /> <!-- =================== --> <!-- Preference styles --> @@ -585,29 +583,34 @@ <attr name="imeOptions"> <!-- There are no special semantics associated with this editor. --> <flag name="normal" value="0x00000000" /> - <!-- There is no special action associated with this editor. + <!-- There is no specific action associated with this editor, let the + editor come up with its own if it can. + Corresponds to + {@link android.view.inputmethod.EditorInfo#IME_ACTION_UNSPECIFIED}. --> + <flag name="actionUnspecified" value="0x00000000" /> + <!-- This editor has no action associated with it. Corresponds to {@link android.view.inputmethod.EditorInfo#IME_ACTION_NONE}. --> - <flag name="actionNone" value="0x00000000" /> + <flag name="actionNone" value="0x00000001" /> <!-- 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. {@link android.view.inputmethod.EditorInfo#IME_ACTION_GO}. --> - <flag name="actionGo" value="0x00000001" /> + <flag name="actionGo" value="0x00000002" /> <!-- 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). {@link android.view.inputmethod.EditorInfo#IME_ACTION_SEARCH}. --> - <flag name="actionSearch" value="0x00000002" /> + <flag name="actionSearch" value="0x00000003" /> <!-- The action key performs a "send" operation, delivering the text to its target. This is typically used when composing a message. {@link android.view.inputmethod.EditorInfo#IME_ACTION_SEND}. --> - <flag name="actionSend" value="0x00000003" /> + <flag name="actionSend" value="0x00000004" /> <!-- The action key performs a "next" operation, taking the user to the next field that will accept text. {@link android.view.inputmethod.EditorInfo#IME_ACTION_NEXT}. --> - <flag name="actionNext" value="0x00000004" /> + <flag name="actionNext" value="0x00000005" /> <!-- Used in conjunction with a custom action, this indicates that the action should not be available in-line as the same as a "enter" key. Typically this is @@ -1935,34 +1938,6 @@ </attr> <attr name="inputType" /> </declare-styleable> - <declare-styleable name="ZoomRing"> - <!-- Defines the drawable used as the thumb. --> - <attr name="thumbDrawable" format="reference" /> - <!-- Defines the distance of the thumb from the center of the ring. --> - <attr name="thumbDistance" format="dimension" /> - <!-- Defines the distance from the center of the ring to the beginning of the track. --> - <attr name="trackInnerRadius" format="dimension" /> - <!-- Defines the distance from the center of the ring to the end of the track. --> - <attr name="trackOuterRadius" format="dimension" /> - <!-- Defines the drawable used as a hint to show which direction is zoom in. This should be - the same size as the zoom ring's asset. It will be rotated programmatically. --> - <attr name="zoomInArrowDrawable" format="reference" /> - <!-- Defines the drawable used as a hint to show which direction is zoom out. This should be - the same size as the zoom ring's asset. It will be rotated programmatically. --> - <attr name="zoomOutArrowDrawable" format="reference" /> - <!-- Defines the drawable that is laid on top of the zoom in arrow. - For example, this could be a +. --> - <attr name="zoomInArrowHintDrawable" format="reference" /> - <!-- Defines the drawable that is laid on top of the zoom out arrow. - For example, this could be a -. --> - <attr name="zoomOutArrowHintDrawable" format="reference" /> - <!-- Defines the distance of the zoom arrow hint from the center of the ring. --> - <attr name="zoomArrowHintDistance" format="dimension" /> - <!-- Defines the offset of the zoom arrow hints from the thumb. Valid ranges are [0, 359] --> - <attr name="zoomArrowHintOffsetAngle" format="integer" /> - <!-- Defines the drawable used to hint that the zoom ring can pan its owner. --> - <attr name="panningArrowsDrawable" format="reference" /> - </declare-styleable> <declare-styleable name="PopupWindow"> <attr name="popupBackground" format="reference|color" /> </declare-styleable> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 9f4d82e..54eba62 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -144,11 +144,6 @@ <item name="windowExitAnimation">@anim/search_bar_exit</item> </style> - <!-- Standard animations for the zoom ring. --> - <style name="Animation.ZoomRing"> - <item name="windowEnterAnimation">@anim/zoom_ring_enter</item> - <item name="windowExitAnimation">@anim/zoom_ring_exit</item> - </style> <!-- Status Bar Styles --> <style name="TextAppearance.StatusBarTitle"> @@ -461,21 +456,6 @@ <item name="android:shadowRadius">2.75</item> </style> - <style name="Widget.ZoomRing"> - <item name="android:background">@android:drawable/zoom_ring_track</item> - <item name="android:thumbDrawable">@android:drawable/zoom_ring_thumb</item> - <item name="android:thumbDistance">63dip</item> - <item name="android:trackInnerRadius">43dip</item> - <item name="android:trackOuterRadius">91dip</item> - <item name="android:zoomInArrowDrawable">@android:drawable/zoom_ring_thumb_plus_arrow_rotatable</item> - <item name="android:zoomOutArrowDrawable">@android:drawable/zoom_ring_thumb_minus_arrow_rotatable</item> - <item name="android:zoomInArrowHintDrawable">@android:drawable/zoom_ring_thumb_plus</item> - <item name="android:zoomOutArrowHintDrawable">@android:drawable/zoom_ring_thumb_minus</item> - <item name="android:zoomArrowHintDistance">69dip</item> - <item name="android:zoomArrowHintOffsetAngle">33</item> - <item name="android:panningArrowsDrawable">@android:drawable/zoom_ring_arrows</item> - </style> - <!-- Text Appearances --> <eat-comment /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index bde6b2a..01c46de 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -162,7 +162,6 @@ <item name="spinnerItemStyle">@android:style/Widget.TextView.SpinnerItem</item> <item name="dropDownHintAppearance">@android:style/TextAppearance.Widget.DropDownHint</item> <item name="keyboardViewStyle">@android:style/Widget.KeyboardView</item> - <item name="zoomRingStyle">@android:style/Widget.ZoomRing</item> <!-- Preference styles --> <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item> diff --git a/data/etc/Android.mk b/data/etc/Android.mk index 4b5464f..a32d8ea 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 9e3697f..0000000 --- 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 0000000..8722983 --- /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 Binary files differindex b35ea65..738f6ae 100644 --- a/data/sounds/effects/KeypressDelete.ogg +++ b/data/sounds/effects/KeypressDelete.ogg diff --git a/data/sounds/effects/KeypressDelete.wav b/data/sounds/effects/KeypressDelete.wav Binary files differindex 5903f7f..0a2f3cc 100644 --- a/data/sounds/effects/KeypressDelete.wav +++ b/data/sounds/effects/KeypressDelete.wav diff --git a/data/sounds/effects/KeypressReturn.ogg b/data/sounds/effects/KeypressReturn.ogg Binary files differindex 1b0fd56..6c807b0 100644 --- a/data/sounds/effects/KeypressReturn.ogg +++ b/data/sounds/effects/KeypressReturn.ogg diff --git a/data/sounds/effects/KeypressReturn.wav b/data/sounds/effects/KeypressReturn.wav Binary files differindex 9558fcf..59ff13a 100644 --- a/data/sounds/effects/KeypressReturn.wav +++ b/data/sounds/effects/KeypressReturn.wav diff --git a/data/sounds/effects/KeypressSpacebar.ogg b/data/sounds/effects/KeypressSpacebar.ogg Binary files differindex 5299337..b59c8ce 100644 --- a/data/sounds/effects/KeypressSpacebar.ogg +++ b/data/sounds/effects/KeypressSpacebar.ogg diff --git a/data/sounds/effects/KeypressSpacebar.wav b/data/sounds/effects/KeypressSpacebar.wav Binary files differindex 1226a21..1f819c9 100644 --- a/data/sounds/effects/KeypressSpacebar.wav +++ b/data/sounds/effects/KeypressSpacebar.wav diff --git a/data/sounds/effects/KeypressStandard.ogg b/data/sounds/effects/KeypressStandard.ogg Binary files differindex 8a152d1..a465cf8 100644 --- a/data/sounds/effects/KeypressStandard.ogg +++ b/data/sounds/effects/KeypressStandard.ogg diff --git a/data/sounds/effects/KeypressStandard.wav b/data/sounds/effects/KeypressStandard.wav Binary files differindex 0c9aa2a..56a7911 100644 --- a/data/sounds/effects/KeypressStandard.wav +++ b/data/sounds/effects/KeypressStandard.wav diff --git a/docs/html/guide/topics/graphics/2d-graphics.jd b/docs/html/guide/topics/graphics/2d-graphics.jd index a72962e..befb018 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:</p> <p>This animation runs for just three frames. By setting the <code>android:oneshot</code> attribute of the list to <var>true</var>, it will cycle just once then stop and hold on the last frame. If it is set <var>false</var> then -the animation will loop. With this XML saved as <code>rocket_thrust.xml</p> in the <code>res/anim/</code> directory +the animation will loop. With this XML saved as <code>rocket_thrust.xml</code> in the <code>res/anim/</code> 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:</p> <pre> diff --git a/libs/audioflinger/A2dpAudioInterface.cpp b/libs/audioflinger/A2dpAudioInterface.cpp index eb00f8c..2974e32 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 a56e8a0..99614dc 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<String16>& 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 92c40e9..440778d 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<MixerThread::Track> > tracks; + SortedVector < wp<MixerThread::Track> > 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<IBinder> binder = mNotificationClients.itemAt(i).promote(); + if (binder != NULL) { + LOGV("Notifying output change to client %p", binder.get()); + sp<IAudioFlingerClient> client = interface_cast<IAudioFlingerClient> (binder); + client->a2dpEnabledChanged(mA2dpEnabled); + } + } + mNotifyA2dpChange = false; + } } #endif // WITH_A2DP @@ -236,8 +269,12 @@ status_t AudioFlinger::dumpInternals(int fd, const Vector<String16>& 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<String16>& 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<String16>& args) if (mAudioHardware) { mAudioHardware->dumpState(fd, args); } + if (locked) mLock.unlock(); } return NO_ERROR; } @@ -320,18 +365,19 @@ sp<IAudioTrack> 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<IBinder>& who) { } } -void AudioFlinger::handleOutputSwitch() -{ - if (mA2dpEnabled != mA2dpEnabledReq) - { - Mutex::Autolock _l(mLock); - - if (mA2dpEnabled != mA2dpEnabledReq) - { - mA2dpEnabled = mA2dpEnabledReq; - SortedVector < sp<MixerThread::Track> > tracks; - SortedVector < wp<MixerThread::Track> > 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<IBinder> binder = mNotificationClients.itemAt(i).promote(); - if (binder != NULL) { - LOGV("Notifying output change to client %p", binder.get()); - sp<IAudioFlingerClient> client = interface_cast<IAudioFlingerClient> (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<Track> >& 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<count ; i++) { const sp<Track>& 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::Track> AudioFlinger::MixerThread::createTrack( +// MixerThread::createTrack_l() must be called with AudioFlinger::mLock held +sp<AudioFlinger::MixerThread::Track> AudioFlinger::MixerThread::createTrack_l( const sp<AudioFlinger::Client>& client, int streamType, uint32_t sampleRate, @@ -1218,25 +1216,21 @@ sp<AudioFlinger::MixerThread::Track> AudioFlinger::MixerThread::createTrack( goto Exit; } - { - Mutex::Autolock _l(mLock); - if (mSampleRate == 0) { - LOGE("Audio driver not initialized."); - lStatus = NO_INIT; - goto Exit; - } + 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; + 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) { @@ -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<Track> >& tracks, SortedVector < wp<Track> >& 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<Track> 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<Track> 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<Track> >& tracks, SortedVector < wp<Track> >& 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<Track> 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>& track) +// addTrack_l() must be called with AudioFlinger::mLock held +status_t AudioFlinger::MixerThread::addTrack_l(const sp<Track>& 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>& 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> track, int name) +// removeTrack_l() must be called with AudioFlinger::mLock held +void AudioFlinger::MixerThread::removeTrack_l(wp<Track> track, int name) { - Mutex::Autolock _l(mLock); sp<Track> t = track.promote(); if (t!=NULL && (t->mState <= TrackBase::STOPPED)) { - remove_track_l(track, name); - } -} - -void AudioFlinger::MixerThread::remove_track_l(wp<Track> track, int name) -{ - sp<Track> 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>& track) +// destroyTrack_l() must be called with AudioFlinger::mLock held +void AudioFlinger::MixerThread::destroyTrack_l(const sp<Track>& 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<Track> 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<Track>& t) +// addActiveTrack_l() must be called with AudioFlinger::mLock held +void AudioFlinger::MixerThread::addActiveTrack_l(const wp<Track>& t) { mActiveTracks.add(t); @@ -1476,7 +1458,8 @@ void AudioFlinger::MixerThread::addActiveTrack(const wp<Track>& t) } } -void AudioFlinger::MixerThread::removeActiveTrack(const wp<Track>& t) +// removeActiveTrack_l() must be called with AudioFlinger::mLock held +void AudioFlinger::MixerThread::removeActiveTrack_l(const wp<Track>& t) { mActiveTracks.remove(t); @@ -1492,12 +1475,14 @@ void AudioFlinger::MixerThread::removeActiveTrack(const wp<Track>& 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>& mixerThread, const sp<Client>& 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>& mixerThread, const sp<Client>& client, @@ -1681,13 +1668,26 @@ AudioFlinger::MixerThread::Track::Track( AudioFlinger::MixerThread::Track::~Track() { wp<Track> 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<Track> 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>& mixerThread, const sp<Client>& 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<IAudioRecord> AudioFlinger::openRecord( uint32_t flags, status_t *status) { - sp<AudioRecordThread> thread; sp<MixerThread::RecordTrack> recordTrack; sp<RecordHandle> recordHandle; sp<Client> client; @@ -2227,7 +2229,7 @@ sp<IAudioRecord> 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<IAudioRecord> 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 and pass to record thread - 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. The record track uses one track in mHardwareMixerThread by convention. + 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 77f064b..c505336 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<IBinder>& 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<Track> createTrack( + sp<Track> createTrack_l( const sp<AudioFlinger::Client>& client, int streamType, uint32_t sampleRate, @@ -468,12 +469,10 @@ private: int frameCount, const sp<IMemory>& sharedBuffer, status_t *status); - - void wakeUp() { mWaitWorkCV.broadcast(); } - void getTracks(SortedVector < sp<Track> >& tracks, + void getTracks_l(SortedVector < sp<Track> >& tracks, SortedVector < wp<Track> >& activeTracks); - void putTracks(SortedVector < sp<Track> >& tracks, + void putTracks_l(SortedVector < sp<Track> >& tracks, SortedVector < wp<Track> >& 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>& track); - void removeTrack(wp<Track> track, int name); - void remove_track_l(wp<Track> track, int name); - void destroyTrack(const sp<Track>& track); - int getTrackName(); - void deleteTrackName(int name); - void addActiveTrack(const wp<Track>& t); - void removeActiveTrack(const wp<Track>& t); + status_t addTrack_l(const sp<Track>& track); + void removeTrack_l(wp<Track> track, int name); + void destroyTrack_l(const sp<Track>& track); + int getTrackName_l(); + void deleteTrackName_l(int name); + void addActiveTrack_l(const wp<Track>& t); + void removeActiveTrack_l(const wp<Track>& t); size_t getOutputFrameCount(); status_t dumpInternals(int fd, const Vector<String16>& args); status_t dumpTracks(int fd, const Vector<String16>& args); sp<AudioFlinger> mAudioFlinger; - mutable Mutex mLock; - mutable Condition mWaitWorkCV; SortedVector< wp<Track> > mActiveTracks; SortedVector< sp<Track> > 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<Client> > mClients; sp<MixerThread> mA2dpMixerThread; @@ -620,7 +616,7 @@ private: AudioHardwareInterface* mA2dpAudioInterface; sp<AudioRecordThread> mAudioRecordThread; bool mA2dpEnabled; - bool mA2dpEnabledReq; + bool mNotifyA2dpChange; mutable int mHardwareStatus; SortedVector< wp<IBinder> > mNotificationClients; int mForcedSpeakerCount; diff --git a/location/data/Android.mk b/location/data/Android.mk index befd792..794e6c7 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 5416629..8560593 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 5e9c488..07b43bb 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 0e88719..caba47c 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 dd94164..ee6a727 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<MediaFra return true; } - + + //Audio //Wait for PV bugs for MP3 duration @MediumTest @@ -410,8 +411,6 @@ public class MediaPlayerApiTest extends ActivityInstrumentationTestCase<MediaFra assertTrue("Play mp3 from resources", mp3Resources); } - //Bug# 1422662 - @Suppress @MediumTest public void testPrepareAsyncReset() throws Exception { boolean isReset = CodecTest.prepareAsyncReset(MediaNames.STREAM_LARGE_MP3); @@ -429,5 +428,19 @@ public class MediaPlayerApiTest extends ActivityInstrumentationTestCase<MediaFra boolean isLooping = CodecTest.isLoopingAfterReset(MediaNames.AMR); assertTrue("isLooping after reset", isLooping); } + + @LargeTest + public void testLocalMp3PrepareAsyncCallback() throws Exception { + boolean onPrepareSuccess = + CodecTest.prepareAsyncCallback(MediaNames.VIDEO_H263_AMR); + assertTrue("LocalMp3prepareAsyncCallback", onPrepareSuccess); + } + + @LargeTest + public void testStreamPrepareAsyncCallback() throws Exception { + boolean onPrepareSuccess = + CodecTest.prepareAsyncCallback(MediaNames.STREAM_H264_480_360_1411k); + assertTrue("StreamH264PrepareAsyncCallback", onPrepareSuccess); + } } diff --git a/packages/SettingsProvider/Android.mk b/packages/SettingsProvider/Android.mk index 330a673..724e988 100644 --- a/packages/SettingsProvider/Android.mk +++ b/packages/SettingsProvider/Android.mk @@ -1,7 +1,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := user development +LOCAL_MODULE_TAGS := user LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/packages/SettingsProvider/etc/Android.mk b/packages/SettingsProvider/etc/Android.mk index e3f958c..d73175f 100644 --- a/packages/SettingsProvider/etc/Android.mk +++ b/packages/SettingsProvider/etc/Android.mk @@ -21,7 +21,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := bookmarks.xml -LOCAL_MODULE_TAGS := user development +LOCAL_MODULE_TAGS := user # This will install the file in /system/etc # @@ -36,7 +36,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := favorites.xml -LOCAL_MODULE_TAGS := user development +LOCAL_MODULE_TAGS := user # This will install the file in /system/etc # diff --git a/services/Android.mk b/services/java/Android.mk index 5e912d6..5e912d6 100644 --- a/services/Android.mk +++ b/services/java/Android.mk diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index a254081..9948322 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -196,6 +196,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ClientState mCurClient; /** + * The last window token that gained focus. + */ + IBinder mCurFocusedWindow; + + /** * The input context last provided by the current client. */ IInputContext mCurInputContext; @@ -557,7 +562,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void unbindCurrentInputLocked() { + void unbindCurrentClientLocked() { if (mCurClient != null) { if (DEBUG) Log.v(TAG, "unbindCurrentInputLocked: client = " + mCurClient.client.asBinder()); @@ -658,7 +663,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (mCurClient != cs) { // If the client is changing, we need to switch over to the new // one. - unbindCurrentInputLocked(); + unbindCurrentClientLocked(); if (DEBUG) Log.v(TAG, "switching to client: client = " + cs.client.asBinder()); @@ -721,21 +726,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("Unknown id: " + mCurMethodId); } - if (mHaveConnection) { - mContext.unbindService(this); - mHaveConnection = false; - } - - if (mCurToken != null) { - try { - if (DEBUG) Log.v(TAG, "Removing window token: " + mCurToken); - mIWindowManager.removeWindowToken(mCurToken); - } catch (RemoteException e) { - } - mCurToken = null; - } - - clearCurMethod(); + unbindCurrentMethodLocked(false); mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); mCurIntent.setComponent(info.getComponent()); @@ -814,7 +805,30 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void clearCurMethod() { + void unbindCurrentMethodLocked(boolean reportToClient) { + if (mHaveConnection) { + mContext.unbindService(this); + mHaveConnection = false; + } + + if (mCurToken != null) { + try { + if (DEBUG) Log.v(TAG, "Removing window token: " + mCurToken); + mIWindowManager.removeWindowToken(mCurToken); + } catch (RemoteException e) { + } + mCurToken = null; + } + + clearCurMethodLocked(); + + if (reportToClient && mCurClient != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( + MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); + } + } + + void clearCurMethodLocked() { if (mCurMethod != null) { for (ClientState cs : mClients.values()) { cs.sessionRequested = false; @@ -831,7 +845,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " mCurIntent=" + mCurIntent); if (mCurMethod != null && mCurIntent != null && name.equals(mCurIntent.getComponent())) { - clearCurMethod(); + clearCurMethodLocked(); // We consider this to be a new bind attempt, since the system // should now try to restart the service for us. mLastBindTime = SystemClock.uptimeMillis(); @@ -871,14 +885,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } void updateFromSettingsLocked() { + // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and + // ENABLED_INPUT_METHODS is taking care of keeping them correctly in + // sync, so we will never have a DEFAULT_INPUT_METHOD that is not + // enabled. String id = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - if (id != null) { + if (id != null && id.length() > 0) { try { setInputMethodLocked(id); } catch (IllegalArgumentException e) { Log.w(TAG, "Unknown input method from prefs: " + id, e); + unbindCurrentMethodLocked(true); } + } else { + // There is no longer an input method set, so stop any current one. + unbindCurrentMethodLocked(true); } } @@ -903,7 +925,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub intent.putExtra("input_method_id", id); mContext.sendBroadcast(intent); } - unbindCurrentInputLocked(); + unbindCurrentClientLocked(); } finally { Binder.restoreCallingIdentity(ident); } @@ -1023,7 +1045,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } - public void windowGainedFocus(IInputMethodClient client, + public void windowGainedFocus(IInputMethodClient client, IBinder windowToken, boolean viewHasFocus, boolean isTextEditor, int softInputMode, boolean first, int windowFlags) { long ident = Binder.clearCallingIdentity(); @@ -1043,13 +1065,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // focus in the window manager, to allow this call to // be made before input is started in it. if (!mIWindowManager.inputMethodClientHasFocus(client)) { - Log.w(TAG, "Ignoring focus gain of: " + client); + Log.w(TAG, "Client not active, ignoring focus gain of: " + client); return; } } catch (RemoteException e) { } } + if (mCurFocusedWindow == windowToken) { + Log.w(TAG, "Window already focused, ignoring focus gain of: " + client); + return; + } + mCurFocusedWindow = windowToken; + switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: if (!isTextEditor || (softInputMode & @@ -1558,7 +1586,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mInputMethodData=" + mInputMethodData); p.println(" mCurrentMethod=" + mCurMethodId); client = mCurClient; - p.println(" mCurSeq=" + mCurSeq + " mCurClient=" + client); + p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); + p.println(" mCurFocusedWindow=" + mCurFocusedWindow); p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection + " mBoundToMethod=" + mBoundToMethod); p.println(" mCurToken=" + mCurToken); diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index eece581..e298f49 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -22,9 +22,9 @@ import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN; -import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.PendingIntent; +import android.bluetooth.BluetoothA2dp; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -1491,6 +1491,12 @@ public class WifiService extends IWifiManager.Stub { return; } mPluggedType = pluggedType; + } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) { + boolean isBluetoothPlaying = + intent.getIntExtra( + BluetoothA2dp.SINK_STATE, + BluetoothA2dp.STATE_DISCONNECTED) == BluetoothA2dp.STATE_PLAYING; + mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying); } else { return; } @@ -1603,13 +1609,11 @@ public class WifiService extends IWifiManager.Stub { private void registerForBroadcasts() { IntentFilter intentFilter = new IntentFilter(); - if (isAirplaneSensitive()) { - intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); - } intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(ACTION_DEVICE_IDLE); + intentFilter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION); mContext.registerReceiver(mReceiver, intentFilter); } diff --git a/core/jni/server/Android.mk b/services/jni/Android.mk index 2f48edf..2f48edf 100644 --- a/core/jni/server/Android.mk +++ b/services/jni/Android.mk diff --git a/core/jni/server/com_android_server_AlarmManagerService.cpp b/services/jni/com_android_server_AlarmManagerService.cpp index 1d66fb1..1d66fb1 100644 --- a/core/jni/server/com_android_server_AlarmManagerService.cpp +++ b/services/jni/com_android_server_AlarmManagerService.cpp diff --git a/core/jni/server/com_android_server_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp index 6636a97..6636a97 100644 --- a/core/jni/server/com_android_server_BatteryService.cpp +++ b/services/jni/com_android_server_BatteryService.cpp diff --git a/core/jni/server/com_android_server_HardwareService.cpp b/services/jni/com_android_server_HardwareService.cpp index ac36348..ac36348 100644 --- a/core/jni/server/com_android_server_HardwareService.cpp +++ b/services/jni/com_android_server_HardwareService.cpp diff --git a/core/jni/server/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp index 63830d5..63830d5 100644 --- a/core/jni/server/com_android_server_KeyInputQueue.cpp +++ b/services/jni/com_android_server_KeyInputQueue.cpp diff --git a/core/jni/server/com_android_server_SensorService.cpp b/services/jni/com_android_server_SensorService.cpp index 695a8a3..695a8a3 100644 --- a/core/jni/server/com_android_server_SensorService.cpp +++ b/services/jni/com_android_server_SensorService.cpp diff --git a/core/jni/server/com_android_server_SystemServer.cpp b/services/jni/com_android_server_SystemServer.cpp index ae29405..ae29405 100644 --- a/core/jni/server/com_android_server_SystemServer.cpp +++ b/services/jni/com_android_server_SystemServer.cpp diff --git a/core/jni/server/onload.cpp b/services/jni/onload.cpp index 3d68cfb..3d68cfb 100644 --- a/core/jni/server/onload.cpp +++ b/services/jni/onload.cpp diff --git a/tests/FrameworkTest/tests/src/com/android/frameworktest/layout/table/VerticalGravityTest.java b/tests/FrameworkTest/tests/src/com/android/frameworktest/layout/table/VerticalGravityTest.java index de3e68b..d731243 100644 --- a/tests/FrameworkTest/tests/src/com/android/frameworktest/layout/table/VerticalGravityTest.java +++ b/tests/FrameworkTest/tests/src/com/android/frameworktest/layout/table/VerticalGravityTest.java @@ -21,6 +21,7 @@ import com.android.frameworktest.R; import android.test.ActivityInstrumentationTestCase; import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.Suppress; import android.test.ViewAsserts; import android.view.View; @@ -73,6 +74,7 @@ public class VerticalGravityTest extends ActivityInstrumentationTestCase<Vertica ViewAsserts.assertVerticalCenterAligned(mReference2, mCenter); } + @Suppress @MediumTest public void testBottomGravity() throws Exception { ViewAsserts.assertBottomAligned(mReference3, mBottom); diff --git a/tests/StatusBar/Android.mk b/tests/StatusBar/Android.mk index 44f5099..18fcad0 100644 --- a/tests/StatusBar/Android.mk +++ b/tests/StatusBar/Android.mk @@ -1,7 +1,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_MODULE_TAGS := test +LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 2a47a71..3851ac0 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -117,6 +117,16 @@ public class WifiNative { * @return Whether the mode was successfully set. */ public native static boolean setBluetoothCoexistenceModeCommand(int mode); + + /** + * Enable or disable Bluetooth coexistence scan mode. When this mode is on, + * some of the low-level scan parameters used by the driver are changed to + * reduce interference with A2DP streaming. + * + * @param isSet whether to enable or disable this mode + * @return {@code true} if the command succeeded, {@code false} otherwise. + */ + public native static boolean setBluetoothCoexistenceScanModeCommand(boolean setCoexScanMode); public native static boolean saveConfigCommand(); diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index f0009be..452a8fa 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -38,6 +38,7 @@ import android.util.Config; import android.app.Notification; import android.app.PendingIntent; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothA2dp; import android.content.ContentResolver; import android.content.Intent; import android.content.Context; @@ -244,6 +245,10 @@ public class WifiStateTracker extends NetworkStateTracker { private int mRunState; private boolean mIsScanOnly; + + private BluetoothA2dp mBluetoothA2dp; + + private boolean mBluetoothScanMode; private String mInterfaceName; private static String LS = System.getProperty("line.separator"); @@ -577,6 +582,30 @@ public class WifiStateTracker extends NetworkStateTracker { } } + /** + * Enable or disable Bluetooth coexistence scan mode. When this mode is on, + * some of the low-level scan parameters used by the driver are changed to + * reduce interference with A2DP streaming. + * + * @param isBluetoothPlaying whether to enable or disable this mode + */ + public synchronized void setBluetoothScanMode(boolean isBluetoothPlaying) { + WifiNative.setBluetoothCoexistenceScanModeCommand(isBluetoothPlaying); + } + + private void checkIsBluetoothPlaying() { + boolean isBluetoothPlaying = false; + List<String> 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: |