From 15ab3eae2ec3d73b3e8aa60b33ae41445bf83f4b Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Fri, 20 Feb 2009 07:38:31 -0800 Subject: [PATCH] auto import from //branches/cupcake/...@132569 --- api/current.xml | 22 + core/java/android/app/Activity.java | 13 +- core/java/android/hardware/SensorManager.java | 18 + .../inputmethodservice/SoftInputWindow.java | 3 +- core/java/android/preference/Preference.java | 7 - .../android/preference/PreferenceScreen.java | 1 - core/java/android/provider/Checkin.java | 5 + core/java/android/provider/Gmail.java | 10 + core/java/android/provider/MediaStore.java | 6 + .../server/BluetoothDeviceService.java | 21 +- .../android/server/BluetoothEventLoop.java | 3 +- core/java/android/view/KeyEvent.java | 4 +- .../view/OrientationEventListener.java | 20 +- core/java/android/view/SurfaceHolder.java | 2 +- core/java/android/view/ViewRoot.java | 11 +- .../android/view/WindowManagerPolicy.java | 17 +- .../view/WindowOrientationListener.java | 20 +- core/java/android/webkit/WebSettings.java | 6 + core/java/android/webkit/WebView.java | 116 ++++- core/java/android/webkit/WebViewCore.java | 9 +- .../webkit/gears/GearsPluginSettings.java | 95 ---- .../webkit/gears/HtmlDialogAndroid.java | 174 ------- .../webkit/gears/IGearsDialogService.java | 107 ---- core/java/android/widget/Adapter.java | 2 +- core/java/android/widget/TextView.java | 16 +- core/java/android/widget/ZoomRing.java | 113 ++-- .../android/widget/ZoomRingController.java | 82 ++- .../internal/widget/TextProgressBar.java | 1 + .../android/util/GoogleWebContentHelper.java | 2 +- core/res/res/color/tertiary_text_dark.xml | 2 +- core/res/res/drawable/sym_action_add.png | Bin 0 -> 930 bytes .../res/drawable/zoom_ring_track_absolute.png | Bin 16759 -> 19660 bytes .../google_web_content_helper_layout.xml | 2 +- core/res/res/values/attrs.xml | 1 + include/ui/KeycodeLabels.h | 4 +- media/libmedia/AudioRecord.cpp | 2 +- media/libmedia/ToneGenerator.cpp | 19 +- .../functional/MediaAudioTrackTest.java | 125 ++++- .../functional/TonesAutoTest.java | 2 +- .../MediaRecorderPrepareStateUnitTest.java | 2 +- ...diaRecorderSetOutputFileStateUnitTest.java | 20 +- .../MediaRecorderStateUnitTestTemplate.java | 7 + .../android/server/PowerManagerService.java | 54 +- .../android/server/WindowManagerService.java | 17 +- .../internal/telephony/WapPushOverSms.java | 211 ++++++++ .../internal/telephony/WspTypeDecoder.java | 344 +++++++++++++ .../internal/telephony/gsm/SMSDispatcher.java | 157 +----- .../telephony/gsm/ServiceStateTracker.java | 83 +-- tests/AndroidTests/AndroidManifest.xml | 2 - .../com/android/unit_tests/BluetoothTest.java | 484 ------------------ tools/aapt/Package.cpp | 10 + wifi/java/android/net/wifi/WifiMonitor.java | 64 +-- 52 files changed, 1255 insertions(+), 1263 deletions(-) delete mode 100644 core/java/android/webkit/gears/GearsPluginSettings.java delete mode 100644 core/java/android/webkit/gears/HtmlDialogAndroid.java delete mode 100644 core/java/android/webkit/gears/IGearsDialogService.java create mode 100644 core/res/res/drawable/sym_action_add.png create mode 100644 telephony/java/com/android/internal/telephony/WapPushOverSms.java create mode 100644 telephony/java/com/android/internal/telephony/WspTypeDecoder.java delete mode 100644 tests/AndroidTests/src/com/android/unit_tests/BluetoothTest.java diff --git a/api/current.xml b/api/current.xml index aa6471b7bbc26..9ccb8f7945ecb 100644 --- a/api/current.xml +++ b/api/current.xml @@ -118963,6 +118963,17 @@ visibility="public" > + + + + Derived classes must call through to the super class's * implementation of this method. If they do not, an exception will be @@ -907,9 +907,9 @@ public class Activity extends ContextThemeWrapper } /** - * Called after {@link #onStart} when the current activity is being + * Called after {@link #onStop} when the current activity is being * re-displayed to the user (the user has navigated back to it). It will - * be followed by {@link #onResume}. + * be followed by {@link #onStart} and then {@link #onResume}. * *

For activities that are using raw {@link Cursor} objects (instead of * creating them through @@ -923,6 +923,7 @@ public class Activity extends ContextThemeWrapper * thrown.

* * @see #onStop + * @see #onStart * @see #onResume */ protected void onRestart() { @@ -1220,7 +1221,7 @@ public class Activity extends ContextThemeWrapper /** * Called when you are no longer visible to the user. You will next - * receive either {@link #onStart}, {@link #onDestroy}, or nothing, + * receive either {@link #onRestart}, {@link #onDestroy}, or nothing, * depending on later user activity. * *

Note that this method may never be called, in low memory situations diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 3981f2784ef40..e232c2cee0435 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -620,6 +620,9 @@ public class SensorManager extends IRotationWatcher.Stub */ @Deprecated public boolean registerListener(SensorListener listener, int sensors, int rate) { + if (listener == null) { + return false; + } boolean result = false; result = registerLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER, listener, sensors, rate) || result; @@ -638,6 +641,9 @@ public class SensorManager extends IRotationWatcher.Stub private boolean registerLegacyListener(int legacyType, int type, SensorListener listener, int sensors, int rate) { + if (listener == null) { + return false; + } boolean result = false; // Are we activating this legacy sensor? if ((sensors & legacyType) != 0) { @@ -693,6 +699,9 @@ public class SensorManager extends IRotationWatcher.Stub private void unregisterLegacyListener(int legacyType, int type, SensorListener listener, int sensors) { + if (listener == null) { + return; + } // do we know about this listener? LegacyListener legacyListener = null; synchronized (mLegacyListenersMap) { @@ -800,6 +809,9 @@ public class SensorManager extends IRotationWatcher.Stub */ public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate, Handler handler) { + if (listener == null || sensor == null) { + return false; + } boolean result; int delay = -1; switch (rate) { @@ -856,6 +868,9 @@ public class SensorManager extends IRotationWatcher.Stub } private void unregisterListener(Object listener, Sensor sensor) { + if (listener == null || sensor == null) { + return; + } try { synchronized (sListeners) { final int size = sListeners.size(); @@ -881,6 +896,9 @@ public class SensorManager extends IRotationWatcher.Stub } private void unregisterListener(Object listener) { + if (listener == null) { + return; + } try { synchronized (sListeners) { final int size = sListeners.size(); diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index da67c6d168a6e..c37845f02b632 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -141,7 +141,8 @@ class SoftInputWindow extends Dialog { lp.gravity = Gravity.BOTTOM; lp.width = -1; // Let the input method window's orientation follow sensor based rotation - lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; + // Turn this off for now, it is very problematic. + //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; getWindow().setAttributes(lp); getWindow().setFlags( diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 3820f28563262..a2554388651ad 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -667,7 +667,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * {@link SharedPreferences}. This should be unique for the package. * * @param key The key for the preference. - * @see #getId() */ public void setKey(String key) { mKey = key; @@ -1460,7 +1459,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * @param container The Bundle in which to save the instance of this Preference. * * @see #restoreHierarchyState - * @see #dispatchSaveInstanceState * @see #onSaveInstanceState */ public void saveHierarchyState(Bundle container) { @@ -1474,7 +1472,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * * @param container The Bundle in which to save the instance of this Preference. * - * @see #dispatchRestoreInstanceState * @see #saveHierarchyState * @see #onSaveInstanceState */ @@ -1503,7 +1500,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * The default implementation returns null. * @see #onRestoreInstanceState * @see #saveHierarchyState - * @see #dispatchSaveInstanceState */ protected Parcelable onSaveInstanceState() { mBaseMethodCalled = true; @@ -1516,7 +1512,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * @param container The Bundle that holds the previously saved state. * * @see #saveHierarchyState - * @see #dispatchRestoreInstanceState * @see #onRestoreInstanceState */ public void restoreHierarchyState(Bundle container) { @@ -1530,7 +1525,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * not want to save state for their children. * * @param container The Bundle that holds the previously saved state. - * @see #dispatchSaveInstanceState * @see #restoreHierarchyState * @see #onRestoreInstanceState */ @@ -1557,7 +1551,6 @@ public class Preference implements Comparable, OnDependencyChangeLis * {@link #onSaveInstanceState}. * @see #onSaveInstanceState * @see #restoreHierarchyState - * @see #dispatchRestoreInstanceState */ protected void onRestoreInstanceState(Parcelable state) { mBaseMethodCalled = true; diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java index 9929b963755e0..5353b531dd068 100644 --- a/core/java/android/preference/PreferenceScreen.java +++ b/core/java/android/preference/PreferenceScreen.java @@ -100,7 +100,6 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi * * @return An adapter that provides the {@link Preference} contained in this * {@link PreferenceScreen}. - * @see PreferenceGroupAdapter */ public ListAdapter getRootAdapter() { if (mRootAdapter == null) { diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java index 688e19a83c76d..0cdac53525bd8 100644 --- a/core/java/android/provider/Checkin.java +++ b/core/java/android/provider/Checkin.java @@ -126,6 +126,11 @@ public final class Checkin { /** Valid tag values. Extend as necessary for your needs. */ public enum Tag { + 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/provider/Gmail.java b/core/java/android/provider/Gmail.java index 253dd4a6d22e1..5b3c2234ded1c 100644 --- a/core/java/android/provider/Gmail.java +++ b/core/java/android/provider/Gmail.java @@ -1477,10 +1477,20 @@ public final class Gmail { LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL, LABEL_SPAM, LABEL_TRASH); + + private static final Set USER_MEANINGFUL_SYSTEM_LABELS_SET = + Sets.newHashSet( + SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray( + new String[]{})); + public static List getSortedUserMeaningfulSystemLabels() { return SORTED_USER_MEANINGFUL_SYSTEM_LABELS; } + public static Set getUserMeaningfulSystemLabelsSet() { + return USER_MEANINGFUL_SYSTEM_LABELS_SET; + } + /** * If you are ever tempted to remove outbox or draft from this set make sure you have a * way to stop draft and outbox messages from getting purged before they are sent to the diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 2897b4e7645d8..b91bc9d56f2af 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -157,6 +157,12 @@ public final class MediaStore */ public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; + /** + * Specify the maximum allowed size. + * @hide + */ + public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit"; + /** * The name of the Intent-extra used to indicate a content resolver Uri to be used to * store the requested image or video. diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 86d5a1e73bd35..fa53a601077e2 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -47,8 +47,8 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; public class BluetoothDeviceService extends IBluetoothDevice.Stub { @@ -121,7 +121,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { * Bring down bluetooth. Returns true on success. * * @param saveSetting If true, disable BT in settings - * + * */ public synchronized boolean disable(boolean saveSetting) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, @@ -172,7 +172,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { * This turns on/off the underlying hardware. * * @param saveSetting If true, enable BT in settings - * + * * @return True on success (so far), guaranteeing the callback with be * notified when complete. */ @@ -275,6 +275,14 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { private final HashMap mState = new HashMap(); private final HashMap mPinAttempt = new HashMap(); private final ArrayList mAutoPairingFailures = new ArrayList(); + // List of all the vendor_id prefix of Bluetooth addresses for which + // auto pairing is not attempted + private final ArrayList mAutoPairingBlacklisted = + new ArrayList(Arrays.asList( + "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", //ALPS + "00:21:4F", "00:23:06", "00:24:33", "00:A0:79", // ALPS + "00:0E:6D", "00:13:E0", "00:21:E8", "00:60:57"// Murata for Prius 2007 + )); public synchronized void loadBondState() { if (!mIsEnabled) { @@ -322,6 +330,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { mContext.sendBroadcast(intent, BLUETOOTH_PERM); } + public boolean isAutoPairingBlacklisted(String address) { + for (String blacklistAddress : mAutoPairingBlacklisted) { + if (address.startsWith(blacklistAddress)) return true; + } + return false; + } + public synchronized int getBondState(String address) { Integer state = mState.get(address); if (state == null) { diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 187ec2cf3a0b3..8b09583492d4b 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -312,7 +312,8 @@ class BluetoothEventLoop { case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: - if (!mBluetoothService.getBondState().hasAutoPairingFailed(address)) { + if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) && + !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) { mBluetoothService.getBondState().attempt(address); mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); return; diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index d5434b6a1a8bb..d6ea91c4b5e37 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -117,7 +117,8 @@ public class KeyEvent implements Parcelable { public static final int KEYCODE_PREVIOUSSONG = 88; public static final int KEYCODE_REWIND = 89; public static final int KEYCODE_FORWARD = 90; - private static final int LAST_KEYCODE = KEYCODE_FORWARD; + public static final int KEYCODE_MUTE = 91; + private static final int LAST_KEYCODE = KEYCODE_MUTE; // NOTE: If you add a new keycode here you must also add it to: // isSystem() @@ -502,6 +503,7 @@ public class KeyEvent implements Parcelable { case KEYCODE_ENDCALL: case KEYCODE_VOLUME_UP: case KEYCODE_VOLUME_DOWN: + case KEYCODE_MUTE: case KEYCODE_POWER: case KEYCODE_HEADSETHOOK: case KEYCODE_PLAYPAUSE: diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java index cddec11a35638..391ba1e794c30 100755 --- a/core/java/android/view/OrientationEventListener.java +++ b/core/java/android/view/OrientationEventListener.java @@ -69,8 +69,11 @@ public abstract class OrientationEventListener { public OrientationEventListener(Context context, int rate) { mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; - mSensorEventListener = new SensorEventListenerImpl(); mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (mSensor != null) { + // Create listener only if sensors do exist + mSensorEventListener = new SensorEventListenerImpl(); + } } void registerListener(OrientationListener lis) { @@ -82,6 +85,10 @@ public abstract class OrientationEventListener { * {@link #onOrientationChanged} when the device orientation changes. */ public void enable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Not enabled"); + return; + } if (mEnabled == false) { if (localLOGV) Log.d(TAG, "OrientationEventListener enabled"); mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); @@ -93,6 +100,10 @@ public abstract class OrientationEventListener { * Disables the OrientationEventListener. */ public void disable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Invalid disable"); + return; + } if (mEnabled == true) { if (localLOGV) Log.d(TAG, "OrientationEventListener disabled"); mSensorManager.unregisterListener(mSensorEventListener); @@ -138,6 +149,13 @@ public abstract class OrientationEventListener { } } + + /* + * Returns true if sensor is enabled and false otherwise + */ + public boolean canDetectOrientation() { + return mSensor != null; + } /** * Called when the orientation of the device has changed. diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java index 21a72e7184fe7..3d0dda3db48be 100644 --- a/core/java/android/view/SurfaceHolder.java +++ b/core/java/android/view/SurfaceHolder.java @@ -240,7 +240,7 @@ public interface SurfaceHolder { * in particular there is no guarantee that the content of the Surface * will remain unchanged when lockCanvas() is called again. * - * @see android.view.SurfaceHolder.lockCanvas + * @see #lockCanvas() * * @param canvas The Canvas previously returned by lockCanvas(). */ diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index db8829f9bc027..3cfaf1b4c27cf 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -1817,6 +1817,11 @@ public final class ViewRoot extends Handler implements ViewParent, if (event != null) { event.recycle(); } + // If we reach this, we delivered a trackball event to mView and + // mView consumed it. Because we will not translate the trackball + // event into a key event, touch mode will not exit, so we exit + // touch mode here. + ensureTouchMode(false); //noinspection ReturnInsideFinallyBlock return; } @@ -2140,7 +2145,7 @@ public final class ViewRoot extends Handler implements ViewParent, } boolean keyHandled = mView.dispatchKeyEvent(event); - if ((!keyHandled && isDown) || (action == KeyEvent.ACTION_MULTIPLE)) { + if (!keyHandled && isDown) { int direction = 0; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: @@ -2541,7 +2546,7 @@ public final class ViewRoot extends Handler implements ViewParent, * for us to consider the user to be doing fast trackball movements, * and thus apply an acceleration. */ - static final long FAST_MOVE_TIME = 100; + static final long FAST_MOVE_TIME = 150; /** * Scaling factor to the time (in milliseconds) between events to how @@ -2549,7 +2554,7 @@ public final class ViewRoot extends Handler implements ViewParent, * is < FAST_MOVE_TIME this multiplies the acceleration; when > * FAST_MOVE_TIME it divides it. */ - static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/50); + static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40); float position; float absPosition; diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 051f823fdfdea..0f15b176344f2 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -129,13 +129,21 @@ public interface WindowManagerPolicy { Rect contentFrame, Rect visibleFrame); /** - * Retrieve the current frame of the window. Must be called with the - * window manager lock held. + * Retrieve the current frame of the window that has been assigned by + * the window manager. Must be called with the window manager lock held. * * @return Rect The rectangle holding the window frame. */ public Rect getFrameLw(); + /** + * Retrieve the current frame of the window that is actually shown. + * Must be called with the window manager lock held. + * + * @return Rect The rectangle holding the shown window frame. + */ + public Rect getShownFrameLw(); + /** * Retrieve the frame of the display that this window was last * laid out in. Must be called with the @@ -273,9 +281,12 @@ public interface WindowManagerPolicy { * false, this is based on the currently requested * frame, which any current animation will be moving * towards. + * @param onlyOpaque If true, this will only pass if the window is + * also opaque. * @return Returns true if the window is both full screen and opaque */ - public boolean fillsScreenLw(int width, int height, boolean shownFrame); + public boolean fillsScreenLw(int width, int height, boolean shownFrame, + boolean onlyOpaque); /** * Returns true if this window has been shown on screen at some time in diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index 4aa3f7aa783bc..58779321f79a0 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -74,8 +74,11 @@ public abstract class WindowOrientationListener { public WindowOrientationListener(Context context, int rate) { mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; - mSensorEventListener = new SensorEventListenerImpl(); mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (mSensor != null) { + // Create listener only if sensors do exist + mSensorEventListener = new SensorEventListenerImpl(); + } } /** @@ -83,6 +86,10 @@ public abstract class WindowOrientationListener { * {@link #onOrientationChanged} when the device orientation changes. */ public void enable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Not enabled"); + return; + } if (mEnabled == false) { if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); @@ -94,6 +101,10 @@ public abstract class WindowOrientationListener { * Disables the WindowOrientationListener. */ public void disable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Invalid disable"); + return; + } if (mEnabled == true) { if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); mSensorManager.unregisterListener(mSensorEventListener); @@ -145,6 +156,13 @@ public abstract class WindowOrientationListener { } } + /* + * Returns true if sensor is enabled and false otherwise + */ + public boolean canDetectOrientation() { + return mSensor != null; + } + /** * Called when the orientation of the device has changed. * orientation parameter is in degrees, ranging from 0 to 359. diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 5c470cf1bf15f..4e2b2ab6df393 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -20,6 +20,8 @@ import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Message; +import android.provider.Checkin; + import java.lang.SecurityException; import android.content.pm.PackageManager; @@ -408,6 +410,10 @@ public class WebSettings { * @see WebSettings.TextSize */ public synchronized void setTextSize(TextSize t) { + if (WebView.mLogEvent && mTextSize != t ) { + Checkin.updateStats(mContext.getContentResolver(), + Checkin.Stats.Tag.BROWSER_TEXT_SIZE_CHANGE, 1, 0.0); + } mTextSize = t; postSync(); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 4d9a8fbafda55..c59a5fcfbf8d7 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -38,11 +38,13 @@ import android.os.Handler; import android.os.Message; import android.os.ServiceManager; import android.os.SystemClock; +import android.provider.Checkin; import android.text.IClipboard; import android.text.Selection; import android.text.Spannable; import android.util.AttributeSet; import android.util.Config; +import android.util.EventLog; import android.util.Log; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -450,6 +452,13 @@ public class WebView extends AbsoluteLayout // Used to match key downs and key ups private boolean mGotKeyDown; + /* package */ static boolean mLogEvent = true; + private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101; + private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102; + + // for event log + private long mLastTouchUpTime = 0; + /** * URI scheme for telephone number */ @@ -556,7 +565,11 @@ public class WebView extends AbsoluteLayout private ZoomRingController.OnZoomListener mZoomListener = new ZoomRingController.OnZoomListener() { - + + private float mClockwiseBound; + private float mCounterClockwiseBound; + private float mStartScale; + public void onCenter(int x, int y) { // Don't translate when the control is invoked, hence we do nothing // in this callback @@ -564,6 +577,10 @@ public class WebView extends AbsoluteLayout public void onBeginPan() { setZoomOverviewVisible(false); + if (mLogEvent) { + Checkin.updateStats(mContext.getContentResolver(), + Checkin.Stats.Tag.BROWSER_ZOOM_RING_DRAG, 1, 0.0); + } } public boolean onPan(int deltaX, int deltaY) { @@ -576,12 +593,25 @@ public class WebView extends AbsoluteLayout 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) { + 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) { + } else if (mActualScale < 1 && mCounterClockwiseBound > 0) { angle = (float) Math.round(ZOOM_RING_STEPS * (1 - mActualScale) / (1 - mMinZoomScale)) / ZOOM_RING_STEPS; @@ -590,16 +620,26 @@ public class WebView extends AbsoluteLayout // 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); + } } } 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); } @@ -616,21 +656,21 @@ public class WebView extends AbsoluteLayout mZoomCenterY = (float) centerY; float scale = 1.0f; - if (curAngle > (float) Math.PI) - curAngle -= (float) 2 * Math.PI; - if (curAngle > 0) { - if (curAngle >= MAX_ZOOM_RING_ANGLE) { + // 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 < 0) { - if (curAngle <= -MAX_ZOOM_RING_ANGLE) { + } else { + if (curAngle <= mClockwiseBound) { scale = mMaxZoomScale; } else { - scale = 1 + (float) Math.round(-curAngle + scale = 1 + (float) Math.round( + ((float) 2 * Math.PI - curAngle) / ZOOM_RING_ANGLE_UNIT) / ZOOM_RING_STEPS * (mMaxZoomScale - 1); } @@ -687,12 +727,11 @@ public class WebView extends AbsoluteLayout mScroller = new Scroller(context); mZoomRingController = new ZoomRingController(context, this); mZoomRingController.setResetThumbAutomatically(false); - mZoomRingController.setThumbClockwiseBound( - (float) (2 * Math.PI - MAX_ZOOM_RING_ANGLE)); - mZoomRingController.setThumbCounterclockwiseBound(MAX_ZOOM_RING_ANGLE); mZoomRingController.setCallback(mZoomListener); mZoomRingController.setZoomRingTrack( com.android.internal.R.drawable.zoom_ring_track_absolute); + mZoomRingController.setPannerAcceleration(160); + mZoomRingController.setPannerStartAcceleratingDuration(700); createZoomRingOverviewTab(); } @@ -730,6 +769,10 @@ public class WebView extends AbsoluteLayout 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(); }}); @@ -3468,7 +3511,7 @@ public class WebView extends AbsoluteLayout // update mMinZoomScale if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { boolean atMin = Math.abs(mActualScale - mMinZoomScale) < 0.01f; - mMinZoomScale = (float) getViewWidth() / mMinContentWidth; + mMinZoomScale = (float) getViewWidth() / mContentWidth; if (atMin) { // if the WebView was at the minimum zoom scale, keep it. e,g., // the WebView was at the minimum zoom scale at the portrait @@ -3535,7 +3578,8 @@ public class WebView extends AbsoluteLayout return false; } - if (mShowZoomRingTutorial && mMinZoomScale < mMaxZoomScale) { + if (mShowZoomRingTutorial && getSettings().supportZoom() + && (mMaxZoomScale - mMinZoomScale) > ZOOM_RING_STEPS * 0.01f) { ZoomRingController.showZoomTutorialOnce(mContext); mShowZoomRingTutorial = false; mPrivateHandler.sendMessageDelayed(mPrivateHandler @@ -3616,10 +3660,18 @@ public class WebView extends AbsoluteLayout mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); mZoomRingController.setVisible(true); mInZoomTapDragMode = true; + if (mLogEvent) { + EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, + (eventTime - mLastTouchUpTime), eventTime); + } return mZoomRingController.handleDoubleTapEvent(ev); } else { mTouchMode = TOUCH_INIT_MODE; mPreventDrag = mForwardTouchEvents; + if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { + EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, + (eventTime - mLastTouchUpTime), eventTime); + } } // don't trigger the link if zoom ring is visible if (mTouchMode == TOUCH_INIT_MODE @@ -3783,17 +3835,23 @@ public class WebView extends AbsoluteLayout break; } case MotionEvent.ACTION_UP: { + mLastTouchUpTime = eventTime; switch (mTouchMode) { case TOUCH_INIT_MODE: // tap if (mZoomRingController.isVisible()) { - // don't trigger the link if zoom ring is visible + // 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() - && (mMinZoomScale < mMaxZoomScale)) { + if (getSettings().supportZoom()) { mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP), + .obtainMessage(RELEASE_SINGLE_TAP, + new Boolean(true)), DOUBLE_TAP_TIMEOUT); } else { // do short press now @@ -3841,7 +3899,8 @@ public class WebView extends AbsoluteLayout // as tap instead of short press. mTouchMode = TOUCH_INIT_MODE; mPrivateHandler.sendMessageDelayed(mPrivateHandler - .obtainMessage(RELEASE_SINGLE_TAP), + .obtainMessage(RELEASE_SINGLE_TAP, + new Boolean(true)), DOUBLE_TAP_TIMEOUT); } else { mTouchMode = TOUCH_DONE_MODE; @@ -3963,6 +4022,7 @@ public class WebView extends AbsoluteLayout + " time=" + time + " mLastFocusTime=" + mLastFocusTime); } + if (isInTouchMode()) requestFocusFromTouch(); return false; // let common code in onKeyDown at it } if (ev.getAction() == MotionEvent.ACTION_UP) { @@ -4399,7 +4459,12 @@ public class WebView extends AbsoluteLayout int contentX = viewToContent((int) mLastTouchX + mScrollX); int contentY = viewToContent((int) mLastTouchY + mScrollY); int contentSize = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - nativeMotionUp(contentX, contentY, contentSize, true); + if (nativeMotionUp(contentX, contentY, contentSize, true)) { + if (mLogEvent) { + Checkin.updateStats(mContext.getContentResolver(), + Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0); + } + } if (nativeUpdateFocusNode() && !mFocusNode.mIsTextField && !mFocusNode.mIsTextArea) { playSoundEffect(SoundEffectConstants.CLICK); @@ -4619,7 +4684,9 @@ public class WebView extends AbsoluteLayout } case RELEASE_SINGLE_TAP: { mTouchMode = TOUCH_DONE_MODE; - doShortPress(); + if ((Boolean)msg.obj) { + doShortPress(); + } break; } case SWITCH_TO_ENTER: @@ -4671,7 +4738,7 @@ public class WebView extends AbsoluteLayout mMinContentWidth = msg.arg1; if (mMinContentWidth > MAX_FLOAT_CONTENT_WIDTH) { mMinZoomScale = (float) getViewWidth() - / mMinContentWidth; + / draw.mWidthHeight.x; } // We update the layout (i.e. request a layout from the // view system) if the last view size that we sent to @@ -5236,7 +5303,8 @@ public class WebView extends AbsoluteLayout private native Rect nativeGetNavBounds(); private native void nativeInstrumentReport(); private native void nativeMarkNodeInvalid(int node); - private native void nativeMotionUp(int x, int y, int slop, boolean isClick); + // return true if the page has been scrolled + private native boolean nativeMotionUp(int x, int y, int slop, boolean isClick); // returns false if it handled the key private native boolean nativeMoveFocus(int keyCode, int count, boolean noScroll); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 45113ab000b7f..a7261c5ca844d 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1197,6 +1197,7 @@ final class WebViewCore { Log.w(LOGTAG, "skip viewSizeChanged as w is 0"); return; } + // negative scale indicate that WebCore should reuse the current scale float scale = (float) viewWidth / w; if (mSettings.getUseWideViewPort() && (w < mViewportWidth || mViewportWidth == -1)) { @@ -1224,7 +1225,7 @@ final class WebViewCore { // keep the same width and screen width so that there is // no reflow when zoom-out width = minContentWidth; - screenWidth = Math.min(screenWidth, viewWidth); + screenWidth = Math.min(screenWidth, Math.abs(viewWidth)); } else { width = Math.max(w, minContentWidth); } @@ -1536,11 +1537,11 @@ final class WebViewCore { // white space in the GMail which uses WebView for message view. if (mWebView != null && mWebView.mHeightCanMeasure) { mWebView.mLastHeightSent = 0; - // Send a negative scale to indicate that WebCore should reuse the - // current scale + // Send a negative screen width to indicate that WebCore should + // reuse the current scale mEventHub.sendMessage(Message.obtain(null, EventHub.VIEW_SIZE_CHANGED, mWebView.mLastWidthSent, - mWebView.mLastHeightSent, new Integer(-1))); + mWebView.mLastHeightSent, -mWebView.mLastWidthSent)); } mBrowserFrame.didFirstLayout(); diff --git a/core/java/android/webkit/gears/GearsPluginSettings.java b/core/java/android/webkit/gears/GearsPluginSettings.java deleted file mode 100644 index d36d3fb2d584b..0000000000000 --- a/core/java/android/webkit/gears/GearsPluginSettings.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2008 The Android Open Source Project -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// 3. Neither the name of Google Inc. nor the names of its contributors may be -// used to endorse or promote products derived from this software without -// specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package android.webkit.gears; - -import android.content.ComponentName; -import android.content.Context; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.util.Log; -import android.webkit.Plugin; -import android.webkit.Plugin.PreferencesClickHandler; - -/** - * Simple bridge class intercepting the click in the - * browser plugin list and calling the Gears settings - * dialog. - */ -public class GearsPluginSettings { - - private static final String TAG = "Gears-J-GearsPluginSettings"; - private Context context; - - public GearsPluginSettings(Plugin plugin) { - plugin.setClickHandler(new ClickHandler()); - } - - /** - * We do not need to call the dialog synchronously here (doing so - * actually cause a lot of problems as the main message loop is also - * blocked), which is why we simply call it via a thread. - */ - private class ClickHandler implements PreferencesClickHandler { - public void handleClickEvent(Context aContext) { - context = aContext; - Thread startService = new Thread(new StartService()); - startService.run(); - } - } - - private static native void runSettingsDialog(Context c); - - /** - * StartService is the runnable we use to open the dialog. - * We bind the service to serviceConnection; upon - * onServiceConnected the dialog will be called from the - * native side using the runSettingsDialog method. - */ - private class StartService implements Runnable { - public void run() { - HtmlDialogAndroid.bindToService(context, serviceConnection); - } - } - - /** - * ServiceConnection instance. - * onServiceConnected is called upon connection with the service; - * we can then safely open the dialog. - */ - private ServiceConnection serviceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - IGearsDialogService gearsDialogService = - IGearsDialogService.Stub.asInterface(service); - HtmlDialogAndroid.setGearsDialogService(gearsDialogService); - runSettingsDialog(context); - context.unbindService(serviceConnection); - HtmlDialogAndroid.setGearsDialogService(null); - } - public void onServiceDisconnected(ComponentName className) { - HtmlDialogAndroid.setGearsDialogService(null); - } - }; -} diff --git a/core/java/android/webkit/gears/HtmlDialogAndroid.java b/core/java/android/webkit/gears/HtmlDialogAndroid.java deleted file mode 100644 index 6209ab91394e1..0000000000000 --- a/core/java/android/webkit/gears/HtmlDialogAndroid.java +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2008 The Android Open Source Project -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// 3. Neither the name of Google Inc. nor the names of its contributors may be -// used to endorse or promote products derived from this software without -// specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -package android.webkit.gears; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; -import android.webkit.CacheManager; - -import java.io.FileInputStream; -import java.io.IOException; - -/** - * Utility class to call a modal HTML dialog on Android - */ -public class HtmlDialogAndroid { - - private static final String TAG = "Gears-J-HtmlDialog"; - private static final String DIALOG_PACKAGE = "com.android.browser"; - private static final String DIALOG_SERVICE = DIALOG_PACKAGE - + ".GearsDialogService"; - private static final String DIALOG_INTERFACE = DIALOG_PACKAGE - + ".IGearsDialogService"; - - private static IGearsDialogService gearsDialogService; - - public static void setGearsDialogService(IGearsDialogService service) { - gearsDialogService = service; - } - - /** - * Bind to the GearsDialogService. - */ - public static boolean bindToService(Context context, - ServiceConnection serviceConnection) { - Intent dialogIntent = new Intent(); - dialogIntent.setClassName(DIALOG_PACKAGE, DIALOG_SERVICE); - dialogIntent.setAction(DIALOG_INTERFACE); - return context.bindService(dialogIntent, serviceConnection, - Context.BIND_AUTO_CREATE); - } - - /** - * Bind to the GearsDialogService synchronously. - * The service is started using our own defaultServiceConnection - * handler, and we wait until the handler notifies us. - */ - public void synchronousBindToService(Context context) { - try { - if (bindToService(context, defaultServiceConnection)) { - if (gearsDialogService == null) { - synchronized(defaultServiceConnection) { - defaultServiceConnection.wait(3000); // timeout after 3s - } - } - } - } catch (InterruptedException e) { - Log.e(TAG, "exception: " + e); - } - } - - /** - * Read the HTML content from the disk - */ - public String readHTML(String filePath) { - FileInputStream inputStream = null; - String content = ""; - try { - inputStream = new FileInputStream(filePath); - StringBuffer out = new StringBuffer(); - byte[] buffer = new byte[4096]; - for (int n; (n = inputStream.read(buffer)) != -1;) { - out.append(new String(buffer, 0, n)); - } - content = out.toString(); - } catch (IOException e) { - Log.e(TAG, "exception: " + e); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - Log.e(TAG, "exception: " + e); - } - } - } - return content; - } - - /** - * Open an HTML dialog synchronously and waits for its completion. - * The dialog is accessed through the GearsDialogService provided by - * the Android Browser. - * We can be called either directly, and then gearsDialogService will - * not be set and we will bind to the service synchronously, and unbind - * after calling the service, or called indirectly via GearsPluginSettings. - * In the latter case, GearsPluginSettings does the binding/unbinding. - */ - public String showDialog(Context context, String htmlFilePath, - String arguments) { - - CacheManager.endCacheTransaction(); - - String ret = null; - boolean synchronousCall = false; - if (gearsDialogService == null) { - synchronousCall = true; - synchronousBindToService(context); - } - - try { - if (gearsDialogService != null) { - String htmlContent = readHTML(htmlFilePath); - if (htmlContent.length() > 0) { - ret = gearsDialogService.showDialog(htmlContent, arguments, - !synchronousCall); - } - } else { - Log.e(TAG, "Could not connect to the GearsDialogService!"); - } - if (synchronousCall) { - context.unbindService(defaultServiceConnection); - gearsDialogService = null; - } - } catch (RemoteException e) { - Log.e(TAG, "remote exception: " + e); - gearsDialogService = null; - } - - CacheManager.startCacheTransaction(); - - return ret; - } - - private ServiceConnection defaultServiceConnection = - new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - synchronized (defaultServiceConnection) { - gearsDialogService = IGearsDialogService.Stub.asInterface(service); - defaultServiceConnection.notify(); - } - } - public void onServiceDisconnected(ComponentName className) { - gearsDialogService = null; - } - }; -} diff --git a/core/java/android/webkit/gears/IGearsDialogService.java b/core/java/android/webkit/gears/IGearsDialogService.java deleted file mode 100644 index 82a3bd97f2300..0000000000000 --- a/core/java/android/webkit/gears/IGearsDialogService.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * This file is auto-generated. DO NOT MODIFY. - * Original file: android.webkit.gears/IGearsDialogService.aidl - */ -package android.webkit.gears; -import java.lang.String; -import android.os.RemoteException; -import android.os.IBinder; -import android.os.IInterface; -import android.os.Binder; -import android.os.Parcel; -public interface IGearsDialogService extends android.os.IInterface -{ -/** Local-side IPC implementation stub class. */ -public static abstract class Stub extends android.os.Binder implements android.webkit.gears.IGearsDialogService -{ -private static final java.lang.String DESCRIPTOR = "com.android.browser.IGearsDialogService"; -/** Construct the stub at attach it to the interface. */ -public Stub() -{ -this.attachInterface(this, DESCRIPTOR); -} -/** - * Cast an IBinder object into an IGearsDialogService interface, - * generating a proxy if needed. - */ -public static android.webkit.gears.IGearsDialogService asInterface(android.os.IBinder obj) -{ -if ((obj==null)) { -return null; -} -android.webkit.gears.IGearsDialogService in = (android.webkit.gears.IGearsDialogService)obj.queryLocalInterface(DESCRIPTOR); -if ((in!=null)) { -return in; -} -return new android.webkit.gears.IGearsDialogService.Stub.Proxy(obj); -} -public android.os.IBinder asBinder() -{ -return this; -} -public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException -{ -switch (code) -{ -case INTERFACE_TRANSACTION: -{ -reply.writeString(DESCRIPTOR); -return true; -} -case TRANSACTION_showDialog: -{ -data.enforceInterface(DESCRIPTOR); -java.lang.String _arg0; -_arg0 = data.readString(); -java.lang.String _arg1; -_arg1 = data.readString(); -boolean _arg2; -_arg2 = (0!=data.readInt()); -java.lang.String _result = this.showDialog(_arg0, _arg1, _arg2); -reply.writeNoException(); -reply.writeString(_result); -return true; -} -} -return super.onTransact(code, data, reply, flags); -} -private static class Proxy implements android.webkit.gears.IGearsDialogService -{ -private android.os.IBinder mRemote; -Proxy(android.os.IBinder remote) -{ -mRemote = remote; -} -public android.os.IBinder asBinder() -{ -return mRemote; -} -public java.lang.String getInterfaceDescriptor() -{ -return DESCRIPTOR; -} -public java.lang.String showDialog(java.lang.String htmlContent, java.lang.String dialogArguments, boolean inSettings) throws android.os.RemoteException -{ -android.os.Parcel _data = android.os.Parcel.obtain(); -android.os.Parcel _reply = android.os.Parcel.obtain(); -java.lang.String _result; -try { -_data.writeInterfaceToken(DESCRIPTOR); -_data.writeString(htmlContent); -_data.writeString(dialogArguments); -_data.writeInt(((inSettings)?(1):(0))); -mRemote.transact(Stub.TRANSACTION_showDialog, _data, _reply, 0); -_reply.readException(); -_result = _reply.readString(); -} -finally { -_reply.recycle(); -_data.recycle(); -} -return _result; -} -} -static final int TRANSACTION_showDialog = (IBinder.FIRST_CALL_TRANSACTION + 0); -} -public java.lang.String showDialog(java.lang.String htmlContent, java.lang.String dialogArguments, boolean inSettings) throws android.os.RemoteException; -} diff --git a/core/java/android/widget/Adapter.java b/core/java/android/widget/Adapter.java index e952dd5ed0104..f2b3e2ac917fe 100644 --- a/core/java/android/widget/Adapter.java +++ b/core/java/android/widget/Adapter.java @@ -116,7 +116,7 @@ public interface Adapter { * can be converted to the other in {@link #getView}. Note: Integers must be in the * range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can * also be returned. - * @see IGNORE_ITEM_VIEW_TYPE + * @see #IGNORE_ITEM_VIEW_TYPE */ int getItemViewType(int position); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index bdc54ffa02f7d..c852be5fd3631 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4132,8 +4132,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener removeParcelableSpans(content, start, end); content.replace(start, end, text.text); } - Selection.setSelection((Spannable)getText(), - text.selectionStart, text.selectionEnd); + + // Now set the selection position... make sure it is in range, to + // avoid crashes. If this is a partial update, it is possible that + // the underlying text may have changed, causing us problems here. + // Also we just don't want to trust clients to do the right thing. + Spannable sp = (Spannable)getText(); + final int N = sp.length(); + int start = text.selectionStart; + if (start < 0) start = 0; + else if (start > N) start = N; + int end = text.selectionEnd; + if (end < 0) end = 0; + else if (end > N) end = N; + Selection.setSelection(sp, start, end); } /** diff --git a/core/java/android/widget/ZoomRing.java b/core/java/android/widget/ZoomRing.java index 22881b3bab49f..a29e1a0ec3834 100644 --- a/core/java/android/widget/ZoomRing.java +++ b/core/java/android/widget/ZoomRing.java @@ -84,6 +84,10 @@ public class ZoomRing extends View { private Drawable mThumbPlusArrowDrawable; /** Shown beneath the thumb if we can still zoom out. */ private Drawable mThumbMinusArrowDrawable; + private static final int THUMB_ARROW_PLUS = 1 << 0; + private static final int THUMB_ARROW_MINUS = 1 << 1; + /** Bitwise-OR of {@link #THUMB_ARROW_MINUS} and {@link #THUMB_ARROW_PLUS} */ + private int mThumbArrowsToDraw; private static final int THUMB_ARROWS_FADE_DURATION = 300; private long mThumbArrowsFadeStartTime; private int mThumbArrowsAlpha = 255; @@ -166,7 +170,7 @@ public class ZoomRing extends View { // TODO: add padding to drawable setBackgroundResource(R.drawable.zoom_ring_track); // TODO get from style - setRingBounds(30, Integer.MAX_VALUE); + setRingBounds(43, Integer.MAX_VALUE); mThumbHalfHeight = mThumbDrawable.getIntrinsicHeight() / 2; mThumbHalfWidth = mThumbDrawable.getIntrinsicWidth() / 2; @@ -276,7 +280,7 @@ public class ZoomRing extends View { thumbCenterX + mThumbHalfWidth, thumbCenterY + mThumbHalfHeight); - if (mThumbArrowsAlpha > 0) { + if (mThumbArrowsToDraw > 0) { setThumbArrowsAngle(angle); } @@ -420,12 +424,15 @@ public class ZoomRing extends View { switch (action) { case MotionEvent.ACTION_DOWN: - mCallback.onUserInteractionStarted(); - if (time - mPreviousUpTime <= DOUBLE_TAP_DISMISS_TIMEOUT) { mCallback.onZoomRingDismissed(true); + onTouchUp(time); + + // Dismissing, so halt here + return true; } + mCallback.onUserInteractionStarted(); mPreviousDownX = x; mPreviousDownY = y; resetState(); @@ -441,23 +448,7 @@ public class ZoomRing extends View { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: - if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { - mCallback.onZoomRingSetMovableHintVisible(false); - if (mMode == MODE_MOVE_ZOOM_RING) { - mCallback.onZoomRingMovingStopped(); - } - } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG || - mMode == MODE_WAITING_FOR_DRAG_THUMB) { - onThumbDragStopped(); - - if (mMode == MODE_DRAG_THUMB) { - // Animate back to a tick - setThumbAngleAnimated(mPreviousCallbackAngle, 0); - } - } - - mPreviousUpTime = time; - mCallback.onUserInteractionStopped(); + onTouchUp(time); return true; default: @@ -524,10 +515,9 @@ public class ZoomRing extends View { deltaThumbAndTick = getDelta(mThumbAngle, tickAngle, !oldDirectionIsCcw); boundAngle = getBoundIfExceeds(mThumbAngle, deltaThumbAndTick); if (boundAngle != Integer.MIN_VALUE) { - Log - .d( - TAG, - "Tapped somewhere where the shortest distance goes through a bound, but then the opposite direction also went through a bound!"); + // Not allowed to be here, it is between two bounds + mMode = MODE_IGNORE_UNTIL_UP; + return true; } } } @@ -574,6 +564,26 @@ public class ZoomRing extends View { return true; } + + private void onTouchUp(long time) { + if (mMode == MODE_MOVE_ZOOM_RING || mMode == MODE_WAITING_FOR_MOVE_ZOOM_RING) { + mCallback.onZoomRingSetMovableHintVisible(false); + if (mMode == MODE_MOVE_ZOOM_RING) { + mCallback.onZoomRingMovingStopped(); + } + } else if (mMode == MODE_DRAG_THUMB || mMode == MODE_TAP_DRAG || + mMode == MODE_WAITING_FOR_DRAG_THUMB) { + onThumbDragStopped(); + + if (mMode == MODE_DRAG_THUMB) { + // Animate back to a tick + setThumbAngleAnimated(mPreviousCallbackAngle, 0); + } + } + + mPreviousUpTime = time; + mCallback.onUserInteractionStopped(); + } private boolean isDeltaInBounds(int startAngle, int deltaAngle) { return getBoundIfExceeds(startAngle, deltaAngle) == Integer.MIN_VALUE; @@ -766,9 +776,11 @@ public class ZoomRing extends View { } } - int deltaAngle = getDelta(mThumbAngle, touchAngle, useDirection, ccw); - mAcculumalatedTrailAngle += Math.toDegrees(deltaAngle / (double) RADIAN_INT_MULTIPLIER); - + 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); @@ -851,15 +863,10 @@ public class ZoomRing extends View { if (DRAW_TRAIL) { mTrail.draw(canvas); } - - // If we aren't near the bounds, draw the corresponding arrows - int callbackAngle = mPreviousCallbackAngle; - if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR || - callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) { + if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) { mThumbPlusArrowDrawable.draw(canvas); } - if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR || - callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) { + if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) { mThumbMinusArrowDrawable.draw(canvas); } mThumbDrawable.draw(canvas); @@ -875,8 +882,21 @@ public class ZoomRing extends View { public void setThumbArrowsVisible(boolean visible) { if (visible) { mThumbArrowsAlpha = 255; - mThumbPlusArrowDrawable.setAlpha(255); - mThumbMinusArrowDrawable.setAlpha(255); + int callbackAngle = mPreviousCallbackAngle; + if (callbackAngle < mThumbCwBound - RADIAN_INT_ERROR || + callbackAngle > mThumbCwBound + RADIAN_INT_ERROR) { + mThumbPlusArrowDrawable.setAlpha(255); + mThumbArrowsToDraw |= THUMB_ARROW_PLUS; + } else { + mThumbArrowsToDraw &= ~THUMB_ARROW_PLUS; + } + if (callbackAngle < mThumbCcwBound - RADIAN_INT_ERROR || + callbackAngle > mThumbCcwBound + RADIAN_INT_ERROR) { + mThumbMinusArrowDrawable.setAlpha(255); + 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) @@ -886,17 +906,24 @@ public class ZoomRing extends View { } private void onThumbArrowsFadeTick() { - if (mThumbArrowsAlpha <= 0) return; + if (mThumbArrowsAlpha <= 0) { + mThumbArrowsToDraw = 0; + return; + } mThumbArrowsAlpha = (int) (255 - (255 * (SystemClock.elapsedRealtime() - mThumbArrowsFadeStartTime) / THUMB_ARROWS_FADE_DURATION)); if (mThumbArrowsAlpha < 0) mThumbArrowsAlpha = 0; - mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha); - mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha); - invalidateDrawable(mThumbPlusArrowDrawable); - invalidateDrawable(mThumbMinusArrowDrawable); - + if ((mThumbArrowsToDraw & THUMB_ARROW_PLUS) != 0) { + mThumbPlusArrowDrawable.setAlpha(mThumbArrowsAlpha); + invalidateDrawable(mThumbPlusArrowDrawable); + } + if ((mThumbArrowsToDraw & THUMB_ARROW_MINUS) != 0) { + mThumbMinusArrowDrawable.setAlpha(mThumbArrowsAlpha); + invalidateDrawable(mThumbMinusArrowDrawable); + } + if (!mHandler.hasMessages(MSG_THUMB_ARROWS_FADE_TICK)) { mHandler.sendEmptyMessage(MSG_THUMB_ARROWS_FADE_TICK); } diff --git a/core/java/android/widget/ZoomRingController.java b/core/java/android/widget/ZoomRingController.java index 31074b6402698..2e97fdafebade 100644 --- a/core/java/android/widget/ZoomRingController.java +++ b/core/java/android/widget/ZoomRingController.java @@ -57,7 +57,7 @@ import android.view.animation.DecelerateInterpolator; public class ZoomRingController implements ZoomRing.OnZoomRingCallback, View.OnTouchListener, View.OnKeyListener { - private static final int ZOOM_RING_RADIUS_INSET = 10; + private static final int ZOOM_RING_RADIUS_INSET = 24; private static final int ZOOM_RING_RECENTERING_DURATION = 500; @@ -79,7 +79,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, private static final int MAX_PAN_GAP = 20; private static final int MAX_INITIATE_PAN_GAP = 10; // TODO view config - private static final int INITIATE_PAN_DELAY = 400; + private static final int INITIATE_PAN_DELAY = 300; private static final String SETTING_NAME_SHOWN_TOAST = "shown_zoom_ring_toast"; @@ -146,7 +146,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, private int mZoomRingHeight; /** Invokes panning of owner view if the zoom ring is touching an edge. */ - private Panner mPanner = new Panner(); + private Panner mPanner; private long mTouchingEdgeStartTime; private boolean mPanningEnabledForThisInteraction; @@ -241,6 +241,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, mContext = context; mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mPanner = new Panner(); mOwnerView = ownerView; mZoomRing = new ZoomRing(context); @@ -389,6 +390,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, }; } + mPanningArrows.setAnimation(null); + mHandler.post(mPostedVisibleInitializer); // Handle configuration changes when visible @@ -409,12 +412,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, } 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); } @@ -464,6 +468,9 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, public boolean handleDoubleTapEvent(MotionEvent event) { 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(); @@ -493,6 +500,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, mViewConfig.getScaledTouchSlop()) { mZoomRing.setTapDragMode(true, x, y); mTouchMode = TOUCH_MODE_FORWARDING_FOR_TAP_DRAG; + setTouchTargetView(mZoomRing); } return true; @@ -587,7 +595,6 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, public void onZoomRingMovingStarted() { mScroller.abortAnimation(); - mPanningEnabledForThisInteraction = false; mTouchingEdgeStartTime = 0; if (mCallback != null) { mCallback.onBeginPan(); @@ -658,6 +665,8 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, if (!horizontalPanning) { // Neither are panning, reset any timer to start pan mode mTouchingEdgeStartTime = 0; + mPanningEnabledForThisInteraction = false; + mPanner.stop(); } } } @@ -752,6 +761,7 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, // 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; } @@ -763,16 +773,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, switch (action) { case MotionEvent.ACTION_DOWN: - targetView = mTouchTargetView = - getViewForTouch((int) event.getRawX(), (int) event.getRawY()); - if (targetView != null) { - targetView.getLocationInWindow(mTouchTargetLocationInWindow); - } + targetView = getViewForTouch((int) event.getRawX(), (int) event.getRawY()); + setTouchTargetView(targetView); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTouchTargetView = null; + setTouchTargetView(null); break; } @@ -799,6 +806,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, } } + 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. * @@ -950,6 +964,22 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, } } + public void setPannerStartVelocity(float startVelocity) { + mPanner.mStartVelocity = startVelocity; + } + + public void setPannerAcceleration(float acceleration) { + mPanner.mAcceleration = acceleration; + } + + public void setPannerMaxVelocity(float maxVelocity) { + mPanner.mMaxVelocity = maxVelocity; + } + + 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; @@ -967,6 +997,13 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, /** 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) { @@ -1013,11 +1050,12 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, boolean firstRun = mPreviousCallbackTime == 0; long curTime = SystemClock.elapsedRealtime(); - int panAmount = getPanAmount(mStartTime, mPreviousCallbackTime, curTime); + int panAmount = getPanAmount(mPreviousCallbackTime, curTime); mPreviousCallbackTime = curTime; if (firstRun) { mStartTime = curTime; + mVelocity = mStartVelocity; } else { int panX = panAmount * mHorizontalStrength / 100; int panY = panAmount * mVerticalStrength / 100; @@ -1030,12 +1068,22 @@ public class ZoomRingController implements ZoomRing.OnZoomRingCallback, mUiHandler.postDelayed(this, RUN_DELAY); } - // TODO make setter for this value so zoom clients can have different pan rates, if they want - private static final int PAN_VELOCITY_PX_S = 30; - private int getPanAmount(long startTime, long previousTime, long currentTime) { - return (int) ((currentTime - previousTime) * PAN_VELOCITY_PX_S / 100); + 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; } + } + + public interface OnZoomListener { void onBeginDrag(); diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java index d068865634723..5bf4601bdff1a 100644 --- a/core/java/com/android/internal/widget/TextProgressBar.java +++ b/core/java/com/android/internal/widget/TextProgressBar.java @@ -104,6 +104,7 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick * @param durationBase Use the {@link SystemClock#elapsedRealtime} time * base. */ + @android.view.RemotableViewMethod public void setDurationBase(long durationBase) { mDurationBase = durationBase; diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java index 7500ec3b0db9e..291142008934f 100644 --- a/core/java/com/google/android/util/GoogleWebContentHelper.java +++ b/core/java/com/google/android/util/GoogleWebContentHelper.java @@ -206,7 +206,7 @@ public class GoogleWebContentHelper { WebSettings settings = mWebView.getSettings(); settings.setCacheMode(WebSettings.LOAD_NO_CACHE); - mProgressBar = mLayout.findViewById(com.android.internal.R.id.progress); + mProgressBar = mLayout.findViewById(com.android.internal.R.id.progressContainer); TextView message = (TextView) mProgressBar.findViewById(com.android.internal.R.id.message); message.setText(com.android.internal.R.string.googlewebcontenthelper_loading); diff --git a/core/res/res/color/tertiary_text_dark.xml b/core/res/res/color/tertiary_text_dark.xml index 7e61fc89d8a51..7ce3580f36c9f 100644 --- a/core/res/res/color/tertiary_text_dark.xml +++ b/core/res/res/color/tertiary_text_dark.xml @@ -18,7 +18,7 @@  - + diff --git a/core/res/res/drawable/sym_action_add.png b/core/res/res/drawable/sym_action_add.png new file mode 100644 index 0000000000000000000000000000000000000000..af637b3be9d552e5d594057a115b773bf4b4f4c9 GIT binary patch literal 930 zcmV;T16}-yP)vfqpqY zK0em(?(Vh$lnjT%+R)ID{NCww#Qpuf4MR>Mk;p+Rm9oAB`1JHtYcv}B(P%V1GczM5 zCnv?o$cP980&<`7?RHz-+}wz2wJKVzRwfpU{a#;Rw?7R`P>>OnC!f3bDMMN6psv1wA-GLGnYrCc$s;&)|K&5kb^k|M>Y%Q^>loZ5nYoJp zP$(oh=1LB^sxdY;CPBFW`1mOIxpGhkB9g@8=7+$ALP3KPjoi_4a^~>5rwo8Wd5@8K zOgG`Mx5Mw0r4H(%&ehdbyC*P&bw|1DF+!Q2pP%1-D>{ipf=0N86pO`Q&|#=dcXa9A zf8OMnUU4d}*c?iwQs1qR|58TDD}5h^8rE7AP#$xlo{giUqupk+DS1OS=QlPsqzBn- z)74F!dD&;#5o9LsKK|v7RfMsdpRS4=wx(V-}D81b#CpKz2O`lTrGszTwY$b zKXvD#Ai`xLqcd!cqoboPcT6(XBKeJbPW#MIrWdHMtIzfIH9Hy`eiAa83cb@R3337> z4i*;|zvk-m)<9b%|LVB278Vx%-(~OTLjMEocl;G#0Md~WU_S;M1& literal 0 HcmV?d00001 diff --git a/core/res/res/drawable/zoom_ring_track_absolute.png b/core/res/res/drawable/zoom_ring_track_absolute.png index 6b38c53431f433f067898424f364c75834280e11..0195b9eb70ea7b5a2c3ae4c6969f1d4b3419d892 100644 GIT binary patch literal 19660 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 diff --git a/core/res/res/layout/google_web_content_helper_layout.xml b/core/res/res/layout/google_web_content_helper_layout.xml index 74096212781b4..40f84bf8f3da7 100644 --- a/core/res/res/layout/google_web_content_helper_layout.xml +++ b/core/res/res/layout/google_web_content_helper_layout.xml @@ -20,7 +20,7 @@ + diff --git a/include/ui/KeycodeLabels.h b/include/ui/KeycodeLabels.h index 53c118872814b..efa6d2bdd3c33 100644 --- a/include/ui/KeycodeLabels.h +++ b/include/ui/KeycodeLabels.h @@ -113,6 +113,7 @@ static const KeycodeLabel KEYCODES[] = { { "PREVIOUSSONG", 88 }, { "REWIND", 89 }, { "FORWARD", 90 }, + { "MUTE", 91 }, // NOTE: If you add a new keycode here you must also add it to: // (enum KeyCode, in this file) @@ -216,7 +217,8 @@ typedef enum KeyCode { kKeyCodeNextSong = 87, kKeyCodePreviousSong = 88, kKeyCodeRewind = 89, - kKeyCodeForward = 90 + kKeyCodeForward = 90, + kKeyCodeMute = 91 } KeyCode; static const KeycodeLabel FLAGS[] = { diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index 3d39181922f1f..e833c85a7890c 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -400,7 +400,7 @@ status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) uint32_t u = cblk->user; uint32_t bufferEnd = cblk->userBase + cblk->frameCount; - if (u + framesReady > bufferEnd) { + if (u + framesReq > bufferEnd) { framesReq = bufferEnd - u; } diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp index 7fafc562894ec..5416629b8e10a 100644 --- a/media/libmedia/ToneGenerator.cpp +++ b/media/libmedia/ToneGenerator.cpp @@ -175,27 +175,28 @@ bool ToneGenerator::startTone(int toneType) { if (mState == TONE_INIT) { if (prepareWave()) { LOGV("Immediate start, time %d\n", (unsigned int)(systemTime()/1000000)); - + lResult = true; mState = TONE_STARTING; mLock.unlock(); mpAudioTrack->start(); mLock.lock(); if (mState == TONE_STARTING) { + LOGV("Wait for start callback"); if (mWaitCbkCond.waitRelative(mLock, seconds(1)) != NO_ERROR) { LOGE("--- Immediate start timed out"); mState = TONE_IDLE; + lResult = false; } } - - if (mState == TONE_PLAYING) - lResult = true; + } else { + mState == TONE_IDLE; } } else { LOGV("Delayed start\n"); mState = TONE_RESTARTING; if (mWaitCbkCond.waitRelative(mLock, seconds(1)) == NO_ERROR) { - if (mState != TONE_INIT) { + if (mState != TONE_IDLE) { lResult = true; } LOGV("cond received"); @@ -206,7 +207,8 @@ bool ToneGenerator::startTone(int toneType) { } mLock.unlock(); - LOGV("Tone started, time %d\n", (unsigned int)(systemTime()/1000000)); + LOGV_IF(lResult, "Tone started, time %d\n", (unsigned int)(systemTime()/1000000)); + LOGW_IF(!lResult, "Tone start failed!!!, time %d\n", (unsigned int)(systemTime()/1000000)); return lResult; } @@ -457,8 +459,11 @@ audioCallback_EndLoop: if (lpToneGen->prepareWave()) { lpToneGen->mState = TONE_STARTING; } else { - lpToneGen->mState = TONE_INIT; + LOGW("Cbk restarting prepareWave() failed\n"); + lpToneGen->mState = TONE_IDLE; lpToneGen->mpAudioTrack->stop(); + // Force loop exit + lNumSmp = 0; } lSignal = true; break; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioTrackTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioTrackTest.java index ae6a834fa66f2..b6a0848aafa92 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioTrackTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioTrackTest.java @@ -19,6 +19,8 @@ package com.android.mediaframeworktest.functional; import com.android.mediaframeworktest.MediaFrameworkTest; import com.android.mediaframeworktest.MediaNames; +import android.media.AudioFormat; +import android.media.AudioManager; import android.media.AudioTrack; import android.content.Context; import android.test.ActivityInstrumentationTestCase2; @@ -47,12 +49,127 @@ public class MediaAudioTrackTest extends ActivityInstrumentationTestCase2= screenWidth diff --git a/telephony/java/com/android/internal/telephony/WapPushOverSms.java b/telephony/java/com/android/internal/telephony/WapPushOverSms.java new file mode 100644 index 0000000000000..66fa943d2079c --- /dev/null +++ b/telephony/java/com/android/internal/telephony/WapPushOverSms.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.content.Intent; +import android.provider.Telephony.Sms.Intents; +import android.util.Config; +import android.util.Log; +import com.android.internal.telephony.gsm.GSMPhone; +import com.android.internal.telephony.gsm.SimUtils; + + +/** + * WAP push handler class. + * + * @hide + */ +public class WapPushOverSms { + private static final String LOG_TAG = "WAP PUSH"; + + private final GSMPhone mPhone; + private WspTypeDecoder pduDecoder; + + + public WapPushOverSms(GSMPhone phone) { + mPhone = phone; + } + + /** + * Dispatches inbound messages that are in the WAP PDU format. See + * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format. + * + * @param pdu The WAP PDU, made up of one or more SMS PDUs + */ + public void dispatchWapPdu(byte[] pdu) { + + if (Config.LOGD) Log.d(LOG_TAG, "Rx: " + SimUtils.bytesToHexString(pdu)); + + int index = 0; + int transactionId = pdu[index++] & 0xFF; + int pduType = pdu[index++] & 0xFF; + int headerLength = 0; + + if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) && + (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { + if (Config.LOGD) Log.w(LOG_TAG, "Received non-PUSH WAP PDU. Type = " + pduType); + return; + } + + pduDecoder = new WspTypeDecoder(pdu); + + /** + * Parse HeaderLen(unsigned integer). + * From wap-230-wsp-20010705-a section 8.1.2 + * The maximum size of a uintvar is 32 bits. + * So it will be encoded in no more than 5 octets. + */ + if (pduDecoder.decodeUintvarInteger(index) == false) { + if (Config.LOGD) Log.w(LOG_TAG, "Received PDU. Header Length error."); + return; + } + headerLength = (int)pduDecoder.getValue32(); + index += pduDecoder.getDecodedDataLength(); + + int headerStartIndex = index; + + /** + * Parse Content-Type. + * From wap-230-wsp-20010705-a section 8.4.2.24 + * + * Content-type-value = Constrained-media | Content-general-form + * Content-general-form = Value-length Media-type + * Media-type = (Well-known-media | Extension-Media) *(Parameter) + * Value-length = Short-length | (Length-quote Length) + * Short-length = (octet <= WAP_PDU_SHORT_LENGTH_MAX) + * Length-quote = (WAP_PDU_LENGTH_QUOTE) + * Length = Uintvar-integer + */ + if (pduDecoder.decodeContentType(index) == false) { + if (Config.LOGD) Log.w(LOG_TAG, "Received PDU. Header Content-Type error."); + return; + } + int binaryContentType; + String mimeType = pduDecoder.getValueString(); + if (mimeType == null) { + binaryContentType = (int)pduDecoder.getValue32(); + switch (binaryContentType) { + case WspTypeDecoder.CONTENT_TYPE_B_DRM_RIGHTS_XML: + mimeType = WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_XML; + break; + case WspTypeDecoder.CONTENT_TYPE_B_DRM_RIGHTS_WBXML: + mimeType = WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_WBXML; + break; + case WspTypeDecoder.CONTENT_TYPE_B_PUSH_SI: + mimeType = WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_SI; + break; + case WspTypeDecoder.CONTENT_TYPE_B_PUSH_SL: + mimeType = WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_SL; + break; + case WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO: + mimeType = WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_CO; + break; + case WspTypeDecoder.CONTENT_TYPE_B_MMS: + mimeType = WspTypeDecoder.CONTENT_MIME_TYPE_B_MMS; + break; + default: + if (Config.LOGD) { + Log.w(LOG_TAG, + "Received PDU. Unsupported Content-Type = " + binaryContentType); + } + return; + } + } else { + if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_XML)) { + binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_DRM_RIGHTS_XML; + } else if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_WBXML)) { + binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_DRM_RIGHTS_WBXML; + } else if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_SI)) { + binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_PUSH_SI; + } else if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_SL)) { + binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_PUSH_SL; + } else if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_CO)) { + binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO; + } else if (mimeType.equals(WspTypeDecoder.CONTENT_MIME_TYPE_B_MMS)) { + binaryContentType = WspTypeDecoder.CONTENT_TYPE_B_MMS; + } else { + if (Config.LOGD) Log.w(LOG_TAG, "Received PDU. Unknown Content-Type = " + mimeType); + return; + } + } + index += pduDecoder.getDecodedDataLength(); + + int dataIndex = headerStartIndex + headerLength; + boolean dispatchedByApplication = false; + switch (binaryContentType) { + case WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO: + dispatchWapPdu_PushCO(pdu, transactionId, pduType); + dispatchedByApplication = true; + break; + case WspTypeDecoder.CONTENT_TYPE_B_MMS: + dispatchWapPdu_MMS(pdu, transactionId, pduType, dataIndex); + dispatchedByApplication = true; + break; + default: + break; + } + if (dispatchedByApplication == false) { + dispatchWapPdu_default(pdu, transactionId, pduType, mimeType, dataIndex); + } + } + + + + private void dispatchWapPdu_default( + byte[] pdu, int transactionId, int pduType, String mimeType, int dataIndex) { + byte[] data; + + data = new byte[pdu.length - dataIndex]; + System.arraycopy(pdu, dataIndex, data, 0, data.length); + + Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION); + intent.setType(mimeType); + intent.putExtra("transactionId", transactionId); + intent.putExtra("pduType", pduType); + intent.putExtra("data", data); + + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_WAP_PUSH"); + } + + private void dispatchWapPdu_PushCO(byte[] pdu, int transactionId, int pduType) { + Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION); + intent.setType(WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_CO); + intent.putExtra("transactionId", transactionId); + intent.putExtra("pduType", pduType); + intent.putExtra("data", pdu); + + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_WAP_PUSH"); + } + + private void dispatchWapPdu_MMS(byte[] pdu, int transactionId, int pduType, int dataIndex) { + byte[] data; + + data = new byte[pdu.length - dataIndex]; + System.arraycopy(pdu, dataIndex, data, 0, data.length); + + Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION); + intent.setType(WspTypeDecoder.CONTENT_MIME_TYPE_B_MMS); + intent.putExtra("transactionId", transactionId); + intent.putExtra("pduType", pduType); + intent.putExtra("data", data); + + mPhone.getContext().sendBroadcast( + intent, "android.permission.RECEIVE_MMS"); + } +} diff --git a/telephony/java/com/android/internal/telephony/WspTypeDecoder.java b/telephony/java/com/android/internal/telephony/WspTypeDecoder.java new file mode 100644 index 0000000000000..2984fa8a576cd --- /dev/null +++ b/telephony/java/com/android/internal/telephony/WspTypeDecoder.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + + +/** + * Implement the WSP data type decoder. + * + * @hide + */ +public class WspTypeDecoder { + + private static final int WAP_PDU_SHORT_LENGTH_MAX = 30; + private static final int WAP_PDU_LENGTH_QUOTE = 31; + + public static final int PDU_TYPE_PUSH = 0x06; + public static final int PDU_TYPE_CONFIRMED_PUSH = 0x07; + + public static final int CONTENT_TYPE_B_DRM_RIGHTS_XML = 0x4a; + public static final int CONTENT_TYPE_B_DRM_RIGHTS_WBXML = 0x4b; + public static final int CONTENT_TYPE_B_PUSH_SI = 0x2e; + public static final int CONTENT_TYPE_B_PUSH_SL = 0x30; + public static final int CONTENT_TYPE_B_PUSH_CO = 0x32; + public static final int CONTENT_TYPE_B_MMS = 0x3e; + + public static final String CONTENT_MIME_TYPE_B_DRM_RIGHTS_XML = + "application/vnd.oma.drm.rights+xml"; + public static final String CONTENT_MIME_TYPE_B_DRM_RIGHTS_WBXML = + "application/vnd.oma.drm.rights+wbxml"; + public static final String CONTENT_MIME_TYPE_B_PUSH_SI = "application/vnd.wap.sic"; + public static final String CONTENT_MIME_TYPE_B_PUSH_SL = "application/vnd.wap.slc"; + public static final String CONTENT_MIME_TYPE_B_PUSH_CO = "application/vnd.wap.coc"; + public static final String CONTENT_MIME_TYPE_B_MMS = "application/vnd.wap.mms-message"; + + public static final int PARAMETER_ID_X_WAP_APPLICATION_ID = 0x2f; + + + byte[] wspData; + int dataLength; + long unsigned32bit; + String stringValue; + + public WspTypeDecoder(byte[] pdu) { + wspData = pdu; + } + + /** + * Decode the "Text-string" type for WSP pdu + * + * @param startIndex The starting position of the "Text-string" in this pdu + * + * @return false when error(not a Text-string) occur + * return value can be retrieved by getValueString() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeTextString(int startIndex) { + int index = startIndex; + while (wspData[index] != 0) { + index++; + } + dataLength = index - startIndex + 1; + if (wspData[startIndex] == 127) { + stringValue = new String(wspData, startIndex+1, dataLength - 2); + } else { + stringValue = new String(wspData, startIndex, dataLength - 1); + } + return true; + } + + /** + * Decode the "Short-integer" type for WSP pdu + * + * @param startIndex The starting position of the "Short-integer" in this pdu + * + * @return false when error(not a Short-integer) occur + * return value can be retrieved by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeShortInteger(int startIndex) { + if ((wspData[startIndex] & 0x80) == 0) { + return false; + } + unsigned32bit = wspData[startIndex] & 0x7f; + dataLength = 1; + return true; + } + + /** + * Decode the "Long-integer" type for WSP pdu + * + * @param startIndex The starting position of the "Long-integer" in this pdu + * + * @return false when error(not a Long-integer) occur + * return value can be retrieved by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeLongInteger(int startIndex) { + int lengthMultiOctet = wspData[startIndex] & 0xff; + + if (lengthMultiOctet > WAP_PDU_SHORT_LENGTH_MAX) { + return false; + } + unsigned32bit = 0; + for (int i=1; i<=lengthMultiOctet; i++) { + unsigned32bit = (unsigned32bit << 8) | (wspData[startIndex+i] & 0xff); + } + dataLength = 1+lengthMultiOctet; + return true; + } + + /** + * Decode the "Integer-Value" type for WSP pdu + * + * @param startIndex The starting position of the "Integer-Value" in this pdu + * + * @return false when error(not a Integer-Value) occur + * return value can be retrieved by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeIntegerValue(int startIndex) { + if (decodeShortInteger(startIndex) == true) { + return true; + } + return decodeLongInteger(startIndex); + } + + /** + * Decode the "Uintvar-integer" type for WSP pdu + * + * @param startIndex The starting position of the "Uintvar-integer" in this pdu + * + * @return false when error(not a Uintvar-integer) occur + * return value can be retrieved by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeUintvarInteger(int startIndex) { + int index = startIndex; + + unsigned32bit = 0; + while ((wspData[index] & 0x80) != 0) { + if ((index - startIndex) >= 4) { + return false; + } + unsigned32bit = (unsigned32bit << 7) | (wspData[index] & 0x7f); + index++; + } + unsigned32bit = (unsigned32bit << 7) | (wspData[index] & 0x7f); + dataLength = index - startIndex + 1; + return true; + } + + /** + * Decode the "Value-length" type for WSP pdu + * + * @param startIndex The starting position of the "Value-length" in this pdu + * + * @return false when error(not a Value-length) occur + * return value can be retrieved by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeValueLength(int startIndex) { + if ((wspData[startIndex] & 0xff) > WAP_PDU_LENGTH_QUOTE) { + return false; + } + if (wspData[startIndex] < WAP_PDU_LENGTH_QUOTE) { + unsigned32bit = wspData[startIndex]; + dataLength = 1; + } else { + decodeUintvarInteger(startIndex+1); + dataLength ++; + } + return true; + } + + /** + * Decode the "Extension-media" type for WSP pdu + * + * @param startIndex The starting position of the "Extension-media" in this pdu + * + * @return false when error(not a Extension-media) occur + * return value can be retrieved by getValueString() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeExtensionMedia(int startIndex) { + int index = startIndex; + while (wspData[index] != 0) { + index++; + } + dataLength = index - startIndex + 1; + stringValue = new String(wspData, startIndex, dataLength - 1); + return true; + } + + /** + * Decode the "Constrained-encoding" type for WSP pdu + * + * @param startIndex The starting position of the "Constrained-encoding" in this pdu + * + * @return false when error(not a Constrained-encoding) occur + * return value can be retrieved first by getValueString() and second by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeConstrainedEncoding(int startIndex) { + if (decodeShortInteger(startIndex) == true) { + stringValue = null; + return true; + } + return decodeExtensionMedia(startIndex); + } + + /** + * Decode the "Content-type" type for WSP pdu + * + * @param startIndex The starting position of the "Content-type" in this pdu + * + * @return false when error(not a Content-type) occur + * return value can be retrieved first by getValueString() and second by getValue32() + * method length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeContentType(int startIndex) { + int mediaPrefixLength; + long mediaFieldLength; + + if (decodeValueLength(startIndex) == false) { + return decodeConstrainedEncoding(startIndex); + } + mediaPrefixLength = getDecodedDataLength(); + mediaFieldLength = getValue32(); + if (decodeIntegerValue(startIndex + mediaPrefixLength) == true) { + dataLength += mediaPrefixLength; + stringValue = null; + return true; + } + if (decodeExtensionMedia(startIndex + mediaPrefixLength) == true) { + dataLength += mediaPrefixLength; + return true; + } + return false; + } + + /** + * Decode the "Content length" type for WSP pdu + * + * @param startIndex The starting position of the "Content length" in this pdu + * + * @return false when error(not a Content length) occur + * return value can be retrieved by getValue32() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeContentLength(int startIndex) { + return decodeIntegerValue(startIndex); + } + + /** + * Decode the "Content location" type for WSP pdu + * + * @param startIndex The starting position of the "Content location" in this pdu + * + * @return false when error(not a Content location) occur + * return value can be retrieved by getValueString() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeContentLocation(int startIndex) { + return decodeTextString(startIndex); + } + + /** + * Decode the "X-Wap-Application-Id" type for WSP pdu + * + * @param startIndex The starting position of the "X-Wap-Application-Id" in this pdu + * + * @return false when error(not a X-Wap-Application-Id) occur + * return value can be retrieved first by getValueString() and second by getValue32() + * method length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeXWapApplicationId(int startIndex) { + if (decodeIntegerValue(startIndex) == true) { + stringValue = null; + return true; + } + return decodeTextString(startIndex); + } + + /** + * Decode the "X-Wap-Content-URI" type for WSP pdu + * + * @param startIndex The starting position of the "X-Wap-Content-URI" in this pdu + * + * @return false when error(not a X-Wap-Content-URI) occur + * return value can be retrieved by getValueString() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeXWapContentURI(int startIndex) { + return decodeTextString(startIndex); + } + + /** + * Decode the "X-Wap-Initiator-URI" type for WSP pdu + * + * @param startIndex The starting position of the "X-Wap-Initiator-URI" in this pdu + * + * @return false when error(not a X-Wap-Initiator-URI) occur + * return value can be retrieved by getValueString() method + * length of data in pdu can be retrieved by getValue32() method + */ + public boolean decodeXWapInitiatorURI(int startIndex) { + return decodeTextString(startIndex); + } + + /** + * The data length of latest operation. + */ + public int getDecodedDataLength() { + return dataLength; + } + + /** + * The 32-bits result of latest operation. + */ + public long getValue32() { + return unsigned32bit; + } + + /** + * The String result of latest operation. + */ + public String getValueString() { + return stringValue; + } +} diff --git a/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java index 417600408fa30..6eea1d49b685a 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/SMSDispatcher.java @@ -37,6 +37,7 @@ import android.provider.Settings; import android.provider.Telephony.Sms.Intents; import android.telephony.gsm.SmsMessage; import android.telephony.gsm.SmsManager; +import com.android.internal.telephony.WapPushOverSms; import android.telephony.ServiceState; import android.util.Config; import com.android.internal.util.HexDump; @@ -62,37 +63,6 @@ final class SMSDispatcher extends Handler { /** Default timeout for SMS sent query */ private static final int DEFAULT_SMS_TIMOUEOUT = 6000; - private static final int WAP_PDU_TYPE_PUSH = 0x06; - - private static final int WAP_PDU_TYPE_CONFIRMED_PUSH = 0x07; - - private static final byte DRM_RIGHTS_XML = (byte)0xca; - - private static final String DRM_RIGHTS_XML_MIME_TYPE = "application/vnd.oma.drm.rights+xml"; - - private static final byte DRM_RIGHTS_WBXML = (byte)0xcb; - - private static final String DRM_RIGHTS_WBXML_MIME_TYPE = - "application/vnd.oma.drm.rights+wbxml"; - - private static final byte WAP_SI_MIME_PORT = (byte)0xae; - - private static final String WAP_SI_MIME_TYPE = "application/vnd.wap.sic"; - - private static final byte WAP_SL_MIME_PORT = (byte)0xb0; - - private static final String WAP_SL_MIME_TYPE = "application/vnd.wap.slc"; - - private static final byte WAP_CO_MIME_PORT = (byte)0xb2; - - private static final String WAP_CO_MIME_TYPE = "application/vnd.wap.coc"; - - private static final int WAP_PDU_SHORT_LENGTH_MAX = 30; - - private static final int WAP_PDU_LENGTH_QUOTE = 31; - - private static final String MMS_MIME_TYPE = "application/vnd.wap.mms-message"; - private static final String[] RAW_PROJECTION = new String[] { "pdu", "sequence", @@ -124,6 +94,8 @@ final class SMSDispatcher extends Handler { private final GSMPhone mPhone; + private final WapPushOverSms mWapPush; + private final Context mContext; private final ContentResolver mResolver; @@ -208,6 +180,7 @@ final class SMSDispatcher extends Handler { SMSDispatcher(GSMPhone phone) { mPhone = phone; + mWapPush = new WapPushOverSms(phone); mContext = phone.getContext(); mResolver = mContext.getContentResolver(); mCm = phone.mCM; @@ -538,7 +511,7 @@ final class SMSDispatcher extends Handler { if (destPort != -1) { if (destPort == SmsHeader.PORT_WAP_PUSH) { - dispatchWapPdu(sms.getUserData()); + mWapPush.dispatchWapPdu(sms.getUserData()); } // The message was sent to a port, so concoct a URI for it dispatchPortAddressedPdus(pdus, destPort); @@ -621,7 +594,7 @@ final class SMSDispatcher extends Handler { } // Handle the PUSH - dispatchWapPdu(output.toByteArray()); + mWapPush.dispatchWapPdu(output.toByteArray()); break; } @@ -663,122 +636,6 @@ final class SMSDispatcher extends Handler { intent, "android.permission.RECEIVE_SMS"); } - /** - * Dispatches inbound messages that are in the WAP PDU format. See - * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format. - * - * @param pdu The WAP PDU, made up of one or more SMS PDUs - */ - private void dispatchWapPdu(byte[] pdu) { - int index = 0; - int transactionId = pdu[index++] & 0xFF; - int pduType = pdu[index++] & 0xFF; - int headerLength = 0; - - if ((pduType != WAP_PDU_TYPE_PUSH) && - (pduType != WAP_PDU_TYPE_CONFIRMED_PUSH)) { - Log.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType); - return; - } - - /** - * Parse HeaderLen(unsigned integer). - * From wap-230-wsp-20010705-a section 8.1.2 - * The maximum size of a uintvar is 32 bits. - * So it will be encoded in no more than 5 octets. - */ - int temp = 0; - do { - temp = pdu[index++]; - headerLength = headerLength << 7; - headerLength |= temp & 0x7F; - } while ((temp & 0x80) != 0); - - int headerStartIndex = index; - - /** - * Parse Content-Type. - * From wap-230-wsp-20010705-a section 8.4.2.24 - * - * Content-type-value = Constrained-media | Content-general-form - * Content-general-form = Value-length Media-type - * Media-type = (Well-known-media | Extension-Media) *(Parameter) - * Value-length = Short-length | (Length-quote Length) - * Short-length = (octet <= WAP_PDU_SHORT_LENGTH_MAX) - * Length-quote = (WAP_PDU_LENGTH_QUOTE) - * Length = Uintvar-integer - */ - // Parse Value-length. - if ((pdu[index] & 0xff) <= WAP_PDU_SHORT_LENGTH_MAX) { - // Short-length. - index++; - } else if (pdu[index] == WAP_PDU_LENGTH_QUOTE) { - // Skip Length-quote. - index++; - // Skip Length. - // Now we assume 8bit is enough to store the content-type length. - index++; - } - String mimeType; - switch (pdu[headerStartIndex]) - { - case DRM_RIGHTS_XML: - mimeType = DRM_RIGHTS_XML_MIME_TYPE; - break; - case DRM_RIGHTS_WBXML: - mimeType = DRM_RIGHTS_WBXML_MIME_TYPE; - break; - case WAP_SI_MIME_PORT: - // application/vnd.wap.sic - mimeType = WAP_SI_MIME_TYPE; - break; - case WAP_SL_MIME_PORT: - mimeType = WAP_SL_MIME_TYPE; - break; - case WAP_CO_MIME_PORT: - mimeType = WAP_CO_MIME_TYPE; - break; - default: - int start = index; - - // Skip text-string. - // Now we assume the mimetype is Extension-Media. - while (pdu[index++] != '\0') { - ; - } - mimeType = new String(pdu, start, index-start-1); - break; - } - - // XXX Skip the remainder of the header for now - int dataIndex = headerStartIndex + headerLength; - byte[] data; - if (pdu[headerStartIndex] == WAP_CO_MIME_PORT) - { - // because SMSDispatcher can't parse push headers "Content-Location" and - // X-Wap-Content-URI, so pass the whole push to CO application. - data = pdu; - } else - { - data = new byte[pdu.length - dataIndex]; - System.arraycopy(pdu, dataIndex, data, 0, data.length); - } - - // Notify listeners about the WAP PUSH - Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION); - intent.setType(mimeType); - intent.putExtra("transactionId", transactionId); - intent.putExtra("pduType", pduType); - intent.putExtra("data", data); - - if (mimeType.equals(MMS_MIME_TYPE)) { - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_MMS"); - } else { - mPhone.getContext().sendBroadcast( - intent, "android.permission.RECEIVE_WAP_PUSH"); - } - } /** * Send a multi-part text based SMS. @@ -913,7 +770,7 @@ final class SMSDispatcher extends Handler { sendSms(tracker); } } - + /** * Send a SMS * diff --git a/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java index f37d1ebf6bf95..995173b0f2cf4 100644 --- a/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/ServiceStateTracker.java @@ -35,6 +35,7 @@ import android.database.ContentObserver; import android.os.AsyncResult; import android.os.Handler; import android.os.Message; +import android.os.PowerManager; import android.os.Registrant; import android.os.RegistrantList; import android.os.SystemClock; @@ -141,6 +142,9 @@ final class ServiceStateTracker extends Handler // Already sent the event-log for no gprs register private boolean mReportedGprsNoReg = false; + // Wake lock used while setting time of day. + private PowerManager.WakeLock mWakeLock; + private static final String WAKELOCK_TAG = "ServiceStateTracker"; // Keep track of SPN display rules, so we only broadcast intent if something changes. private String curSpn = null; @@ -230,7 +234,11 @@ final class ServiceStateTracker extends Handler cellLoc = new GsmCellLocation(); newCellLoc = new GsmCellLocation(); - cm.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null); + PowerManager powerManager = + (PowerManager)phone.getContext().getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); + + cm.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null); cm.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); cm.registerForNetworkStateChanged(this, EVENT_NETWORK_STATE_CHANGED, null); @@ -1348,42 +1356,49 @@ final class ServiceStateTracker extends Handler return; } - if (getAutoTime()) { - long millisSinceNitzReceived - = SystemClock.elapsedRealtime() - nitzReceiveTime; + try { + mWakeLock.acquire(); - if (millisSinceNitzReceived < 0) { - // Sanity check: something is wrong - Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled " - + "backwards since NITZ time was received, " - + nitz); - return; + if (getAutoTime()) { + long millisSinceNitzReceived + = SystemClock.elapsedRealtime() - nitzReceiveTime; + + if (millisSinceNitzReceived < 0) { + // Sanity check: something is wrong + Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled " + + "backwards since NITZ time was received, " + + nitz); + return; + } + + if (millisSinceNitzReceived > Integer.MAX_VALUE) { + // If the time is this far off, something is wrong > 24 days! + Log.i(LOG_TAG, "NITZ: not setting time, processing has taken " + + (millisSinceNitzReceived / (1000 * 60 * 60 * 24)) + + " days"); + return; + } + + // Note: with range checks above, cast to int is safe + c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived); + + Log.i(LOG_TAG, "NITZ: Setting time of day to " + c.getTime() + + " NITZ receive delay(ms): " + millisSinceNitzReceived + + " gained(ms): " + + (c.getTimeInMillis() - System.currentTimeMillis()) + + " from " + nitz); + + SystemClock.setCurrentTimeMillis(c.getTimeInMillis()); + Log.i(LOG_TAG, "NITZ: after Setting time of day"); } - - if (millisSinceNitzReceived > Integer.MAX_VALUE) { - // If the time is this far off, something is wrong > 24 days! - Log.i(LOG_TAG, "NITZ: not setting time, processing has taken " - + (millisSinceNitzReceived / (1000 * 60 * 60 * 24)) - + " days"); - return; + SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis())); + saveNitzTime(c.getTimeInMillis()); + if (Config.LOGV) { + long end = SystemClock.elapsedRealtime(); + Log.v(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start)); } - - // Note: with range checks above, cast to int is safe - c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived); - - Log.i(LOG_TAG, "NITZ: Setting time of day to " + c.getTime() - + " NITZ receive delay(ms): " + millisSinceNitzReceived - + " gained(ms): " - + (c.getTimeInMillis() - System.currentTimeMillis()) - + " from " + nitz); - - setAndBroadcastNetworkSetTime(c.getTimeInMillis()); - } - SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis())); - saveNitzTime(c.getTimeInMillis()); - if (Config.LOGV) { - long end = SystemClock.elapsedRealtime(); - Log.v(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start)); + } finally { + mWakeLock.release(); } } catch (RuntimeException ex) { Log.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz, ex); diff --git a/tests/AndroidTests/AndroidManifest.xml b/tests/AndroidTests/AndroidManifest.xml index 36f7b9be52df5..843d844379ae4 100644 --- a/tests/AndroidTests/AndroidManifest.xml +++ b/tests/AndroidTests/AndroidManifest.xml @@ -44,8 +44,6 @@ - - diff --git a/tests/AndroidTests/src/com/android/unit_tests/BluetoothTest.java b/tests/AndroidTests/src/com/android/unit_tests/BluetoothTest.java deleted file mode 100644 index 0a60319761e5c..0000000000000 --- a/tests/AndroidTests/src/com/android/unit_tests/BluetoothTest.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.unit_tests; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothIntent; -import android.bluetooth.BluetoothClass; -import android.bluetooth.IBluetoothDeviceCallback; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.SystemProperties; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; - -import junit.framework.Assert; - -import java.util.List; -import java.util.HashSet; - -public class BluetoothTest extends AndroidTestCase { - private static final String TAG = "BluetoothTest"; - - @MediumTest - public void testBluetoothSmokeTest() throws Exception { - - BluetoothDevice btDevice = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - - // TODO: Use a more reliable check to see if this product should - // support Bluetooth - see bug 988521 - boolean shouldSupportBluetooth = SystemProperties.get("ro.kernel.qemu").equals("0"); - - assertFalse(shouldSupportBluetooth && btDevice == null); - if (!shouldSupportBluetooth) { - Log.i(TAG, "Skipping test - this device does not have bluetooth."); - return; - } - - boolean bluetoothWasEnabled = btDevice.isEnabled(); - - if (bluetoothWasEnabled) { - Log.i(TAG, "Bluetooth already enabled"); - } else { - Log.i(TAG, "Enabling Bluetooth..."); - btDevice.enable(); - Log.i(TAG, "Bluetooth enabled"); - } - Assert.assertTrue(btDevice.isEnabled()); - - String myAddress = btDevice.getAddress(); - Assert.assertTrue(myAddress != null); - Log.i(TAG, "My Bluetooth Address is " + myAddress); - Assert.assertFalse(myAddress.equals("00:00:00:00:00:00")); - - if (!bluetoothWasEnabled) { - Log.i(TAG, "Disabling Bluetooth..."); - btDevice.disable(); - Log.i(TAG, "Bluetooth disabled"); - } - } - - private boolean listenA2dp = false; - private void listenA2dp() { - if (!listenA2dp) { - listenA2dp = true; - getContext().registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int state = intent.getIntExtra(BluetoothA2dp.SINK_STATE, -1); - int oldState = intent.getIntExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, -1); - Log.e(TAG, "A2DP INTENT: state = " + state + " oldState = " + oldState); - } - }, new IntentFilter(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)); - } - } - - @MediumTest - public void testA2dpSmokeTest() throws Exception { - listenA2dp(); - - BluetoothA2dp a2dp = new BluetoothA2dp(getContext()); - - List sinks = a2dp.listConnectedSinks(); - Log.e(TAG, "listConnectedSinks()..."); - for (String sink : sinks) { - Log.e(TAG, sink + " state = " + a2dp.getSinkState(sink)); - } - } - - @MediumTest - public void testA2dpConnect() throws Exception { - listenA2dp(); - String address = SystemProperties.get("debug.a2dp.address", ""); - BluetoothA2dp a2dp = new BluetoothA2dp(getContext()); - int result = a2dp.connectSink(address); - Log.e(TAG, "connectSink(" + address + ") = " + result); - } - - @MediumTest - public void testA2dpDisconnect() throws Exception { - listenA2dp(); - String address = SystemProperties.get("debug.a2dp.address", ""); - BluetoothA2dp a2dp = new BluetoothA2dp(getContext()); - int result = a2dp.disconnectSink(address); - Log.e(TAG, "disconnectSink(" + address + ") = " + result); - } - - @MediumTest - public void testBluetoothEnabled() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - - if (device.isEnabled()) { - Log.i(TAG, "isEnabled() = yes"); - } else { - Log.i(TAG, "isEnabled() = no"); - } - } - - @MediumTest - public void testEnableBluetooth() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - device.enable(); - } - - @MediumTest - public void testEnableBluetoothWithCallback() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - if (!device.enable(mCallback)) { - Log.e(TAG, "enable() failed"); - } - } - - @MediumTest - public void testDisableBluetooth() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - device.disable(); - } - - @LargeTest - public void testDiscovery() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - if (device.isEnabled()) { - getContext().registerReceiver((BroadcastReceiver)new TestDiscoveryReceiver(), - new IntentFilter(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)); - Log.i(TAG, "Starting discovery..."); - String result = device.startDiscovery() ? "true" : "false"; - Log.i(TAG, "startDiscovery() = " + result); - } - } - - @LargeTest - public void testMultipleDiscovery() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - if (device.isEnabled()) { - getContext().registerReceiver((BroadcastReceiver)new TestDiscoveryReceiver(), - new IntentFilter(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION)); - String result; - Log.i(TAG, "Starting multiple discovery..."); - for (int i = 0; i < 5; i++) { - result = device.startDiscovery() ? "true" : "false"; - Log.i(TAG, "startDiscovery() = " + result); - } - } - } - private class TestDiscoveryReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { - String address = intent.getStringExtra(BluetoothIntent.ADDRESS); - int deviceClass = intent.getIntExtra(BluetoothIntent.CLASS, -1); - short rssi = intent.getShortExtra(BluetoothIntent.RSSI, (short)-1); - Log.i(TAG, "Discovered Device: " + address + " " + deviceClass + " " + rssi); - } - } - - private IBluetoothDeviceCallback mCallback = new IBluetoothDeviceCallback.Stub() { - public void onEnableResult(int res) { - String result = "unknown"; - switch (res) { - case BluetoothDevice.RESULT_SUCCESS: - result = "success"; - break; - case BluetoothDevice.RESULT_FAILURE: - result = "FAILURE"; - break; - } - Log.i(TAG, "onEnableResult(" + result + ")"); - } - public void onGetRemoteServiceChannelResult(String device, int channel) {} - }; - - @SmallTest - public void testCreateBond() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - if (!device.createBond("01:23:45:67:89:AB")) { - Log.e(TAG, "createBonding() failed"); - } - } - - @SmallTest - public void testIsPeriodicDiscovery() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - boolean ret = device.isPeriodicDiscovery(); - if (ret) { - Log.i(TAG, "isPeriodicDiscovery() = TRUE"); - } else { - Log.i(TAG, "isPeriodicDiscovery() = FALSE"); - } - } - - @LargeTest - public void testListBondings() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - String[] addresses = device.listBonds(); - if (addresses == null) { - Log.i(TAG, "Bluetooth disabled"); - return; - } - for (String address : addresses) { - String name = device.getRemoteName(address); - Log.i(TAG, "BONDING: " + address + " (" + name + ")"); - } - } - - @LargeTest - public void testListAclConnections() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - String[] addresses = device.listAclConnections(); - if (addresses == null) { - Log.i(TAG, "Bluetooth disabled"); - return; - } - for (String address : addresses) { - String name = device.getRemoteName(address); - Log.i(TAG, "CONNECTION: " + address + " (" + name + ")"); - } - } - - @LargeTest - public void testListRemoteDevices() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - String[] addresses = device.listRemoteDevices(); - if (addresses == null) { - Log.i(TAG, "Bluetooth disabled"); - return; - } - for (String address : addresses) { - String name = device.getRemoteName(address); - Log.i(TAG, "KNOWN DEVICE: " + address + " (" + name + ")"); - } - } - - @MediumTest - public void testSetupBTIntentRecv() throws Exception { - BluetoothDevice device = - (BluetoothDevice)getContext().getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { - Log.i(TAG, "Device not Bluetooth capable, skipping test"); - return; - } - if (device.isEnabled()) { - IntentFilter filter = new IntentFilter(BluetoothIntent.ENABLED_ACTION); - filter.addAction(BluetoothIntent.ENABLED_ACTION); - filter.addAction(BluetoothIntent.DISABLED_ACTION); - filter.addAction(BluetoothIntent.NAME_CHANGED_ACTION); - filter.addAction(BluetoothIntent.DISCOVERY_STARTED_ACTION); - filter.addAction(BluetoothIntent.DISCOVERY_COMPLETED_ACTION); - filter.addAction(BluetoothIntent.PAIRING_REQUEST_ACTION); - filter.addAction(BluetoothIntent.PAIRING_CANCEL_ACTION); - filter.addAction(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION); - filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION); - filter.addAction(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION); - filter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION); - filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION); - filter.addAction(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION); - filter.addAction(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION); - filter.addAction(BluetoothIntent.REMOTE_NAME_FAILED_ACTION); - filter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION); - filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION); - getContext().registerReceiver( - (BroadcastReceiver)new BluetoothIntentReceiver(), filter); - Log.i(TAG, "Listening for BLUETOOTH INTENTS...."); - } else { - Log.e(TAG, "BT not enabled"); - } - } - - - private class BluetoothIntentReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { - String msg = ""; - - String address = intent.getStringExtra(BluetoothIntent.ADDRESS); - if (address != null) { - msg += " address=" + address; - } - - int deviceClass = intent.getIntExtra(BluetoothIntent.CLASS, BluetoothClass.ERROR); - if (deviceClass != BluetoothClass.ERROR) { - msg += " class=" + deviceClass; - } - - int rssi = intent.getIntExtra(BluetoothIntent.RSSI, -1); - if (rssi != -1) { - msg += " rssi=" + rssi; - } - - String name = intent.getStringExtra(BluetoothIntent.NAME); - if (name != null) { - msg += " name=" + name; - } - - int state = intent.getIntExtra(BluetoothIntent.HEADSET_STATE, -10); - if (state != -10) { - msg += " headset state=" + state; - } - Log.i(TAG, "BLUETOOTH INTENT: " + intent.getAction() + msg); - } - } - - - private static final int[] ALL_SERVICE_CLASSES = new int[] { - BluetoothClass.Service.LIMITED_DISCOVERABILITY, - BluetoothClass.Service.POSITIONING, - BluetoothClass.Service.NETWORKING, - BluetoothClass.Service.RENDER, - BluetoothClass.Service.CAPTURE, - BluetoothClass.Service.OBJECT_TRANSFER, - BluetoothClass.Service.AUDIO, - BluetoothClass.Service.TELEPHONY, - BluetoothClass.Service.INFORMATION - }; - private void assertOnlyTheseServiceClassesAreSupported(int deviceClass, HashSet serviceClasses) { - for (int serviceClassType : ALL_SERVICE_CLASSES) { - Assert.assertEquals(serviceClasses.contains(new Integer(serviceClassType)), - BluetoothClass.Service.hasService(deviceClass, serviceClassType)); - } - } - - @SmallTest - public void testDeviceClass() throws Exception { - // This test does not require bluetooth hardware - int deviceClass; - HashSet serviceClasses; - - deviceClass = BluetoothClass.ERROR; // bogus class - serviceClasses = new HashSet(); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.ERROR, BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(BluetoothClass.ERROR, BluetoothClass.Device.getDevice(deviceClass)); - - deviceClass = 0x10210C; // mac book pro - serviceClasses = new HashSet(); - serviceClasses.add(BluetoothClass.Service.OBJECT_TRANSFER); - serviceClasses.add(BluetoothClass.Service.LIMITED_DISCOVERABILITY); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.Device.Major.COMPUTER, - BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(0x10C, BluetoothClass.Device.getDevice(deviceClass)); - - // mac book pro with some unused bits set. Expecting the same results - deviceClass = 0xFF10210F; - serviceClasses = new HashSet(); - serviceClasses.add(BluetoothClass.Service.OBJECT_TRANSFER); - serviceClasses.add(BluetoothClass.Service.LIMITED_DISCOVERABILITY); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.Device.Major.COMPUTER, - BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(0x10C, BluetoothClass.Device.getDevice(deviceClass)); - - deviceClass = 0x3E0100; // droid.corp.google.com - serviceClasses = new HashSet(); - serviceClasses.add(BluetoothClass.Service.AUDIO); - serviceClasses.add(BluetoothClass.Service.OBJECT_TRANSFER); - serviceClasses.add(BluetoothClass.Service.CAPTURE); - serviceClasses.add(BluetoothClass.Service.RENDER); - serviceClasses.add(BluetoothClass.Service.NETWORKING); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.Device.Major.COMPUTER, - BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(0x100, BluetoothClass.Device.getDevice(deviceClass)); - - deviceClass = 0x40020C; // Android - serviceClasses = new HashSet(); - serviceClasses.add(BluetoothClass.Service.TELEPHONY); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.Device.Major.PHONE, BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(0x20C, BluetoothClass.Device.getDevice(deviceClass)); - - // Motorola T305 & Jabra BT125 & Jabra BT250V - // This seems to be a very common headset & handsfree device code - deviceClass = 0x200404; - serviceClasses = new HashSet(); - serviceClasses.add(BluetoothClass.Service.AUDIO); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.Device.Major.AUDIO_VIDEO, - BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET, - BluetoothClass.Device.getDevice(deviceClass)); - - // Audi UHV 0128 - deviceClass = 0x200408; - serviceClasses = new HashSet(); - serviceClasses.add(BluetoothClass.Service.AUDIO); - assertOnlyTheseServiceClassesAreSupported(deviceClass, serviceClasses); - Assert.assertEquals(BluetoothClass.Device.Major.AUDIO_VIDEO, - BluetoothClass.Device.Major.getDeviceMajor(deviceClass)); - Assert.assertEquals(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE, - BluetoothClass.Device.getDevice(deviceClass)); - } -} diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 5d9e140d6614b..eb7d6f559ecb3 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -5,6 +5,7 @@ // #include "Main.h" #include "AaptAssets.h" +#include "ResourceTable.h" #include #include @@ -190,11 +191,20 @@ bail: ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp& assets) { + ResourceFilter filter; + status_t status = filter.parse(bundle->getConfigurations()); + if (status != NO_ERROR) { + return -1; + } + ssize_t count = 0; const size_t N = assets->getGroupEntries().size(); for (size_t i=0; igetGroupEntries()[i]; + if (!filter.match(ge.toParams())) { + continue; + } ssize_t res = processAssets(bundle, zip, assets, ge); if (res < 0) { return res; diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java index 1799560dea7e6..fc750e2595ba1 100644 --- a/wifi/java/android/net/wifi/WifiMonitor.java +++ b/wifi/java/android/net/wifi/WifiMonitor.java @@ -28,7 +28,7 @@ import java.util.regex.Matcher; * Listens for events from the wpa_supplicant server, and passes them on * to the {@link WifiStateTracker} for handling. Runs in its own thread. * - * {@hide} + * @hide */ public class WifiMonitor { @@ -119,14 +119,8 @@ public class WifiMonitor { private final WifiStateTracker mWifiStateTracker; - private boolean supplicantConnected; - - private boolean oneShot; - public WifiMonitor(WifiStateTracker tracker) { mWifiStateTracker = tracker; - supplicantConnected = false; - oneShot = true; } public void startMonitoring() { @@ -144,10 +138,17 @@ public class WifiMonitor { public void run() { + if (connectToSupplicant()) { + // Send a message indicating that it is now possible to send commands + // to the supplicant + mWifiStateTracker.notifySupplicantConnection(); + } else { + mWifiStateTracker.notifySupplicantLost(); + return; + } + //noinspection InfiniteLoopStatement for (;;) { - ensureSupplicantConnection(); - String eventStr = WifiNative.waitForEvent(); if (eventStr == null) { @@ -214,42 +215,32 @@ public class WifiMonitor { handleSupplicantStateChange(eventData); } else if (event == DRIVER_STATE) { handleDriverEvent(eventData); + } else if (event == TERMINATING) { + mWifiStateTracker.notifySupplicantLost(); + // If supplicant is gone, exit the thread + break; } else { handleEvent(event, eventData); - // If supplicant is gone, exit the thread - if (event == TERMINATING) { - break; - } } } } - private void ensureSupplicantConnection() { - while (!supplicantConnected) { - boolean connected; + private boolean connectToSupplicant() { + int connectTries = 0; + + while (true) { synchronized (mWifiStateTracker) { - connected = WifiNative.connectToSupplicant(); - } - if (!connected) { - /* - * If we fail to connect on the very first attempt, send a message - * indicating a lost connection to the supplicant, so that the - * receiver can initialize to the proper state. - */ - if (oneShot) { - oneShot = false; - mWifiStateTracker.notifySupplicantLost(); + if (WifiNative.connectToSupplicant()) { + return true; } + } + if (connectTries++ < 3) { nap(5); } else { - supplicantConnected = true; - oneShot = false; - // Send a message indicating that it is now possible to send commands - // to the supplicant - mWifiStateTracker.notifySupplicantConnection(); - + break; } } + return false; } private void handlePasswordKeyMayBeIncorrect() { @@ -287,11 +278,6 @@ public class WifiMonitor { mWifiStateTracker.notifyScanResultsAvailable(); break; - case TERMINATING: - supplicantConnected = false; - mWifiStateTracker.notifySupplicantLost(); - break; - case UNKNOWN: break; } @@ -370,7 +356,7 @@ public class WifiMonitor { private static void nap(int secs) { try { Thread.sleep(secs * 1000); - } catch (InterruptedException e) { + } catch (InterruptedException ignore) { } } }